I have to acces a c written dll function, prototyped as:
#include "extcode.h"
#pragma pack(push)
#pragma pack(1)
#ifdef __cplusplus
extern "C" {
#endif
void __stdcall PSA_Send_BO(char hostname[], char BO_NumberIn[],
char BO_NumberOut[], int16_t *Status, char Error_text[], int32_t *Error_code,
int32_t *length_BO_NumberOut, int32_t *length_error_text);
long __cdecl LVDLLStatus(char *errStr, int errStrLen, void *module);
#ifdef __cplusplus
} // extern "C"
#endif
#pragma pack(pop)
My Delphi code:
procedure Aaa(HostNaam: PChar; BO_To: PChar; BO_From: PChar; ErrorText: PChar;
var OutputLength: LongInt; var ErrorLength: LongInt;
var ErrorNumber: Longint
) ; far; stdcall; external 'aaa.dll'
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
BO_From, ErrorText: Array[0..999] of Char;
ErrorLength, BoLength, ErrorNumber: LongInt;
begin
Aaa(PChar(edtHostName.Text), PChar(edtBoNumber.Text), BO_From, ErrorText, BoLength, ErrorLength, ErrorNumber);
Label1.Caption := 'BO_From = ' + BO_From ;
Label2.Caption := 'BoLength = ' + IntToStr(BoLength);
Label3.Caption := 'ErrorText = ' + ErrorText;
Label4.Caption := 'ErrorLength = ' + IntToStr(ErrorLength);
Label5.Caption := 'ErrorNumber = ' + IntToStr(ErrorNumber);
end;
When I run this example, the returned strings BO_From and ErrorText are empty, all other returned parameters are OK.
When I comment one of the lines out where I do the display of the returned parameters, the strings are displayed well!
Stepping into the code with the debugger has similar effect.
Copying all returned parameters before displaying them has no effect.
The length of the returned strings is far below the declared size.
Does someone has any clue?
Thanks in advance for any help,
Cock
You've got a missing var Status: Smallint in the declaration of procedure Aaa.
As Sertac Akyuz mentioned, you have a missing Status parameter, and since stdcall parameters are passed right-to-left (http://docwiki.embarcadero.com/RADStudio/en/Procedures_and_Functions#Calling_Conventions), any parameters declared before this missing parameter will be corrupted.
If you want the code to function on Delphi 2009+ you should also convert PChar => PAnsiChar, and Char => AnsiChar, since SizeOf(Char)=2 on Delphi 2009+.
The "far" directive is also obsolete.
procedure Aaa(HostNaam: PAnsiChar; BO_To: PAnsiChar; BO_From: PAnsiChar;
var Status: SmallInt; ErrorText: PAnsiChar;
var OutputLength: LongInt; var ErrorLength: LongInt;
var ErrorNumber: Longint
) ; stdcall; external 'aaa.dll';
Without seeing the details of the dll, it is hard to say exactly what is going on. One thing, though ...
Do you need to set ErrorLength and BOLength? Usually on calls like this these are filled in with the size of the buffer on the call. That allows the dll to avoid any kind of buffer overrun. So try, setting them to 999 before making the call.
Related
I am trying to call a dll from Delphi XE5.
I have spent a day or so Googling for things like "Call C DLL from Delphi" and found a number of pages but nothing really helped me.
I have received examples of how to call the dll in VB:
Declare Function IxCommand Lib "IxxDLL.dll" (ByVal command As String, ByVal mailbox As String) As Integer
...
Sub Command1_Click ()
Dim command As String * 135
Dim mailbox As String * 135
command = "move:a,1000"
IxCommand( command, mailbox)
End Sub
Also calling the DLL in VC 6.0:
#include "stdafx.h"
#include "windows.h"
#include "stdio.h"
#include "string.h"
typedef UINT (CALLBACK* LPFNIXDLLFUNC)(char *ixstr, char *mbstr);
int main(int argc, char* argv[])
{
HINSTANCE hDLL; // Handle to DLL
LPFNIXDLLFUNC lpfnIxDllFunc; // Function pointer
hDLL = LoadLibrary( "IxxDLL.dll" );
if (hDLL == NULL) // Fails to load Indexer LPT
{
printf("Can't open IxxDLL.dll\n");
exit(1);
}
else // Success opening DLL - get DLL function pointer
{
lpfnIxDllFunc = (LPFNIXDLLFUNC)GetProcAddress(hDLL, "IxCommand");
}
printf( "Type Indexer LPT command and press <Enter> to send\n" );
printf( "Type \"exit\" and press <Enter> to quit\n\n" );
while( 1 )
{
char ix_str[135]; // String to be sent to Indexer LPT
char mailbox_str[135]; // Results from call into Indexer LPT
gets( ix_str ); // Get the string from the console
if( _stricmp( ix_str, "exit" ) == 0 ) // Leave this program if "exit"
break;
lpfnIxDllFunc( ix_str, mailbox_str ); // Otherwise call into Indexer LPT
printf( "%s\n\n", mailbox_str ); // Print the results
}
FreeLibrary( hDLL );
return 0;
}
A complication I have noticed is the need to define the size of the memory allocation before calling the DLL, as shown above.
The dll takes a command in the first argument and returns result text in the second argument.
Here is the Delphi code I have generated to try and call the DLL. I know the dll loads because it has a splash screen that shows. No error is generated when I call the dll. You will see that I used arrays to allocate space and then assigned their locations to Pchar variables. I do not have any header file for the original dll, nor the source code. You will see I declared the external function using stdcall but I have also tried cdecl with no change.
The Problem: The information returned in arg2 is not the expected text string but a string of what translates as non-english characters (looks like chinese).
I am guessing I am not sending the dll the correct variable types.
The Question: Can anyone help me formulate the declaration of the external function and use it correctly, so that I get back the text strings as desired?
See below:
function IxCommand (command : PChar; mailbox : PChar) : Integer; stdcall; external 'IxxDLL.dll';
...
procedure TfrmXYZ.btn1Click(Sender: TObject);
var
LocalResult : Integer;
arg1,
arg2 : PChar;
ArrayStrCmd : array[0..134] of char;
ArrayStrMbx : array[0..134] of char;
begin
ArrayStrCmd := 'Accel?:a' + #0;
ArrayStrMbx := ' ' + #0;
arg1 := #ArrayStrCmd;
arg2 := #ArrayStrMbx;
LocalResult := IxCommand(arg1, arg2);
end;
The problem is character encodings. In Delphi 2009+, PChar is an alias for PWideChar, but the DLL is using Ansi character strings instead, so you need to use PAnsiChar instead of PChar.
Try this:
function IxCommand (command : PAnsiChar; mailbox : PAnsiChar) : Integer; stdcall; external 'IxxDLL.dll';
...
procedure TfrmXYZ.btn1Click(Sender: TObject);
var
LocalResult : Integer;
ArrayStrCmd : array[0..134] of AnsiChar;
ArrayStrMbx : array[0..134] of AnsiChar;
begin
ArrayStrCmd := 'Accel?:a' + #0;
LocalResult := IxCommand(ArrayStrCmd, ArrayStrMbx);
end;
Alternatively:
function IxCommand (command : PAnsiChar; mailbox : PAnsiChar) : Integer; stdcall; external 'IxxDLL.dll';
...
procedure TfrmXYZ.btn1Click(Sender: TObject);
var
LocalResult : Integer;
ArrayStrCmd,
ArrayStrMbx : AnsiString;
begin
SetLength(ArrayStrCmd, 135);
StrPCopy(ArrayStrCmd, 'Accel?:a');
SetLength(ArrayStrMbx, 135);
LocalResult := IxCommand(PAnsiChar(arg1), PAnsiChar(arg2));
end;
You are using a Unicode version of Delphi for which Char is an alias to a 16 bit WideChar, and PChar is an alias to PWideChar.
Simply replace Char with AnsiChar and PChar with PAnsiChar.
function IxCommand(command, mailbox: PAnsiChar): Cardinal;
stdcall; external 'IxxDLL.dll';
The return value is UINT which maps to Cardinal in Delphi.
The calling code you have used is needlessly complex. I'd do it like this:
var
retval: Cardinal;
mailbox: array [0..134] of AnsiChar;
....
retval := IxCommand('Accel?:a', mailbox);
// check retval for errors
As you observe, there is scope for buffer overrun. I'm not sure how you are meant to guard against that. Documentation for the library, if it exists, would presumably explain how.
I am trying to get a c-based DLL working in Delphi. I thought I'd try to get one of the functions to work before I set in on all the others, and I'm stymied. Here's the C call:
int FindCityCounty(char *zip, char cityname[28], char state[2], char countyname[25],
char countynum[3]);
I'm confused by the notion of char cityname[25]. Is that:
array[0..24] of Char/AnsiChar;
?
What's the proper way to translate these constructs? All my attempts have ended in jibberish.
UPDATE:
Here's an example of the function in question being called:
#include <stdio.h>
#include <Windows.h>
#include <srv.h> /* for CALLBACK, HINSTANCE */
#include <direct.h> /* for _getdrive and _getdcwd */
#define cpy_blank(word,len) memset(word,' ',len)
#define cpy_word strncpy
typedef int (CALLBACK* FindCityCounty)(
char ZIP[5],char cityname[28],char state[2],char countyname[25],char countynum[3]);
void runCA(FindCityCounty pCA)
{
char ZIP[5], cityname[28], state[2], countyname[25], countynum[3];
int rc;
printf("CorrectAddress C API Demonstration\n\n");
printf("FindCityCounty() function call\n");
printf("==================================\n");
/* set input parameters */
cpy_word(ZIP,"10601",5);
printf("Input ZIP code: %.5s\n", ZIP);
/* call function */
rc = pCA(ZIP, cityname, state, countyname, countynum);
/*** Now output results ***/
printf("Output city: %.28s\n",cityname);
printf("Output state: %.2s\n",state);
printf("Output county name: %.25s\n",countyname);
printf("Output county number: %.3s\n",countynum);
printf("\n\n");
printf("=====================================\n");
printf("(c) Intelligent Search Technology Ltd\n");
printf("=====================================\n");
}
int main()
{
HINSTANCE hDLL;
FindCityCounty pCA;
/* Load DLL and get function location */
hDLL = LoadLibrary("CorrectA.dll");
pCA = (FindCityCounty)GetProcAddress(hDLL,"FindCityCounty");
runCA(pCA);
getch();
return 0;
}
Delphi Code:
unit CorrectAdressAPI;
interface
type
TZip = array[0..4] of AnsiChar;
TCityName = array[0..27] of AnsiChar;
TState = array[0..1] of AnsiChar;
TCountyName = array[0..24] of AnsiChar;
TCountyNum = array[0..2] of AnsiChar;
PCityName = ^TCityName;
PState = ^TState;
PCountyName = ^TCountyName;
PCountyNum = ^TCountyNum;
type
TFindCityCounty = function(aZip: PAnsiChar;
var aCity: TCityName;
var aState: TState;
var aCounty: TCountyName;
var aCountyNum: TCountyNum): Integer; stdcall;
function Load(const AFileName : string) : Boolean;
function Unload : Boolean;
function FindCityCounty(aZip: PANSIChar;
var aCity: TCityName;
var aState: TState;
var aCounty: TCountyName;
var aCountyNum: TCountyNum): Integer;
implementation
uses
Winapi.Windows, dialogs, SysUtils;
var
hDll : THandle;
PFindCityCounty: TFindCityCounty;
function Load(const AFileName : string) : Boolean;
begin
hDll := LoadLibrary(PChar(AFileName));
Result := hDll <> 0;
if Result then
begin
PFindCityCounty := GetProcAddress(hDLL, PAnsiChar('FindCityCounty'));
Assert(Assigned(PFindCityCounty));
end;
end;
function Unload : Boolean;
begin
Result := (hDll <> 0) and FreeLibrary(hDll);
end;
function FindCityCounty(aZip: PANSIChar;
var aCity: TCityName;
var aState: TState;
var aCounty: TCountyName;
var aCountyNum: TCountyNum): Integer;
begin
Result := PFindCityCounty(aZip, aCity, aState, aCounty, aCountyNum);
end;
end.
As Mason says, arrays passed as parameters in 'C' are always passed as a pointer to the declared array type. However, a char array of fixed size is not certain to be a null terminated array, so using a PChar, PWideChar or PANSIChar would be potentially wrong.
To answer your specific question regarding the dimensions of the arrays, you appear to have mixed up your city and county names in your example, but essentially you are correct. C arrays are 0-based so the C declaration:
char name[N]
is equivalent to the Pascal:
name: array[0..N-1] of Char;
But getting the correct number of elements in the array is not the end of the story. You also need to be careful to ensure your array element type is declared correctly.
In the case of the char type you need to be careful that 'Char' is the correct type for the corresponding C environment when converting any C code. In Delphi 1-2007 'Char' is a single byte ANSIChar. In Delphi 2009 onwards 'Char' is a 2-byte WideChar.
In most (if not all) C implementations, certainly for Windows, char is an 8-bit/1-byte value, so an ANSIChar is most likely to be the appropriate Pascal type and ensures a 1-byte char on any Delphi version.
Returning to your specific example, the fact that explicitly dimensioned arrays are involved, rather than explicitly (or implicitly) null terminated strings, I think it may be advisable in this particular case to declare types for each of the required array types, with corresponding pointer types:
type
TCityName = array[0..27] of ANSIChar;
TState = array[0..1] of ANSIChar;
TCountyName = array[0..24] of ANSIChar;
TCountyNum = array[0..2] of ANSIChar;
PCityName = ^TCityName;
PState = ^TState;
PCountyName = ^TCountyName;
PCountyNum = ^TCountyNum;
You then have two choices in your Pascal version of the C function declaration. You could explicitly use the implicit pointer types that the C code produces 'under the hood':
function FindCityCounty(aZip: PANSIChar;
aCity: PCityName;
aState: PState;
aCounty: PCountyName;
aCountyNum: PCountyNum): Integer; cdecl;
Or you could use the array types themselves, but declare them as var parameters to co-erce the Pascal compiler to pass them by reference, thus producing implied pointers, as per the C code:
function FindCityCounty(aZip: PANSIChar;
var aCity: TCityName;
var aState: TState;
var aCounty: TCountyName;
var aCountyNum: TCountyNum): Integer; cdecl;
The effect is essentially the same in both cases, though personally I would favour the explicit pointers since the use of var parameters overtly suggests that the parameter values may/will be modified by the function call which is almost certainly not the case.
Note that the zip parameter is already being passed explicitly as a pointer to a char in the C code, so using the PANSIChar pointer type here is appropriate for this parameter in either case.
If this was a struct, you would be correct. But in C, arrays as function parameters are always pointers. According to this tutorial, this is still the case even in an array parameter with an explicit size given. So this should be translated as PChar or PAnsiChar, as appropriate.
I am new to Delphi. I have a DLL with the following exported function in it:
bool __stdcall MyFunction(char * name, int * index)
This code which calls this DLL function in C++ works perfectly:
typedef void (WINAPI * MyFunction_t)(char *, int *);
void main()
{
HMODULE mydll = LoadLibrary(L"C:\\mydll.dll");
MyFunction_t MyFunction = (MyFunction_t)GetProcAddress(mydll, "MyFunction");
int index = 0;
MyFunction("MyString", &index);
}
I need to do the same in Delphi. Here is my code, which is not working (MyFunction gets called but the index variable doesn't receive the appropriate value). This is a code excerpt so please ignore disorder. Any input would be much appreciated!
type
TMyFunction= function(name: PChar; var index_ptr: Integer): Boolean; stdcall;
var
fMyFunction : TMyFunction;
i : Integer;
h: THandle;
begin
Result := 0;
h := LoadLibrary('c:\\mydll.dll');
fMyFunction := GetProcAddress(h, 'MyFunction');
if #fMyFunction <> nil then
begin
fMyFunction('MyString', i);
Result := i;
end;
FreeLibrary(h);
end;
First of all I am assuming that you are using C linkage with extern "C" in case this function is defined in a C++ translation unit.
If you are using Delphi 2009 or later, you need to be aware that PChar is a pointer to a null-terminated wide character string.
To interop with your ANSI C function you need to use:
type
TMyFunction= function(name: PAnsiChar; var index: Integer): Boolean; stdcall;
The C bool type is probably best mapped to LongBool since it's not quite the same as a Delphi Boolean:
type
TMyFunction= function(name: PAnsiChar; var index: Integer): LongBool; stdcall;
You don't need to escape \ in strings so you can write:
h := LoadLibrary('c:\mydll.dll');
You probably ought to check for errors on the call to LoadLibrary and, technically, h is an HMODULE rather than a THandle, although that won't cause you any problems.
Idiomatic Delphi would be to write:
if Assigned(fMyFunction) then
fMyFunction('MyString', Result);
Basically it looks reasonable to me but I'm most suspicious of the character width.
Hope that helps.
Try not using STDCALL in your TMyFunction type.
I have an external function like this:
extern "C" __declspec(dllexport) int __cdecl Identify(BSTR* bstrTemplates, __int64 lCount, __int64* lIndex, __int64* lRetCode)
The bstrTemplates should be a string array.
How should my function look like in D7, and how to pass a string array to the external function. Can't get my head around right now.
A BSTR is a WideString in Delphi, and a pointer to a BSTR is also a pointer to a WideString in Delphi, but in terms of C-code, it is most likely an array reference. A typical way to handle such arrays, and I'm going to assume this is how it's done here, is to use a null-terminated array.
So, we need to declare an array of WideString's in Delphi, and leave the last element as null, or nil in Delphi:
var
templates : array of WideString;
begin
SetLength(templates, 3); // 2 template names + 1 nil
templates[0] := 'template1';
templates[1] := 'template2';
templates[2] := nil;
Identify(#templates[0], ....); // pass it as a pointer to the first element
I'm not guaranteeing this will work. I'm guessing here, and haven't tried it (which would involve creating a C project and testing) so this might fail horribly. Worth a shot though.
Finally solved the problem. It was the dynamic array. Looks like it can't be used as C-style array. Looks like the length prefix confused the c dll. For the records here the prototype and usage:
Type
type
TArrayOfWideString= array[0..999] of WideString;
Declaration
function Identify(var ATemplates: TArrayOfWideString; ATemplatesCount: int64; var ATemplateIndex: int64; var ARetCode: int64): Integer; cdecl; external 'Identify.dll';
Usage
var
templateIndex, retCode: int64;
templates: TArrayOfWideString;
retval: integer;
//TODO: range checking for TArrayOfWideString needed
templates[0] := 'template1';
templates[1] := 'template2';
retVal := Identify(templates, 2, scanIndex, retCode);
BSTR* is a pointer to a BSTR (in Delphi BSTR is a WideString).
EDIT: To make the answer complete (and make Rob Kennedy happy :-) ):
Most literal translation:
function Identify(bstrTemplates: PWideString; lCount: int64; lIndex: PInt64; lRetCode: PInt64): Integer; cdecl external 'mydll.dll';
or more the Delphi way:
function Identify(bstrTemplates: PWideString; lCount: int64; var lIndex: Int64; var lRetCode: Int64): Integer; cdecl external 'mydll.dll';
or even (but this depends if the bstrTemplates can be nil):
function Identify(var bstrTemplates: WideString; lCount: int64; var lIndex: Int64; var lRetCode: Int64): Integer; cdecl external 'mydll.dll';
Use the first element in the array when you pass bstrTemplates (eg #MyArray[0])
I need call a DLL file in my delphi code, here is the code snippet of the DLL Head file:
#define BookInfoDLL __declspec(dllexport)
struct _BookTime
{
unsigned char day;
unsigned char month;
unsigned short year;
};
struct _stBookData
{
unsigned char encrypt;
_BookTime bkTime;
unsigned int PageCount;
};
int BookInfoDLL UpdateBooks(const char * const pBookID,
const char cBookTypeWord,
const _stBookData * const pBookData,
const int nBookDataCounter);
I need invoke the dll function "UpdateBooks" in my delphi code.
How can I convert those code into delphi? Thank you!
Use h2pas! Although it is a freepascal tool, it should produce Delphi compatible code.
Snippet for non-managed Delphi code (not tested, but compiles and changed according suggestions in comments):
interface
type
TBookTime = packed record
day : byte; // unsigned 8-bit
month : byte;
year : word; // unsigned 16-bit
end;
TBookData = packed record
encrypt : byte;
bkTime : TBookTime;
PageCount : LongWord; // unsigned 32-bit
end;
TBookDataPtr = ^TBookData;
function UpdateBooks(
pBookID : PChar;
cBookTypeWord : byte;
pBookData : TBookDataPtr;
nBookDataCounter : integer
) : integer; stdcall; external 'dll_file_name.dll' name 'UpdateBooks';
implementation
// ...
end;
Simple call UpdateBooks(...) from delphi code.
Update: code changed, thanks for commenting!
Below is snippets for sample calls ...
Common functions and constants for all snippets:
// --- Test data fill utility and constants -----------------------------------
const
BOOK_ID = 'Test Book ID';
BOOK_TYPE_WORD = 'T';
BOOK_DATA_COUNT = 5;
procedure FillTestBookData(pBookData : TBookDataPtr; iTestNum : integer);
begin
if(pBookData = nil) then exit;
pBookData^.encrypt := iTestNum;
pBookData^.bkTime.day := iTestNum;
pBookData^.bkTime.month := iTestNum;
pBookData^.bkTime.year := 2000 + iTestNum;
pBookData^.PageCount := iTestNum;
end;
Calling function in common Delphi style:
// --- Test procedure in Delphi style -----------------------------------------
procedure TestBookUpdate_DelphiStyle;
var
bookArray : array of TBookData;
iBookNumber : integer;
begin
SetLength(bookArray, BOOK_DATA_COUNT);
try
for iBookNumber := Low(bookArray) to High(bookArray) do begin
FillTestBookData( #(bookArray[iBookNumber]), iBookNumber );
end;
UpdateBooks(
PChar(BOOK_ID), ord(BOOK_TYPE_WORD),
#(bookArray[Low(bookArray)]), BOOK_DATA_COUNT
);
finally
SetLength(bookArray, 0); // no explicit requirement to include in code
end;
end;
Bonus: same test calls in C-style and Pascal-style :-)
// --- Test procedure in Old Delphi (plain Pascal) style ----------------------
type
TBookDataOldArray = array[0..0] of TBookData;
TBookDataOldArrayPtr = ^TBookDataOldArray;
// Store range checking compiler option state
{$IFOPT R+}
{$DEFINE RANGE_CHECK_ON}
{$ENDIF}
procedure TestBookUpdate_OldDelphiStyle;
var
bookArrayPtr : TBookDataOldArrayPtr;
iBookNumber : integer;
begin
GetMem(bookArrayPtr, BOOK_DATA_COUNT*sizeof(TBookData));
try
// Disable range checking compiler option
{$R-}
for iBookNumber := 0 to BOOK_DATA_COUNT - 1 do begin
FillTestBookData(#(bookArrayPtr^[iBookNumber]), iBookNumber);
end;
// Restore range checking compiler option if turned on before disabling
{$IFDEF RANGE_CHECK_ON}{$R+}{$ENDIF}
UpdateBooks(
PChar(BOOK_ID), ord(BOOK_TYPE_WORD), TBookDataPtr(bookArrayPtr), BOOK_DATA_COUNT
);
finally
FreeMem(bookArrayPtr);
end;
end;
// --- Test procedure in C style ---------------------------------------------
procedure TestBookUpdate_CStyle;
var
bookArrayPtr : TBookDataPtr;
curBookPtr : TBookDataPtr;
curBookNumber : integer;
begin
bookArrayPtr := AllocMem( BOOK_DATA_COUNT * sizeof(TBookData) );
try
curBookNumber := 0;
curBookPtr := bookArrayPtr;
while(curBookNumber < BOOK_DATA_COUNT) do begin
FillTestBookData( curBookPtr, curBookNumber );
inc(curBookNumber);
inc(curBookPtr, 1);
// Another pointer increment solution is :
// curBookPtr := PChar(curBookPtr) + sizeof(TBookData);
end;
UpdateBooks( PChar(BOOK_ID), ord(BOOK_TYPE_WORD), bookArrayPtr, BOOK_DATA_COUNT );
finally
FreeMem(bookArrayPtr);
end;
end;
I have ended my first C header conversion yesterday. The articles and tools from TeamB member Rudy Velthuis were very, very helpful for me, specially
Pitfalls of converting
Conversion Helper Package