I have a dll RL6_dll.dll from a routing program RouteLogix that is used to plan trucks etc.
Now we want to use that from Delphi 2007.
We have a c++ header for the dll and a working example that use it in C++-Builder.
Here is an example from that file:
// Use this routine to find the directory where the data-xxx subdirectories
// are expected.
// char * vszBuf - address of a character array to receive the (null-terminated) path.
// int nBufSize - is the size of the array
// (internally we allow paths up to 256 characters long)
DllFn(void) RL6_GetLocalGeoDir(char *vszBuf, int nBufSize);
My try from Delphi:
procedure TfrmRL6Xml.Button1Click(Sender: TObject);
var
s1: PChar;
IntValue : Integer;
RL6_GetLocalGeoDir: function(vszBuf: pchar; nBufSize: Integer): integer; stdcall;
begin
handle := LoadLibrary('C:\Carp\RL6_app2\rl6dll\RL6_DLL.dll');
if handle <> 0 then
begin
#DllFn := GetProcAddress(handle, 'RL6_PREINIT');
#RL6_GetLocalGeoDir := GetProcAddress(handle, 'RL6_GETLOCALGEODIR');
s1 := ' ';
IntValue := length (s1);
RL6_GetLocalGeoDir (s1, IntValue);
showMessage(s1);
end;
end;
So now I expect s1 contains a string, but instead the functions seems handle IntValue as string. It seems like the s1 and IntValue parameters are exchanged. We have of course tried RL6_GetLocalGeoDir (IntValue, s1) but that didn't work either. Any suggestions how to call it ?
The quest title mentions the Pascal calling convention, but the question body never comes back to that topic. Does the documentation for the DLL say it uses the Pascal calling convention? It's a very rare calling convention to use nowadays. (It was used in the Windows API in the 16-bit days, and although some headers from those times still say PASCAL today, that macro has been redefined to refer to the stdcall calling convention instead.)
You haven't shown the definition of DllFn — neither in the C code nor the Delphi code. In C, I imagine it's a macro that includes the function's calling convention, so go find that definition to confirm what's really being used. In Delphi, it looks like you're using it as a function pointer. I encourage you to use the same function names in your application as the DLL uses. It makes life easier for everyone involved — no more does anyone look at code and wonder what function is really being called.
If you confirm that the DLL really uses the Pascal calling convention, then specifying it in Delphi is as simple as changing the "stdcall" directive on your function declaration to "pascal":
RL6_GetLocalGeoDir: procedure(vszBuf: PAnsiChar; nBufSize: Integer); pascal;
I've also changed the PChar argument to use PAnsiChar because now that some versions of Delphi are Unicode, the PChar type might mean PWideChar, and you don't want that here.
You need to call the procedure with a preallocated buffer, and with the correct declaration, like so:
procedure TfrmRL6Xml.Button1Click(Sender: TObject);
var
s: AnsiString;
IntValue : Integer;
RL6_GetLocalGeoDir: procedure(vszBuf: PAnsiChar; nBufSize: Integer); stdcall;
begin
handle := LoadLibrary('C:\Carp\RL6_app2\rl6dll\RL6_DLL.dll');
if handle <> 0 then
begin
#DllFn := GetProcAddress(handle, 'RL6_PREINIT');
#RL6_GetLocalGeoDir := GetProcAddress(handle, 'RL6_GETLOCALGEODIR');
IntValue := 256;
SetLength(s, IntValue);
RL6_GetLocalGeoDir (PAnsiChar(s), IntValue);
s := PAnsiChar(s);
showMessage(s);
end;
end;
Edit:
Your modified question still contains bad code. You use
var
s1: PChar;
s1 := ' ';
IntValue := length (s1);
This is just wrong, as you don't provide a buffer but a pointer to a string constant in the code segment. Using this will lead to crashes, just try with for example the API function GetWindowsDirectory():
var
P: PAnsiChar;
begin
P := ' ';
GetWindowsDirectory(P, 80);
end;
Running this will result in an access violation in ntddll.dll, write of an address in the code area (for example $0044C6C0).
Related
I have a C++ function :
long CSP2_API csp2GetPacket(char szData[], long nBcrNr, long nMaxDataSz)
parameters are :
szData: buffer where the barcode data is stored in
nBcrNr: the desired barcode’s index (which starts from 0L)
nMaxDataSz: the maximum size of the szData buffer
My way of dealing with this is :
type
TBarcode = Array[0..99] of AnsiChar;
function csp2GetPacket(szBarData : TBarCode;
nBarcodeNumber: LongInt;
nMaxLength: LongInt): LongInt cdecl {$IFDEF WIN32}stdcall {$ENDIF}; external 'CSP2.DLL';
and called using...
procedure TForm1.Button1Click(Sender: TObject);
var
BarCode : TBarcode;
begin
BarCode := #0;
csp2GetPacket(BarCode,0,SizeOf(BarCode));
end;
The C++ documentation confirms it is using standard ASCII text for the barcodes.
Is there a better (more correct way) of calling the C++ function? or is this acceptable?
If you know the barcode data will never exceed 100 bytes, then your approach is fine, though your function declaration should be more like this instead:
type
TBarcode = Array[0..99] of AnsiChar;
function csp2GetPacket(szBarData: TBarCode; nBarcodeNumber: LongInt; nMaxLength: LongInt): LongInt; cdecl; external 'CSP2.DLL';
Or, if the function really does use stdcall on Windows instead of cdecl:
function csp2GetPacket(szBarData: TBarCode; nBarcodeNumber: LongInt; nMaxLength: LongInt): LongInt; stdcall; external 'CSP2.DLL';
Either way, a more correct translation of the function would be to declare the szBarData parameter as PAnsiChar instead:
function csp2GetPacket(szBarData: PAnsiChar; ...) ...
You can still pass a fixed array variable to a PAnsiChar parameter.
This:
long CSP2_API csp2GetPacket(char szData[], long nBcrNr, long nMaxDataSz)
is translated like this:
function csp2GetPacket(szData: PAnsiChar; nBcrNr, nMaxDataSz: Longint): Longint; <calling-convention>;
Your translation is not correct. In C, one can degrade an array to a pointer, but in Delphi, you would really pass the entire array per value, so the translation is not binary compatible with the original.
I assume you have the calling convention right. If not, simply remove the stdcall.
I would simply use an AnsiString as parameter, cast to a PAnsiChar, more or less like:
var
BarCode: AnsiString;
L: Longint;
begin
SetLength(L, 100);
L := csp2GetPacket(PAnsiChar(BarCode), 0, Length(BarCode));
// If csp2GetPacket returns the length, then:
SetLength(BarCode, L);
// Otherwise, this will work too:
BarCode := PAnsiChar(BarCode); // Copies up to and including terminating #0
I don't know the limits for the length of the buffer, so I assumed the same length as your code.
More info in my article on the subject of conversions: Pitfalls of converting.
I've written a c++ function in a DLL which exports a string to a VBA program:
BSTR _stdcall myFunc()
{
CRegKey Key;
CString sValue;
BSTR Str;
LONG nA = Key.Open(HKEY_LOCAL_MACHINE, _T("[group path goes here]"), KEY_READ);
ULONG nValueLength = 0;
LONG nB = Key.QueryStringValue(_T("[key I want to read goes here]"), NULL, &nValueLength);
if (nValueLength > 0)
{
LONG nC = Key.QueryStringValue(_T("[key I want to read goes here]"), sValue.GetBufferSetLength(nValueLength - 1), &nValueLength);
}
Str = _bstr_t(sValue.AllocSysString(), false);
return Str;
Now Str is something like a version number: let's say "4.10.122".
If I call the function from VBA, I receive instead "4 . 1 0 . 1 2 2", where the "spaces" between each characters are NULL (in VBA they are Chr(0)).
I don't like the idea of having to use the Replace function in my VBA code, so is there any way I can include that step in my c++ code?
EDIT: below the code I'm using to call the function in VBA:
Private Declare Function myFunc Lib "[Path of my DLL here]" () As String
Sub Return_string()
Dim a As String
a = myFunc()
End Sub
Far from being able to help you with this specific need but curious about the subject, if i were to come up with a solution i would start from this answer https://stackoverflow.com/a/43423527/781933
Update
According to the above mentioned #SimonMourier detailed answer, and further readings, you should consider string content encodings.
The context of the DLL - VBA marshalling is to be considered and depends also on VBA function declaration (stated by #HansPassant).
From this MSDN documentation:
When a VBA user-defined function is declared as taking a String argument, Excel converts the supplied string to a byte-string in a locale-specific way.
If you want your function to be passed a Unicode string, your VBA user-defined function should accept a Variant instead of a String argument.
So if you use:
Private Declare Function myFunc Lib "[Path of my DLL here]" () As String
conversion from UNICODE to ANSI is needed via StrConv
Dim str As String
str = StrConv(myFunc(), vbFromUnicode)
Otherwise you should get rid of StrConv but using DLL exported BSTR with this declaration:
Private Declare Function myFunc Lib "[Path of my DLL here]" () As Variant
The problem stems from the Declare statement. When interacting with such functions and strings in particular, VBA will always perform an implicit ANSI to Unicode conversion in both directions. In your case, VBA expects an ANSI string as the return value, which it can then expand into a Unicode equivalent.
To deal with this, you'll have to return an ANSI string and resort to SysAllocStringByteLen:
CStringA sValueA(sValue);
Str = SysAllocStringByteLen(sValueA.GetBuffer(), sValueA.GetLength());
As an alternative, you may also embed a type library inside your DLL. This would omit the automatic string conversion.
I am struggling to implement a function from a C dll. It is declared as
int DetectTransactionCode(wchar_t* wi_type, wchar_t* wi_id);
If have declared and called this in my delphi code as
function DetectTransactionCode (var wi_type, wi_id: PWideChar): Integer;
cdecl; external 'WiGroupDetect.dll';
procedure TForm20.Button2Click(Sender: TObject);
var witype,wi_id : widestring;
res : integer;
begin
res := DetectTransactionCode(PWideChar(witype),PWideChar(wi_id));
showmessage(res.tostring);
ShowMessage(witype +' & ' +wi_id);
end;
I am receiving the result however my witype and wi_id cause access violations.
I have also tried :
Function DetectTransactionCode (var witype,wi_id :widestring ) : Integer cdecl;
external 'WiGroupDetect.dll';
procedure TForm20.Button2Click(Sender: TObject);
var witype,wi_id : widestring;
res : integer;
begin
res := DetectTransactionCode(witype,wi_id);
showmessage(res.tostring);
ShowMessage(witype +' & ' +wi_id);
end;
I assume both parameters are out parameters. The 3rd party supplied the following :
Returns 1 for success, 0 for cancellation/failure
Note: Blocking call only returns when:
1 - A wiCode is detected,
2 - The KillDetectionProcess()is called,
3 – Some error or failure occurs, or
4 - Ultimately the timeout (30 minutes) expires.
Parameters:
wi_type Returns the type of token retrieved (i.e. “WIQR” for QR) on success; or “NONE” on cancellation/failure.
wi_id Returns the wiCode detected on success; “CANCELLED” on cancellation; or with additional error information on failure (i.e. “ERROR: ...”).
I have tried changing the parameters to ansistring, unicodestring but still get the same problem. I suspect it has something to with the var parameter but not sure how to overcome it. Any assistance would be appreciated.
I have been given a C sample implementation by them
[DllImport("WiGroupDetect.dll", EntryPoint = "DetectTransactionCode", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)]
private static extern int DetectTransactionCode([MarshalAsAttribute(UnmanagedType.LPWStr)] StringBuilder strWITYPE, [MarshalAsAttribute(UnmanagedType.LPWStr)] StringBuilder strUID);
Not sure if this changes my Delphi implementation
The two parameters are of type wchar_t* which is a pointer to 16 bit character. Normally that would mean a pointer to null-terminated array of wide UTF-16 characters.
So, the correct translation of the code as presented is:
function DetectTransactionCode (wi_type, wi_id: PWideChar): Integer;
cdecl; external 'WiGroupDetect.dll';
You used var parameters of type WideString. That would match parameters that are pointers to BSTR which is quite different.
I'm assuming the cdecl calling convention, the default for every C compiler that I have encountered. If you have some extra information that says the function is stdcall, so be it. But as written in the question this is cdecl.
It's not clear how the data flows. Are the two string parameters in or out? Is one in and the other out? If either are out parameters then you need some way to know how big a buffer to allocate. The fact that the function does not let you pass the buffer length suggests that the data flows in. That said, in that case the parameters should have been const wchar_t* in the C code. There's a lot of uncertainty here. Do be aware that the function prototype does not completely define the semantics of a function.
Thanks David for your answers they helped. I managed to come right using this code. I am aware that if the string passed back is longer than I have provided for then there will be an error
function DetectTransactionCode (wi_type, wi_id: pwidechar): Integer;
stdcall; external 'WiGroupDetect.dll';
procedure TForm20.Button2Click(Sender: TObject);
var witype,wi_id : widestring;
res : integer;
begin
setlength(witype,10);
setlength(wi_id,100);
res := DetectTransactionCode(pwidechar(witype),pwidechar(wi_id));
showmessage(res.tostring);
setlength(witype,pos(#0,witype)-1);
setlength(wi_id,pos(#0,wi_id)-1);
ShowMessage(trim(witype) +' & ' +trim(wi_id));
end;
I have a C++ program, that calls Delphi DLL to initialize a buffer that contains chars.
I am testing out the interface to make sure the data are passed correctly:
In C++ program:
char * pMsg = (char*)malloc(3); //allocate buffer
Init(pMsg * char); //Delphi DLL function
In Delphi DLL:
procedure Init(pMsg:PChar);
var
pHardcodedMsg:PChar;
begin
pHardcodedMsg:= '123';
CopyMemory(pMsg, pHardcodedMsg, Length(pHardcodedMsg));
end;
But, when I try to printf( (const char*)pMsg ) in C++,
It shows me "123" followed by some rubbish characters.
Why is this so?
How can I successfully place an array of char into the buffer and have the string printed out correctly?
Delphi does not use NULL-terminated strings so you need to slap a 0 at the end, as C/C++ uses that to determine where the string data ends (Pascal uses the size of the string at the beginning IIRC).
The usual character is '\0' - escape value 0.
Don't forget to return 4 characters, not 3.
Your Init function doesn't work because
1) pHardcodedMsg is a pointer for which you didn't allocate memory
2) CopyMemory doesn't add a 0 to the end of pMsg
3) the procedure header of Init misses a semi colon at the end of the line
When you are using a unicode version of Delphi you will also have to consider string length and character set conversion
There's a function, written in C++ and compiled as DLL, which I want to use in my Delphi application.
Scraper.cpp:
SCRAPER_API bool ScraperGetWinList(SWin winList[100])
{
iCurrWin=0;
memset(winList,0,100 * sizeof(SWin));
return EnumWindows(EnumProcTopLevelWindowList, (LPARAM) winList);
}
Scraper.h:
#ifdef SCRAPER_EXPORTS
#define SCRAPER_API __declspec(dllexport)
#else
#define SCRAPER_API __declspec(dllimport)
#endif
struct SWin
{
char title[512];
HWND hwnd;
};
extern "C" {
SCRAPER_API bool ScraperGetWinList(SWin winList[100]);
}
This is how I declare the function in the Delphi application:
type
tWin = record
Title: Array [0..511] of Char;
hWnd: HWND;
end;
tWinList = Array [0..99] of tWin;
function ScraperGetWinList(var WinList: tWinList): Boolean; stdcall; external 'Scraper.dll';
The function works, but when it's finished, I receive Debugger Fault Notification: Project ... faulted with message: ''access violation at 0x0012f773: write of address 0xffffffc0'. Process Stopped. Use Step or Run to continue.
If I add __stdcall (after SCRAPER_API bool) in Scraper.cpp and Scraper.h, then the Delphi application doesn't start at all: The procedure entry point ScraperGetWinList could not be located in the dynamic link library Scraper.dll.
You need to put __stdcall after bool. The complete declaration, after all macros expand, should look like this:
extern "C"
{
__declspec(dllexport)
bool __stdcall ScraperGetWinList(SWin winList[100]);
}
EDIT: Looks like you'll also need a .def file there. It's a file that lists every function exported in the DLL, and in this case it's needed only to force C++ compiler not mangle the exported names. Contents would be this:
EXPORTS
ScraperGetWinList
I'm not sure which C++ compiler you're using, but normally you'd just specify the .def file along with .cpp; for example, the following works for VC++:
cl.exe foo.cpp foo.def
Also, you will need to tell Delphi to use stdcall as well, by inserting stdcall keyword right before external in your Delphi function declaration.
If you use a packed array[1..512] of char you will not need the ConvertToString() function.
"packed array of char" is assignment compatible with Delphi string (this goes back to very early forms of Pascal - packed array of char WAS the string type). You nmay need to scab the result for a null ($0) char to find the end of the C-string
Also what Delphi version are you using? if Delphi 2009 + you will need to use packed array[1..512] of AnsiChar ;
It would be good to know where exactly your access violation occurs. What variable/memory location is your runtime trying to access?
Then find out if this location should actually be accessible, and if so, why it's not.
My suspicion: you access an array element that isn't initialized correctly.
Index := 0;
S := ConvertToString(myWinList[Index].Title);
while S <> '' do
begin
WinListMemo.Lines.Add(S);
Inc(Index);
//////// Is Index pointing to a valid entry here? No check!
S := ConvertToString(myWinList[Index].Title);
end;
Either
the dll does not initialize it correctly,
or there is another way to find out the last element.
or you simply ran off the array altogether: the 101th element is also dereferenced. and the 102nd, if that memory location happens to contain a 0 character.
Check that the definition of your Delphi function matches what you are declaring the C++ function as too. In particular, make sure that you have stdcall at the end, and that your bool values are going to be consistent. C++ and Delphi use different values and sizes for bool, depending on the C++ compiler, so it may be better to use an appropriately sized Integer. As the size of the bool may not match the C++ size, this can affect the stack, and thus cause access violations.
[edited to remove mixed language duff response]