I am having problems getting HidD_GetSerialNumberString to simply give me the serialnumber of some devices.
On some devices it works fine, but on others all I get is "ÿÿÿ" as the serialnumber.
I am suspecting it has something to do with Delphi 2009 and it's unicode, but I simply cannot get my head around it.
Is there a solution to this?
Edit (declaration) :
type
THidD_GetSerialNumberString = function(HidDeviceObject: THandle; Buffer: PWideChar; BufferLength: Integer): LongBool; stdcall;
var
HidD_GetSerialNumberString: THidD_GetSerialNumberString;
//in the LoadHid-function
#HidD_GetSerialNumberString := GetModuleSymbolEx(HidLib, 'HidD_GetSerialNumberString', Result);
Usage :
var
test : PWideChar;
...
GetMem(test, 512);
if HidD_GetSerialNumberString(HidFileHandle, Test, 512) then
FSerialNumber := test; //FSerialNumber is WideString
FreeMem(test);
Your code is fine in all versions of Delphi. Although clearly it involves a UTF-16 to ANSI conversion when compiled by a pre-Unicode Delphi. However, since you use Delphi 2009 that is not an issue.
There are no problems regarding Unicode conversions. The HidD_GetSerialNumberString function fills out the buffer that you supply with a null-terminated UTF-16 string. And since you declared test to be exactly that, Delphi will generate code necessary to copy to a Delphi string variable.
Is it possible that HidD_GetSerialNumberString is returning false and so FSerialNumber is not initialized properly? Other than that, the only conclusions that one can make are that:
HidD_GetSerialNumberString is lying to you when it returns True.
The serial number returned by calling HidD_GetSerialNumberString really is ÿÿÿ.
Related
I am trying to use NPAPI Framework from Yury Sidorov by following this answer:
How to embed Delphi VCL form into HTML page using NPAPI
but I get an error with NPPlugin.pas. I am using delphi XE7 and here what i did by following Krom Stern instructions
procedure DefDebugOut(const Msg: string);
begin
OutputDebugStringA(PAnsiChar(Msg + #13#10)); // Changed From Pchar To PAnsiChar
end;
but still getting error with compiler with this message
[dcc32 Error] NPPlugin.pas(2215): E2010 Incompatible types: 'PWideChar' and 'PAnsiChar'
Error raised on this procedure
procedure TPlugin.SetException(const msg: string);
var
s: String;
begin
try
{$ifopt D+} NPP_DebugOut('Exception: ' + msg); {$endif}
s:=UTF8Encode(msg);
NPN_SetException(m_pScriptableObject, PAnsiChar(s));
except
{ prevent any exception from leaking out of DLL }
end;
end;
here is the procedure NPN_SetException
procedure NPN_SetException(npobj: PNPObject; msg: PNPUTF8);
begin
NavigatorFuncs.SetException(npobj, msg);
end;
I'll start with a piece by piece breakdown of what we can see. Bear in mind that we don't have NPPlugin.pas at hand and have to infer its contents from the information in the question. All the same, I think it's possible for us to do that accurately.
s := UTF8Encode(msg);
Here s is of type string. That's an alias for UnicodeString, encoded as UTF-16. So you convert from UTF-16 to UTF-8 and then back to UTF16.
You need it like this:
NPN_SetException(m_pScriptableObject, PAnsiChar(UTF8Encode(msg)));
Alternatively, if you need a variable to hold UTF-8 encoded text, declare it to be UTF8String which is AnsiString(65001). If you changed the type of s to be UTF8String then the code in the question would be correct. Although somewhat more verbose than it would need to be.
Another problem is here:
OutputDebugStringA(PAnsiChar(Msg + #13#10));
Your cast doesn't make Msg actually be 8 bit encoded. However, you don't want to use the ANSI version of the function. You need this:
OutputDebugString(PChar(Msg + sLineBreak));
Your exception handler is misguided. It is the DLL's job not to leak exceptions. If you attempt to catch and suppress them all you will simply mask errors in your own code. You need to remove that exception handler and check for errors by following the instructions given by the library documentation.
Now to the bigger picture. None of the above explains your reported error. The only sound explanation for that is that your declaration for NPN_SetException accepts wide text. In which case you could make the code compile simply by writing this:
NPN_SetException(m_pScriptableObject, PChar(msg));
Of course, that makes the appearance of UTF-8 somewhat inexplicable. In fact the library Mozilla does accept 8 bit text, UTF-8 encoded. So why would NPN_SetException expect to be passed UTF-16 text? Well it doesn't. The explanation is that you have declared NPN_SetException incorrectly. So, just to be clear, whilst PChar(msg) would make your code compile, it would not resolve your problem. You would be left with code that failed at runtime.
So, how did this happen? You've taken a working piece of code that used PChar aliased to PAnsiChar onto a Delphi with PChar aliased to PWideChar and not translated correctly. Even when you get your code to compile, it will not work correctly. You started with code like this:
function NPN_SetException(..., Msg: PChar): ...;
On older Delphi versions where PChar was PAnsiChar then this was correct. You are compiling this now on XE7 where PChar is PWideChar and so this is not correct. It needs to be:
function NPN_SetException(..., Msg: PAnsiChar): ...;
Then the calling code can be:
NPN_SetException(m_pScriptableObject, PAnsiChar(UTF8Encode(msg)));
My advice is that you:
Step back and revisit the handling of Unicode in Delphi.
Go back to you original code and change all the Mozilla interface code that uses PChar to PAnsiChar.
Whenever you need to provide PAnsiChar do it with PAnsiChar(UTF8Encode(str)).
This NPAPI code was clearly designed for older versions of Delphi before the switch to Unicode in Delphi 2009. The default String/(P)Char types are no longer aliases for AnsiString/(P)AnsiChar, they are now aliases for UnicodeString/(P)WideChar. A UnicodeString cannot be casted to a PAnsiChar, just like an AnsiString could never be casted to a PWideChar.
In DefDebugOut(), the simplest fix is to change PAnsiChar to PChar and change OutputDebugStringA() to OutputDebugString():
procedure DefDebugOut(const Msg: string);
begin
OutputDebugString(PChar(Msg + #13#10));
end;
This is compatible with all Delphi versions (the code should have been doing this from the beginning - there was no reason to call OutputDebugStringA() directly). PChar and OutputDebugString() map to PAnsiChar and OutputDebugStringA() in Delphi 2007 and earlier, and to PWideChar and OutputDebugStringW() in Delphi 2009 and later. So everything matches.
In TPlugin.SetException(), UTF8Encode() returns a UTF8String in all versions of Delphi. However, prior to Delphi 2009, UTF8String was just an alias for AnsiString itself, but in Delphi 2009 it was changed to a true UTF-8 string type with full RTL support (it still has an AnsiString base, so it can still be casted to PAnsiChar). When a UTF8String is assigned to a UnicodeString, the compiler performs an implicit data conversion from UTF-8 to UTF-16. And as stated above, UnicodeString cannot be casted to PAnsiChar. So you need to change the s variable from String to UTF8String for all Delphi versions:
procedure TPlugin.SetException(const msg: string);
var
s: UTF8String;
begin
try
{$ifopt D+} NPP_DebugOut('Exception: ' + msg); {$endif}
s:=UTF8Encode(msg);
{
UTF8Encode() is deprecated in Delphi 2009+.
In those versions, you can use this instead:
s := UTF8String(msg);
}
NPN_SetException(m_pScriptableObject, PAnsiChar(s));
except
{ prevent any exception from leaking out of DLL }
end;
end;
With that said, if you are still getting the same error on the NPN_SetException() call, then it means the second parameter of NPN_SetException() is declared as PChar. It needs to be declared as PAnsiChar instead.
I need to wrap some legacy code in Delphi 7 for use within Delphi XE2. My question seems simple, but I tried ALL the examples I could find and they all fail. Basically, I need to be able to pass strings between D7 and DXE2, and as far as I can figure out, the safest approach is to use pchar (since I do not want to ship the borlandmm dll).
So DLL written in D7, to be called by Delphi XE2
My interface needs to be
IN My DLL:
function d7zipFile(pFatFile,pThinFile : PChar) : integer; stdCall;
function d7unzipfile(pThinFile,pFatFile : PChar) : integer; stdCall;
I need to pass BACK the pFatFile name in the unzipfile function.
In My calling code:
function d7zipFile(pFatFile,pThinFile : PChar) : integer; external 'd7b64zip.dll';
function d7unzipfile(pThinFile,pFatFile : PChar) : integer; external 'd7b64zip.dll';
Could someone please assist with the best way to implement these?
Obviously I am not looking for the actual zip/unzip code - I have that working fine within D7. I want to know how to declare and work with the string / pchar params, since the various types I tried (PWideChar, WideString, ShortString etc) all give errors.
So I would be happy to simply be able to do a showMessage in the d7zipFile function for both filenames.
And then be able to do a showMessage in delphiXE2 on the pFatFile variable, which means the strings went both ways OK?
By far the easiest way to do this is to use WideString. This is the Delphi wrapper around the COM BSTR type. Dynamic allocation of the string payload is done using the shared COM allocator. Since the Delphi RTL manages that, it is transparent to you.
In the Delphi 7 code you declare your functions like this:
function d7zipFile(const FatFile, ThinFile: WideString): integer; stdcall;
function d7unzipfile(const ThinFile: WideString; var FatFile: WideString):
integer; stdcall;
In your calling code you declare the functions like this:
function d7zipFile(const FatFile, ThinFile: WideString): integer; stdcall;
external 'd7b64zip.dll';
function d7unzipfile(const ThinFile: WideString; var FatFile: WideString):
integer; stdcall; external 'd7b64zip.dll';
The alternative to this approach is to use PAnsiChar or PWideChar. Note that you cannot use PChar because that alias refers to different types depending on which version of Delphi you use. In Delphi 7 PChar is an alias for PAnsiChar, and in XE2 it is an alias for PWideChar.
The big downside of using PAnsiChar, say, is that the caller needs to allocate the string which is returned from the DLL. But typically the caller does not know how large that string needs to be. There are a variety of solutions to the problem but the neatest approach is always to use a shared allocator. You state that you do not want to rely on borlandmm.dll and so the next most obvious common allocator is the COM allocator. And that's why WideString is attractive.
I am writing a C# application that interfaces with a pair of hardware sensors. Unfortunately the only interface that is exposed on the devices requires a provided dll written in Delphi.
I am writing a Delphi executable wrapper that takes calls the necessary functions for the DLL and returns the sensor data over stout. However, the return type of this data is a PWideChar (or PChar) and I have been unable to convert it to ansi for printing on command line.
If I directly pass the data to WriteLn, I get '?' for each character. If I look through the array of characters and attempt to print them one at a time with an Ansi Conversion, only a few of the characters print (they do confirm the data though) and they will often print out of order. (printing with the index exposed simply jumps around.) I also tried converting the PWideChar's to integer straight: 'I' corresponds to 21321. I could potentially figure out all the conversions, but some of the data has a multitude of values.
I am unsure of what version of Delphi the dll uses, but I believe it is 4. Definately prior to 7.
Any help is appreciated!
TLDR: Need to convert UTF-16 PWideChar to AnsiString for printing.
Example application:
program SensorReadout;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Windows,
SysUtils,
dllFuncUnit in 'dllFuncUnit.pas'; //This is my dll interface.
var state: integer;
answer: PChar;
I: integer;
J: integer;
output: string;
ch: char;
begin
try
for I := 0 to 9 do
begin
answer:= GetDeviceChannelInfo_HSI(1, Ord('a'), I, state); //DLL Function, returns a PChar with the results. See example in comments.
if state = HSI_NO_ERRORCODE then
begin
output:= '';
for J := 0 to length(answer) do
begin
ch:= answer[J]; //This copies the char. Originally had an AnsiChar convert here.
output:= output + ch;
end;
WriteLn(output);
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
ReadLn(I);
end.`
The issue was PAnsiChar needed to be the return type of the function sourced from the DLL.
To convert PWideChar to AnsiString:
function WideCharToAnsiString(P: PWideChar): AnsiString;
begin
Result := P;
end;
The code converts from UTF-16, null-terminated PWideChar to AnsiString. If you are getting question marks in the output then either your input is not UTF-16, or it contains characters that cannot be encoded in your ANSI codepage.
My guess is that what is actually happening is that your Delphi DLL was created with a pre-Unicode Delphi and so uses ANSI text. But now you are trying to link to it from a post-Unicode Delphi where PChar has a different meaning. I'm sure Rob explained this to you in your other question. So you can simply fix it by declaring your DLL import to return PAnsiChar rather than PChar. Like this:
function GetDeviceChannelInfo_HSI(PortNumber, Address, ChNumber: Integer;
var State: Integer): PAnsiChar; stdcall; external DLL_FILENAME;
And when you have done this you can assign to a string variable in a similar vein as I describe above.
What you need to absorb is that in older versions of Delphi, PChar is an alias for PAnsiChar. In modern Delphi it is an alias for PWideChar. That mismatch would explain everything that you report.
It does occur to me that writing a Delphi wrapper to the DLL and communicating via stdout with your C# app is a very roundabout approach. I'd just p/invoke the DLL directly from the C# code. You seem to think that this is not possible, but it is quite simple.
[DllImport(#"mydll.dll")]
static extern IntPtr GetDeviceChannelInfo_HSI(
int PortNumber,
int Address,
int ChNumber,
ref int State
);
Call the function like this:
IntPtr ptr = GetDeviceChannelInfo_HSI(Port, Addr, Channel, ref State);
If the function returns a UTF-16 string (which seems doubtful) then you can convert the IntPtr like this:
string str = Marshal.PtrToStringUni(ptr);
Or if it is actually an ANSI string which seems quite likely to me then you do it like this:
string str = Marshal.PtrToStringAnsi(ptr);
And then of course you'll want to call into your DLL to deallocate the string pointer that was returned to you, assuming it was allocated on the heap.
Changed my mind on the comment - I'll make it an answer:)
According to that code if "state" is a code <> HSI_NO_ERRORCODE and there is no exception then it will write the uninitialised string "output" to the console. Which could be anything including accidentally showing "S" and "4" and a series of 1 or more question marks
type of answer(variable) is PChar. use length function good for string variable.
use strlen instead of length.
for J := 0 to StrLen(answer)-1 do
also accessible range of PChar(char *) is 0..n-1
To convert UTF-16 PWideChar to AnsiString you can use simple cast:
var
WStr: WideString;
pWStr: PWideString;
AStr: AnsiString;
begin
WStr := 'test';
pWStr := PWideString(WStr);
AStr := AnsiString(WideString(pWStr));
end;
I have a Delphi 5 application in the application code calls a function in the DLL, passing integer and string parameters, this works well when the DLL is called in a static way, when I try to dynamically change does not work.
which is the correct way to pass parameters to function dynamically?
the code is as follows
main application
function Modulo_Pptos_Operacion(No_Orden : Integer; pathBD : string; PathBDConf : String) : Integer ; stdcall;
external 'LIB_Pptos_Oper.dll';
Modulo_Pptos_Operacion(DmDatos.OrdenesNO_Orden.AsInteger,
DmDatos.CiasPATHA.AsString, 'Alguna String');
DLL
Modulo_Pptos_Operacion function (No_Orden: Integer; PathDB: AnsiString; PathDBConfig: AnsiString): Integer; StdCall;
DYNAMIC CRASH
main application
type
TDLLPpto = function(No_Orden : Integer; PathDB : AnsiString; PathDBConfig : AnsiString) : Integer;
var
DLLHandle: THandle;
: TDLLPpto;
PROCEDURE CALL
DLLHandle := LoadLibrary('LIB_Pptos_Oper.dll');
DLLHandle <> 0 then
begin
#DLLPpto := GetProcAddress(DLLHandle, 'Modulo_Pptos_Operacion');
end;
;
which is the right way?
The problem is probably that you are mixing different runtimes and probably different heaps. Delphi strings are not valid interop types because their implementations vary from version to version.
In this case you can simply switch to using null-terminated strings, PAnsiChar.
In the case of dynamically loaded dll you omitted stdcall; calling convention directive in the declaration of TDLLPpto. Still it is advisable to use PAnsiChar type to pass strings across executable boundaries.
The layout of ansistring has changed with Delphi XE: now there is also a codepage field at negative offset and D5 does not have that. EG: strings from D5 and DXE are utterly incompatible. Thus you should use PAnsiChar or PWideChar in your interface, either zero terminated (Delphi strings are always zero terminated) of introduce an extra parameter with the length if the string might contain #$00 bytes.
Also: the different Delphi versions both have different memory managers. If a string is allocated by the main app and freed by the DLL (strings are reference counted) the pointer get's passed to the wrong memory manager which usually results in corrupted memory and thus nasty Access Violations etc.
Another solution is to use WideString; this is both in D5 en DXE equal to a COM BSTR stringtype and managed by the OS and not the Delphi memory manager. They are compatible. The only problem is: they are slow compared to the Delphi strings and are not ref counted.
In all: when using DLL interfaces, try to avoid string, use PAnsiChar or PWideChar, or WideString
I have to use an existing dll, but got a problem with it.
Here's how I try to use it:
unit u_main;
...
implementation
procedure getUserData(var User, Pass: string); stdcall; external 'Common5.dll';
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
u, p: string;
begin
getUserData(u,p);
end;
...
end.
When I press the button the get the userData, I get an EInvalidPointer exception.
The dll is registerd and in some other projects it's in use and work. any ideas?
EDIT:
The DLL was created in Delphi7 and is now used in a Delphi 2009 project.
Maybe there's a problem with unicode strings or something like that?
You need to rebuild the Delphi 7 DLL, make it follow the WinApi standard of getting PChar and BufferLen parameters. You've got multiple problems with the current implementation:
string is platform-specific, it's implementation may change between delphi versions (and did change). You're not supposed to use string outside the platform!
You're passing the parameters as "var", suggesting the DLL might change the value of user and/or pass. String is an special, managed type, changing it requires allocating memory for the new string. This in turns requires you to share the memory manager between the DLL and the EXE (using sharemem.pas and BorlandMM.dll - or variants). The trouble is, sharing the memory manager between different versions of Delphi is an unsupported configuration! (gotton from embarcadero forums)
The Delphi 7 is hoping to receive an simple AnsiString (1 byte chars), while the Delphi 2009 exe is sending Unicode strings (2 bytes per char).
Along with using PChar, be sure to pre-allocate the space before you call GetUserData. i.e. if you assign 'foo' into a pchar that's empty, you'll blow up. So either use static length PChar/PAnsiChar arrays, or use this technique:
var
s : AnsiString;
begin
setlength(s,256);
MyDLLProc(PAnsiChar(s));
end;