Convert variable arguments in C stdcall interface method to Delphi - delphi

I am converting the ICallFrame interface to Delphi and I am not sure how to handle the ICallFrame::Invoke method.
This is the definition:
HRESULT Invoke(
void *pvReceiver,
...
);
Per documentation the varargs directive works only with external routines and only with the cdecl calling convention.
It's a bit strange as Invoke is declared with STDMETHODCALLTYPE thus __stdcall, which is a callee-clean convention. But a variadic function cannot be callee-clean since the callee does not know how many parameters were passed.
Is there some kind of compiler magic if you do this with Visual Studio?
Even if compiler magic uses cdecl, the Delphi compiler doesn't accept this (because it's not external) E2070 Unknown directive: 'varargs'
ICallFrame = interface(IUnknown)
['{D573B4B0-894E-11d2-B8B6-00C04FB9618A}']
// other methods left out
function Invoke(pvReceiver: PVOID): HRESULT; cdecl; varargs;
end;
Based on Rudy's answer it seems this could work:
type
TfnInvoke = function(this: Pointer; pvReceiver: Pointer): HRESULT; cdecl varargs;
implementation
function GetInterfaceMethod(const intf; methodIndex: dword) : pointer;
begin
Result := Pointer(pointer(dword_ptr(pointer(intf)^) + methodIndex * SizeOf(Pointer))^);
end;
...
var
Invoker: TfnInvoke;
...
// Don't forget IUnknown methods when counting!
Invoker := GetInterfaceMethod(myIntf, 21);
Invoker(myIntf, FObject, 11, 17.3);
I will test with this and report back...
EDIT: tested and works.

Stdcall cannot use variable arguments. Any C or C++ function taking variable arguments must be __cdecl, because only in that calling convention, the caller (code), which knows how many parameters were passed, cleans up the stack. Even if the default calling convention is stdcall, var args functions will be cdecl.
Delphi can not generate functions like that, but it can consume existing external C functions (in a DLL or .obj file) using the varargs directive.
So a C (or C++) function like
HRESULT Invoke(void *pvReceiver, ...);
should be converted as:
function Invoke(pvReceiver: Pointer); cdecl; varargs;
Note that you leave out the ... part in the Delphi declaration. It is assumed when you use the varargs directive.
And that allows you to use it in Delphi like in C or C++:
Invoke(someReceiver, 17, 1.456);
Another example: for instance the well known C function printf():
int printf(const char *format, ...);
is declared as
function printf(format: PAnsiChar {args}): Integer; cdecl; varargs;
in System.Win.Crtl.pas.
Update
This seems to be a method of an interface.
Not sure if that works, but I would try:
type
TInvoke = function(Intf: IInterface; pvReceiver: Pointer): HRESULT; cdecl varargs;
// No semicolon between cdecl and varargs.
and use it as:
MyResult := TInvoke(#myIntf.Invoke)(myIntf, someReceiver, 11, 17.3);
This method may have been declared as __stdcall (through the STDMETHODCALLTYPE macro), but even then, if it is variadic, the (VC++) compiler will generate __cdecl, as Remy Lebeau found in Raymond Chen's blog.

Related

Calling C DLL from Delphi program

In my Delphi code I have to call a DLL's function (written in Visual C) with the following prototype:
int PWFunc(LPCSTR szName, int nWidth, int nHeight, LPCSTR szFileName)
How can I convert Delphi AnsiString variables (for Name and FileName) into right type parameters (LPCSTR szName and szFileName) of function call ?
I know that VC LPCSTR type corresponds to Delphi PAnsiChar type, but what is the right procedure to convert AnsiString to PAnsiChar ?
LPCSTR and LPSTR correspond to PAnsiChar, so that is what you use:
function PWFunc(szName: PAnsiChar; nWidth, nHeight: Longint;
szFileName: PAnsiChar): Longint; cdecl { or stdcall, see documentation };
external 'somedll.dll' name 'PWFunc';
You call it like:
X := PWFunc(PAnsiChar(AnsiString(SomeName)), 17, 33,
PAnsiChar(AnsiString(SomeFileName)));
Whether your function is stdcall or dcecl depends on compiler settings. Read the documentation. If in doubt, try both. It looks like cdecl to me, so start with that.

Access violation at address 003B0E8E in module 'MpLib.dll'. Read of address 00120BF4

I've searched around Stackoverflow for the issue I was having, but they were all very specific for that particular access violation.
The piece of code I believe I am having trouble with is a function called MpqExtractFile, stored in a DLL.
Function:
function MpqExtractFile(var hMPQ: Integer; szToExtract, szExtracted: AnsiString): Boolean; stdcall; external 'MpqLib.dll' name 'B2';
Call:
if MpqExtractFile(hMPQ, 'war3map.j', AnsiString(tempDir+'\war3map.j')) = True
then ShowMessage('Success.')
else ShowMessage('Failed.');
Upon execution of this code my application throws the access violation error so the ShowMessage is not displayed at all.
I am unsure if the above is adequate to even estimate what the problem could be, but if there is anything else I should please tell me.
Edit
This is an extract of the VB.NET source code that I have been interpreting:
Declare Function MpqExtractFile Lib "MpqLib.dll" Alias "B2" (ByVal hMPQ As Integer, ByVal szToExtract As String, ByVal szExtracted As String) As Boolean
I am obviously not familiar with declarations in other languages, but I have found the below function in the same VB file where the VB functions were declared.
BOOL WINAPI MpqExtractFile(HANDLE hMpq, const char * szToExtract, const char * szExtracted);
Thank you in advanced!
AnsiString is the completely wrong type to use, unless the DLL was writting in Delphi/C++Builder and actually used AnsiString in its parameters. If that were the case, you would need to know which version of Delphi/C++Builder the DLL was written in, because the memory layout of AnsiString was changed in 2009.
In any case, it is very dangerous to pass non-POD data across DLL boundaries, so most DLLs do not do that. The parameters in question are most likely PAnsiChar instead, eg:
function MpqExtractFile(var hMPQ: Integer; szToExtract, szExtracted: PAnsiChar): Boolean; stdcall; external 'MpqLib.dll' name 'B2';
.
if MpqExtractFile(hMPQ, 'war3map.j', PAnsiChar(AnsiString(tempDir+'\war3map.j'))) then
ShowMessage('Success.')
else
ShowMessage('Failed.');
Other points to consider:
1) not all DLLs use the stdcall calling convention. It is not uncommon for DLLs written in C, like many open-source libraries are, to use the cdecl calling convention instead:
function MpqExtractFile(var hMPQ: Integer; szToExtract, szExtracted: PAnsiChar): Boolean; cdecl; external 'MpqLib.dll' name 'B2';
2) C does not have a true Boolean data type like Delphi and C++ do. It is not uncommon for C code to use Byte or even Integer to mimic a Boolean.
In order to use a DLL in Delphi, you really need to know the actual proper declaration of its exported functions. This is less of an issue in C/C++ because most DLLs have an accompanied .h file that provides the declarations. Do you have such a .h file? If so, post it here so someone can verify your translation to Delphi.
Update:
Based on new information, the correct Delphi declaration is this:
function MpqExtractFile(hMpq: THandle; const szToExtract, szExtracted: PAnsiChar): BOOL; stdcall; external 'MpqLib.dll' name 'B2';
Your VB.net declaration is:
Declare Function MpqExtractFile Lib "MpqLib.dll" Alias "B2" (
ByVal hMPQ As Integer, ByVal szToExtract As String,
ByVal szExtracted As String) As Boolean
The equivalent Delphi import would be:
function MpqExtractFile(MpqExtractFile: Integer;
szToExtract, szExtracted: PAnsiChar): BOOL;
stdcall; external 'MpqLib.dll' name 'B2';
Delphi string types should not be used for interop. The p/invoke marshaller maps String to C++ char* which is PAnsiChar in Delphi.
This sort of task really should be carried out with the C++ header file. You say you have not got that. If the DLL is written in C++ then the header file surely exists. It would pay to track it down and work from that as your source.

