I'm trying to call a C++ DLL function that is defined like this:
int read_record (filep *fptr, int key, char *contents, int *status)
This is a widely used DLL, so I'm pretty certain the problem I'm having is how I'm calling the function.
The DLL docs have this example of how to call it
TFILETYPE *fptr; /* file pointer
char contents[80];
int status = 0;
int single = key;
if (read_record(fptr, key, card, &status)) break;
printf("%s\n", card);
Here's what I think should work, and almost does:
type
TCharArray = Array[1..100] of AnsiChar; // Function returns an array less than 100 char
var
read_record : function( var fptr: TFILETYPE;
Key: Integer;
var Contents: TCharArray; // function fills this in
var Status: Integer): Integer cdecl stdcall;
Procedure Test;
var
Contents: TCharArray;
Status: Integer;
Key: Integer;
begin
#read_record:= GetProcAddress(DLLHandle, 'read_record');
for Key := 1 to 10 do
begin
Contents[1] := #0; // shouldn't be necessary
if (read_record( fptr^, Key, Contents, Status) <> 0) OR (Status <> 0) then
ShowMessage('Error')
else
ShowMessage(Contents); // This shows the expected proper string on all 10 calls
...Other calls at this point to other functions in the DLL result in
an Exception writing to x01a.
end;
Multiple calls from Delphi XE work fine. But after that, when I call different function in the DLL that has always worked in the past, I get an exception writing to x0000001a, which I suspect means I've trashed memory or the stack.
The *fptr pointer datatype I'm using in calls to other functions in the dll, so I don't think that's the problem.
This is the first time I've tried to call a function that returns a string, so I suspect I'm not understanding something with call by reference of string arrays.
Any suggestions on how I should call this function differently to avoid what appears to be trashing of memory?
You have a couple of problems here.
First, the function declaration in Delphi is wrong. First, it's declared as both cdecl and stdcallat the same time. It needs to be one or the other; it can't be both simultaneously.
Also, you have an extra level of dereferencing on the fptr variable.
The declaration indicates it's a pointer:
filep *fptr
You've said in your Delphi declaration that it's a pointer:
var fptr: TFileType
But you're passing a pointer to a pointer:
fptr^
Change your call to the DLL function to
if (read_record( fptr, Key, Contents, Status) <> 0) OR (Status <> 0)
(I think your test of the results should actually be <> 0) AND (Status <> 0), but without the docs I'm not sure.)
If you do in fact need to initialize Contents before passing it to the DLL, you should use FillChar(Contents, SizeOf(Contents), #0) (or ZeroMemory) to do so, BTW.
As an additional suggestion, you can simplify your code somewhat (I've chosen stdcall as the calling convention):
var
read_record : function( var fptr: TFILETYPE;
Key: Integer;
Contents: PAnsiChar; // function fills this in
var Status: Integer): Integer stdcall;
var
Contents: AnsiString;
...
begin
SetLength(Contents, 100);
if (read_record(fptr, Key, PAnsiChar(Contents), Status)...
....
end;
Related
I have, on more than one occasion, advised people to use a return value of type WideString for interop purposes.
Accessing Delphi DLL throwing ocasional exception
ASP.NET web app calling Delphi DLL on IIS webserver, locks up when returning PChar string
Why can Delphi DLLs use WideString without using ShareMem?
The idea is that a WideString is the same as a BSTR. Because a BSTR is allocated on the shared COM heap then it is no problem to allocate in one module and deallocate in a different module. This is because all parties have agreed to use the same heap, the COM heap.
However, it seems that WideString cannot be used as a function return value for interop.
Consider the following Delphi DLL.
library WideStringTest;
uses
ActiveX;
function TestWideString: WideString; stdcall;
begin
Result := 'TestWideString';
end;
function TestBSTR: TBstr; stdcall;
begin
Result := SysAllocString('TestBSTR');
end;
procedure TestWideStringOutParam(out str: WideString); stdcall;
begin
str := 'TestWideStringOutParam';
end;
exports
TestWideString, TestBSTR, TestWideStringOutParam;
begin
end.
and the following C++ code:
typedef BSTR (__stdcall *Func)();
typedef void (__stdcall *OutParam)(BSTR &pstr);
HMODULE lib = LoadLibrary(DLLNAME);
Func TestWideString = (Func) GetProcAddress(lib, "TestWideString");
Func TestBSTR = (Func) GetProcAddress(lib, "TestBSTR");
OutParam TestWideStringOutParam = (OutParam) GetProcAddress(lib,
"TestWideStringOutParam");
BSTR str = TestBSTR();
wprintf(L"%s\n", str);
SysFreeString(str);
str = NULL;
TestWideStringOutParam(str);
wprintf(L"%s\n", str);
SysFreeString(str);
str = NULL;
str = TestWideString();//fails here
wprintf(L"%s\n", str);
SysFreeString(str);
The call to TestWideString fails with this error:
Unhandled exception at 0x772015de in BSTRtest.exe: 0xC0000005: Access violation reading location 0x00000000.
Similarly, if we try to call this from C# with p/invoke, we have a failure:
[DllImport(#"path\to\my\dll")]
[return: MarshalAs(UnmanagedType.BStr)]
static extern string TestWideString();
The error is:
An unhandled exception of type 'System.Runtime.InteropServices.SEHException' occurred in ConsoleApplication10.exe
Additional information: External component has thrown an exception.
Calling TestWideString via p/invoke works as expected.
So, use pass-by-reference with WideString parameters and mapping them onto BSTR appears to work perfectly well. But not for function return values. I have tested this on Delphi 5, 2010 and XE2 and observe the same behaviour on all versions.
Execution enters the Delphi and fails almost immediately. The assignment to Result turns into a call to System._WStrAsg, the first line of which reads:
CMP [EAX],EDX
Now, EAX is $00000000 and naturally there is an access violation.
Can anyone explain this? Am I doing something wrong? Am I unreasonable in expecting WideString function values to be viable BSTRs? Or is it just a Delphi defect?
In regular Delphi functions, the function return is actually a parameter passed by reference, even though syntactically it looks and feels like an 'out' parameter. You can test this out like so (this may be version dependent):
function DoNothing: IInterface;
begin
if Assigned(Result) then
ShowMessage('result assigned before invocation')
else
ShowMessage('result NOT assigned before invocation');
end;
procedure TestParameterPassingMechanismOfFunctions;
var
X: IInterface;
begin
X := TInterfaceObject.Create;
X := DoNothing;
end;
To demonstrate call TestParameterPassingMechanismOfFunctions()
Your code is failing because of a mismatch between Delphi and C++'s understanding of the calling convention in relation to the passing mechanism for function results. In C++ a function return acts like the syntax suggests: an out parameter. But for Delphi it is a var parameter.
To fix, try this:
function TestWideString: WideString; stdcall;
begin
Pointer(Result) := nil;
Result := 'TestWideString';
end;
In C#/C++ you will need to define the Result as out Parameter, in order to maintain binary code compatibility of stdcall calling conventions:
Returning Strings and Interface References From DLL Functions
In the stdcall calling convention, the function’s result is passed via the CPU’s EAX register. However, Visual C++ and Delphi generate different binary code for these routines.
Delphi code stays the same:
function TestWideString: WideString; stdcall;
begin
Result := 'TestWideString';
end;
C# code:
// declaration
[DllImport(#"Test.dll")]
static extern void TestWideString([MarshalAs(UnmanagedType.BStr)] out string Result);
...
string s;
TestWideString(out s);
MessageBox.Show(s);
Hi I'm having quite some issues with integrating a DLL inside my Delphi 2007 application.
i suspect that I'm doing something wrong with the parameters of the calls.
At this moment i have 2 issues, but i think they are related to eachother.
1)
First call with the DLL:
from the .h file:
extern "C" {
__declspec(dllexport) HRESULT Startup(char* version);
}
This call should initialize the DLL and give me the version back of the DLL. HRESULT should be 0, and the version pointer should contain the version.
My Delphi code:
function Startup(var version: Pchar): HRESULT; cdecl; stdcall; external 'myDLL.dll';
And the actual call:
var
res : HRESULT;
Name1 : PChar;
test : AnsiString;
buf2: array [0..20] of AnsiChar;
begin
FillChar(buf2,20,0);
Name1:= #buf2[0];
res := RdmStartup(Name1);
//Here res = 0, but the Name1 stays empty, and the buf2 still contains 0.
end;
But as the result is 0 the call was a success.
Then my second issue: i need to call a function in the DLL that will open a COM port.
The .h:
extern "C" {
__declspec(dllexport) HRESULT Open(HWND hWnd, int Port, DWORD BaudRate, DWORD Interval);
}
And my Delphi declare:
function Open(hWnd: HWND;Port : integer;BaudRate:LongInt;Interval:LongInt): HRESULT; cdecl; stdcall; external 'myDLL.dll';
and i call this by:
res:= Open(self.Handle,5,115200,500);
And here i'm getting a failure back from the DLL in the res variable.
i also have the source of the DLL, and the failure that i'm getting is from the part where the DLL is checking if the parameters are valid, if they are valid it will continue, else return the error i'm currently getting.
The things it is checking:
if(hWnd == NULL)
{
return false;
}
if(BaudRate != 2400 && BaudRate != 9600 && BaudRate != 38400 && BaudRate != 115200)
{
return false;
}
if(IntervalTimer < 300)
{
return false;
}
std::string strPortName = lexical_cast<std::string>( format("COM%d") % Port);
std::string strPortName(lpPortName.c_str());
std::string::size_type loci = strPortName.find("COM");
if( loci == std::string::npos )
{
return false;
}
return true;
And one of these above is returning false on my call, because if the result of this function is false, the DLL gives the error i'm currently getting in the results.
Does anyone have an idea of what i am doing wrong?
i've tried numerous of combinations for the types in the end i sticked to the conversion i found at: http://www.drbob42.com/delphi/headconv.htm
i've also tried different ways of reading the char pointer, but all of them failed.....
So at this stage i know i am succesfully communicating with the DLL as i'm getting different HRESULTs back for the 2 calls, but i suspect my parameters are not working like the should.
I'm using Delphi 2007 and the C++ DLL was build with VS2010.
The declaration of Startup is pretty suspicious:
__declspec(dllexport) HRESULT Startup(char* version);
This translates into:
function Startup(version: PAnsiChar): HResult; stdcall; external 'myDLL.dll';
So there should be no var there.
I got from your comments that the cdecl calling convention works for some of your code. In that case remove stdcall, since it overrules the preceding cdecl.
The declaration of Open() seems to be pretty OK (I would use DWORD as type, not Longint, especially since DWORD is Longword these days -- but in Win32 they are the same size, so that won't make any big difference for you). And you seem to be passing the right parameters too.
You did not write what the HRESULT value is that you get back. But I assume that the port COM5 simply cannot be opened with these settings.
What can you do?
You should remove the var from Startup().
So you can try:
to use cdecl instead of stdcall (the stdcall in your declaration overrules the cdecl)
to open different COM ports with different parameters
to decode the HRESULT that is returned.
A better diagnosis is not possible, from a distance, without the same hardware and software, sorry.
You could read my article on conversion. This has also a few paragraphs that explain how to debug the code to find out the proper calling convention. It can probably help you with more of your problems converting headers, too.
i have a problem to use delphi class from C++ code. delphi dll demo that export a function that return an object.
my delphi Dll code is as follow:
library DelphiTest;
// uses part....
type
IMyObject = interface
procedure DoThis( n: Integer );
function DoThat : PWideChar;
end;
TMyObject = class(TInterfacedObject,IMyObject)
procedure DoThis( n: Integer );
function DoThat: PChar;
end;
// TMyObject implementation go here ...
procedure TMyObject.DoThis( n: Integer );
begin
showmessage('you are calling the DoThis methode with '+intToStr(n) +'parameter');
end;
function TMyObject.DoThat: PChar;
begin
showmessage('you are calling the DoThat function');
Result := Pchar('Hello im Dothat');
end;
// exporting DLL function :
function CreateMyObject : IMyObject; stdcall;export;
var
txt : TextFile;
begin
AssignFile(txt,'C:\log.log');
Reset(txt);
Writeln(txt,'hello');
Result := TMyObject.Create;
end;
exports CreateMyObject;
in my C++ project i declared the IMyObject interface as follow :
class IMyObject
{
public:
IMyObject();
virtual ~IMyObject();
virtual void DoThis(int n) = 0;
virtual char* DoThat() = 0;
};
and my main function as follow :
typedef IMyObject* (__stdcall *CreateFn)();
int main()
{
HMODULE hLib;
hLib = LoadLibrary(L"DelphiTest.dll");
assert(hLib != NULL); // pass !!
CreateFn pfnCreate;
pfnCreate = (CreateFn)GetProcAddress((HINSTANCE)hLib, "CreateMyObject");
if (pfnCreate == NULL)
{
DWORD errc = GetLastError();
printf("%u\n", errc); // it gets error 127
}
else{
printf("success load\n");
}
IMyObject* objptr = pfnCreate();
objptr->DoThis(5);
FreeLibrary(hLib);
int in;
scanf_s("%i", &in);
return 0;
}
with this example i got an error at run time when i try to access the exported function. the errors is at line :
IMyObject* objptr = pfnCreate();
can you tell me what is wrong about my example.
and if possible any working example to access Delphi class (in DLL) from C++ code.
The first problem is calling convention of the methods. The Delphi interface uses register which is a Delphi specific calling convention. Use stdcall, for example, for the methods of the interface.
The next problem is in the C++. Your C++ interface must derive from IUnknown. Further, it should not declare a constructor or destructor.
Beyond that your Delphi code exports PWideChar which does not map to char*. It maps to wchar_t*.
Looking further ahead, returning a PChar works fine here because your implementation returns a literal. But more serious code will want to use a dynamically allocated string presumably and at that point your design is flawed.
Do note that you need to be an elevate administrator to create a file at the root of the system drive. So that's yet another potential failure point.
I expect there are other mistakes, but that's all I've found so far.
I'm not able to use the function of a dll developed in delphi. I'm having some difficulties with the conversions of types.
This is the function I want to call the DLL:
function rData(ID: Cardinal; queue: WideString): WideString; stdcall;
My code in C++ was so:
typedef string (*ReturnDataSPL)(DWORD, string);
string result;
HMODULE hLib;
hLib = LoadLibrary("delphi.dll");
pReturnDataSPL = (ReturnDataSPL)GetProcAddress(hLib,"rData");
if (NULL != pReturnDataSPL)
result = pReturnDataSPL(JobID,printerName);
The problem I'm not able to make it work. I do not know which type is compatible with Delphi WideString and Cardinal.
Someone help me
EDIT:
This is the function I want to call the DLL:
procedure rData(ID: Cardinal; queue: WideString; var Result: WideString); stdcall;
After changing the code looked like this:
typedef void (__stdcall *ReturnDataSPL)(DWORD, BSTR, BSTR&);
HMODULE hLib;
BSTR result = NULL;
hLib = LoadLibrary("delphi.dll");
pReturnDataSPL = (ReturnDataSPL)GetProcAddress(hLib,"rData");
if (NULL != pReturnDataSPL)
{
pReturnDataSPL(JobID,(BSTR)"Lexmark X656de (MS) (Copiar 2)",result);
}
You've got very little chance of calling that function.
For a start your current code can't hope to succeed since I presume string is std::string. That's a C++ data type which Delphi code cannot either provide or consume. To match up against Delphi's WideString you need to use the COM BSTR data type.
Another problem with your code as it stands is that it uses cdecl in the C++ side, and stdcall on the Delphi side. You'll need to align the calling conventions.
However, that will also fail because of a difference between Delphi's ABI for return values, and the platform standard. That topic was covered in detail here: Why can a WideString not be used as a function return value for interop?
Your best bet is to stop using WideString as a return value and convert it into a C++ reference parameter. You'll want to convert the Delphi to match.
You are looking at something like this:
Delphi
procedure rData(ID: Cardinal; queue: WideString; var Result: WideString); stdcall;
C++
typedef void (__stdcall *ReturnDataSPL)(DWORD, BSTR, BSTR&);
This is procedure which is defined in C++ application (exe HOST).
typedef struct _ RX_DATA_OBJ {
UINT32 TIME;
UINT32 ID;
UINT8 LEN;
UINT8 DATA[8];
} RX_DATA_OBJ;
static void RX_DATA(UINT16 CNT, RX_DATA_OBJ *p_RX_DTATA_OBJ)
I need to call as callback above function in my DLL file written in Delphi.
I made the following declaration in Delphi for my DLL:
type
TRX_DATA_OBJ = record
time: UINT32;
id: UINT32;
len: UINT8;
data: array [0..7] of UINT8;
end;
PRX_DATA_OBJ = ^TRX_DATA_OBJ;
RX_DATA = procedure(count:UINT16; RX_DATA_OBJ: PRX_DATA_OBJ ) of object;
var
RX_DATA_out: TRX_DATA_OBJ;
In another procedure I get the pointer to the RX_DATA procedure in HOST.
Now I call this callback in my DLL
procedure PUT_DATA;
begin
RX_DATA_out.Time := 100;
RX_DATA_out.id := $500;
RX_DATA_out.len := 4;
RX_DATA_out.data[0] := 1;
RX_DATA_out.data[1] := 2;
RX_DATA_out.data[2] := 3
RX_DATA_out.data[3] := 4;
RX_DATA_out.data[4] := 5;
RX_DATA_out.data[5] := 6;
RX_DATA_out.data[6] := 7;
RX_DATA_out.data[7] := 8;
RX_DATA(1, #RX_DATA_out); // RX_DATA_out is global variable defined.
end;
Callback is called correct but data passed in RX_DATA_out variable are not correct, it looks that pointer to RX_DATA_out variable is not pointing on this variable.
Can anybody say what can be wrong ?
The C++ function type is a plain function, i.e. not a member function. But you have declared your version to be a method of object. That is a mismatch. You must remove of object.
You must also specify the calling convention. Most likely it will be cdecl. In Delphi, if the calling convention is not specified, then the register based Delphi fastcall convention, register is used. That's certainly wrong.
Finally, if the pointer to the struct can never be nil then it is more idiomatic to use a var parameter:
RX_DATA = procedure(count:UINT16; var RX_DATA_OBJ: TRX_DATA_OBJ); cdecl;
And obviously you'd have to change the calling code to match:
RX_DATA(1, RX_DATA_out);