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.
Related
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 have a compiled a library and I am trying to access the functions from c++ code. Most functions work properly, however I have some trouble with passing parameters to a function that accepts an array as argument.
The pascal function header is defined as:
function MyFunc( const Name : PAnsichar;
const MyArr : array of single;
const ArrLength : Longint;
var output : single
): Longint;
I can compile this function and use is properly when using pascal to load the function and call the functions in the library. Note: the library is compiled using the CDecl calling convention.
However I have trouble with loading the functions in C++.
The function is defined as:
typedef long (*MyFunc)(char *, float, long, float *);
I am able to load the dll properly and acces all the function properly, all but the one above.
long ArrLeng = 300;
float out;
float Arr[ArrLeng];
\\ fill the array
result = MyFunc((char *) "default", Arr[0], ArrLeng, &out);
I can attach the debugger to the library and check the variables read by the library. The strange thing is that the Name and ArrLeng variables are passed on properly, but the array is not passed properly.
What am I doing wrong? How should I pass the array to the library?
Try passing a pointer to the first element. "Array of Single" is a so called open array which is a pascal construct that also passes array boundary information.
However when used in combination with cdecl afaik it reduces to a pointer to elementtype. (single *) At least Free Pascal does, I don't know what Delphi/Kylix does.
In doubt let pascal call it and check the resulting assembler.
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;
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]
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).