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.
Related
I’ve got these 4 dll function probably created in c++ of which I have examples of calling these functions both in c ++ and in visual basic.
I have to use these functions in delphi (Delphi 7 and Delphi 10.4). VbOpen, VbClose, and VbWrite work just fine but I can't get VbRead to work.
I have already declared these functions as follows:
function VbOpen(_1: Integer;_2: LongInt;_3: BYTE;_4: BYTE;_5: BYTE;_6: BYTE;var _7: LongInt): Integer; stdcall;
function VbClose(var com:LongInt):Integer ; stdcall;
function VbWrite(var Command:AnsiString; var _3: LongInt): Integer; stdcall ;
function VbRead(var pBufOut : PAnsiChar ; var nBufferSize : LongInt;var _3: LongInt): integer; stdcall;
//
Call to VbRead
r:=VbOpen(5,...,...,...,...,,.);
if r=0 then
begin
//
IpCommand := '1009';
r:=VbWrite(IpCommand,errorCode);
// Visual Basic
GetMem(pBufOut,100);
r:=VbRead(pBufOut, pByteRead, errorCode);
if r=0 then
begin
if pByteRead > 0 then
begin
SetString(resultString, pBufOut, pdwByteRead);
end;
end;
end;
VbClose(errorCode); // Closes the connection and frees the used memory
The result of VbRead resultString are garbage characters but pByteRead bytes returned like a correct value.
Example in visual basic where I have the working exe file
Dim vReturn As Long
Dim vCodeErr As Long
Dim vRetByte As Long
Dim s As String
Dim strOut As String
Dim pBufOut(1000) As Byte
Dim i As Long
s = "1001" // comand code
vReturn = VbWrite(s, vCodeEr)
vReturn = VbRead(pBufOut(),vRetByte, vCodeEr)
' Close Com Port
vReturn = VbClose(vCodeEr)
i = 1
strOut = ""
While (i <= vRetByte)
strOut = strOut + String(1, pBufOut(i))
i = i + 1
Wend
From the user manual of the dll
Sintax DWORD VbRead(SAFERRAY** pBufOut, LPDWORD pByteRead, LPDWORD lpdwCodeError)
pBufOut address of the bytes read
pByteRead number of bytes read
lpdwCodeError system errors returned
Where is the error hiding?
Thanks for any help
Vincent
You probably have to use VarArrayCreate to allocate space.
var
Arr: Variant;
begin
Arr := VarArrayCreate([1000], varByte);
the VB example allocates 1000 Bytes. But you allocate with GetMem only 100 Bytes.
Also you might remove the "var" statements in methods vbRead and vbWrite.
I have a dll written in C++. It looks like this:
header file:
#define DllExport extern "C" __declspec( dllexport )
#include "SpeedTreeRT_1_8.h"
DllExport CSpeedTreeRT* NewSpeedTree(void);
DllExport void DeleteTree(CSpeedTreeRT* handle);
DllExport bool LoadTree(CSpeedTreeRT* handle, const unsigned char* pBlock, unsigned int nNumBytes);
cpp file:
CSpeedTreeRT* NewSpeedTree(void) {
return new CSpeedTreeRT();
}
void DeleteTree(CSpeedTreeRT* handle) {
delete handle;
}
bool LoadTree(CSpeedTreeRT* handle, const unsigned char* pBlock, unsigned int nNumBytes) {
return handle->LoadTree(pBlock, nNumBytes);
}
Delphi type definitions:
TNewSpeedTreeFunc = function (): Cardinal; cdecl;
TLoadTreeFunc = function (AHandle: Cardinal; const ABlock: String; ANumBytes: Cardinal): Boolean; cdecl;
TDeleteTreeFunc = procedure (AHandle: Integer); cdecl;
I then use LoadLibrary to load dll into delphi application.
DllHandle: Cardinal;
NewSPeedTreeFunc : TNewSpeedTreeFunc;
LoadTreeFunc: TLoadTreeFunc;
DeleteTreeFunc: TDeleteTreeFunc;
DllHandle := LoadLibrary('SpeedTreeFT.dll');
#NewSpeedTreeFunc := GetProcAddress(DllHandle, 'NewSpeedTree');
#LoadTreeFunc := GetProcAddress(DllHandle, 'LoadTree');
#DeleteTreeFunc := GetProcAddress(DllHandle, 'DeleteTree');
SpeedTreeHandle := NewSpeedTreeFunc;
... call other functions here ...
DeleteTreeFunc(SpeedTreeHandle);
FreeLibrary(DLLHandle);
Dll exports other functions besides LoadTree, I just removed them for clarity.
What happens is if I run it once, everything is ok, I can call other functions in dll and I get the expected results. When I run it the second time, I get Access Violation exception on calling NewSpeedTree. I also noticed that a call to DeleteTree doesn't release memory from application.
Am I doing it the right way in dll? What could be causing this problem?
EDIT1: Provided more information in code blocks.
The big problem seems to be your declaration of pBlock as a string!
The declaration:
bool LoadTree(CSpeedTreeRT* handle, const unsigned char* pBlock, unsigned int nNumBytes);
should be translated as:
type
TLoadTreeFunc = function(Handle: THandle; const pBlock: PByte;
nNumBytes: Cardinal): LongBool cdecl;
unsigned char is a Byte, and a pointer to it is a PByte. To use it, just pass a pointer to the first byte of a block you reserved (e.g. a TBytes that has been set to the appropriate length, using SetLength).
Something like:
var
Block: TBytes;
begin
SetLength(Block, the_required_length);
if LoadTree(SpeedTreeHandle, #Block[0], Length(Block)) then
// etc...
Passing the type string to a (non-Delphi) DLL
string is a Delphi type, and in Delphi 2009 or later, it is even a UnicodeString. But even if it is pre-2009, where string is an AnsiString, it is wrong to pass it. Both AnsiString and UnicodeString are Delphi-specific and can't be used as parameter types for a DLL written in C++. So never declare a char * or unsigned char * as string, but always as PAnsiChar or PByte respectively.
More info in these articles of mine: "DLL dos and don'ts" and "Pitfalls of converting".
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 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.