Why can a WideString not be used as a function return value for interop?

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);

Boolean parameter in a dll function in delphi 7

I have a dll library. I have excluded memory unit for delphi types.
In that way, what would be the appropriate Boolean type for function declaration?
Is it BOOL or something else?
The problem is that in the method signature:
function Test(Param1: BOOL; Param2: BOOL; docContent: PCharArray): Integer;
I get AV when program leaves that function.
I assume that it is the problem with the data type of these two first parameters.
BOOL is fine for Boolean types. It's a Windows type, so it's what you'll see in all the functions in Windows.pas.
Access violations upon return from a DLL function often indicate that you have the calling convention wrong — the default calling convention is register, but you probably need stdcall or cdecl. Add it at the end of the declaration:
function Test(Param1: BOOL; Param2: BOOL; docContent: PCharArray): Integer; stdcall;

Call variadic C function by its memory address in Delphi

Suppose I have a function in C++ in which I call it using a pointer to its memory address, with typedef. Now, how can I do the same thing in Delphi?
For example:
typedef void (*function_t)(char *format, ...);
function_t Function;
Function = (function_t)0x00477123;
And then, I can call it with: Function("string", etc);.
Is there any way to do this without using Assembly instructions, in Delphi?
Note that it is a variadic parameters function.
An idiomatic translation for this:
typedef void (*function_t)(char *format, ...);
function_t Function;
Function = (function_t)0x00477123;
Is this:
type
TFunction = procedure(Format: PAnsiChar) cdecl varargs;
var
Function: TFunction;
// ...
Function := TFunction($00477123);
The 'cdecl varargs' is required to get the C calling convention (where the caller pops the stack) and the variadic argument support (which is only supported with the C calling convention). Varargs is only supported as a means for calling C; there is no built-in support in Delphi for implementing variadic parameter lists in the C style. Instead, there is a different mechanism, used by the Format procedure and friends:
function Format(const Fmt: string; const Args: array of const): string;
But you can find out more about that elsewhere.
program Project1;
type
TFoo = procedure(S: String);
var
F: TFoo;
begin
F := TFoo($00477123);
F('string');
end.
Of course, if you just run the above you'll get a runtime error 216 at address $00477123.
Yes, Delphi supports function pointers. Declare it like this:
type MyProcType = procedure(value: string);
Then declare a variable of type MyProcType and assign the address of your procedure to it, and you can call it the same way you would in C.
If you want a pointer to a method of an object instead of a standalone procedure or function, add "of object" to the end of the function pointer declaration.

Resources