I have a 3rd party Delphi DLL which I am calling from C++. Unfortunately, I have no access to the Pascal DLL code, and am not a Pascal programmer.
There is no lib file, so I'm using GetProcAddress to call many DLL functions, successfully passing parameters by value, address and reference. I also register a callback function which gets called when expected.
My problem is that in the callback function, one of the two parameters cannot be evaluated (address 0x000001).
Here are the Pascal DLL function declarations
type
HANDLE = Pointer; /// handle
(** This function Registers the callback function OnACLNeeded
*)
function RegisterCallback(
h: HANDLE;
OnACLNeeded: MyCallbackFunc;
UserData: DWORD): Integer; stdcall;
This is the pascal version of the calling application, the callback function. Both parameters are passed by reference (var).
function TSNAPICongigF.OnACLNeeded(var keySettings, numOfKeys: Byte): Integer; stdcall;
begin
keySettings:=$0F;
numOfKeys:=1;
Result:=0;
end;
This is my C++ version of the callback function
int __stdcall OnACLNeeded(byte& keySettings, byte& numOfKeys)
{
keySettings = 0x0F;
numOfKeys = 1;
return 1;
}
This is my C++ calling code
int _tmain()
{
HMODULE hLib = LoadLibrary(PASCAL_DLL);
// DLL function pointer
typedef int (__stdcall *FnRegisterCallback)(HANDLE hKeyProvider,
int (__stdcall *)(byte&, byte&),
DWORD);
FnRegisterCallback pfnRegisterCallback =
(FnRegisterCallback)GetProcAddress(hLib, "RegisterCallback");
// register my callback function
int ret = (*pfnRegisteraCllback)(h, OnACLNeeded, (DWORD)1);
}
When running in the debugger, I reach the breakpoint on the first line of the callback function keySettings = 0x0F;
I find that numOfKeys
is valid, but keySettings
has an address of 0x00000001 and cannot be assigned to.
The application will crash with an AccessViolation if I continue.
int __stdcall OnACLNeeded(byte& keySettings, byte& numOfKeys)
{
keySettings = 0x0F;
I've tried declaring as __cdecl to no effect.
I've tried declaring the byte params as byte*, and I get the same invalid parameter.
I'm using Visual Studio 2010 running on Win7 64-bit, but compiling as Win32.
These are my compile and link commands
/ZI /nologo /W3 /WX- /Od /Oy- /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /Gm /EHsc /RTC1 /GS /fp:precise /Zc:wchar_t /Zc:forScope /Fp"Debug\CallPascal.pch" /Fa"Debug\" /Fo"Debug\" /Fd"Debug\vc100.pdb" /Gd /analyze- /errorReport:queue
/OUT:"...\Debug\CallPascal.exe" /INCREMENTAL /NOLOGO "kernel32.lib" "user32.lib" "gdi32.lib" "winspool.lib" "comdlg32.lib" "advapi32.lib" "shell32.lib" "ole32.lib" "oleaut32.lib" "uuid.lib" "odbc32.lib" "odbccp32.lib" /MANIFEST /ManifestFile:"Debug\CallPascal.exe.intermediate.manifest" /ALLOWISOLATION /MANIFESTUAC:"level='asInvoker' uiAccess='false'" /DEBUG /PDB:"...\Debug\CallPascal.pdb" /SUBSYSTEM:CONSOLE /PGD:"...y\Debug\CallPascal.pgd" /TLBID:1 /DYNAMICBASE /NXCOMPAT /MACHINE:X86 /ERRORREPORT:QUEUE
Any suggestions very gratefully received. Thank you.
------ edit -----
I added the struct
struct Bodge {
void* code;
void* instance;
};
Bodge bodge;
bodge.code = OnACLNeeded;
bodge.instance = (void*)0x99; // just to test
My callback becomes
Integer __stdcall OnACLNeeded(void* instance, Byte& keySettings, Byte& numOfKeys);
And my function call to register the callback becomes
typedef Integer (__stdcall *FnRegisterCallback)(
HANDLE,
Bodge,
DWORD);
and is called like this
ret = (*pfnRegisterCallback)(
h,
bodge,
(DWORD)1);
This call produces an error
The value of ESP was not properly saved across a function call. This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.
which may also indicate a corrupt stack, I think.
BUT if I ignore the error and continue, I get into the callback function body, and BOTH parameters are now valid!
So success of a kind, but also, the void* instance
param has a value of zero, not the 0x99 that I set.
I feel we are getting there though!
----- edit -----
This is the function call to register the callback, from the original Pascal calling code.
* @param hKeyProvider the key provider handle for Desfire card created previously with LASSeOKeyProvider_CreateHandle
* @param OnACLNeeded supplies a callback to be called for quering host application for the PICC master key settings
* @param UserData unsigned integer values specifiing any custom provided data to be returned when the callback is called
* @return 0 - on success; <>0 - denotes error code
RegisterCallback(hKeyProv,
@TSNAPICongigF.OnACLNeeded,
(Self));
Note the "self" reference. What would be the C++ equivalent? (I'm not using classes)
The problem is that the Delphi version of the callback is an instance method. Your C++ callback it not. This is a significant mismatch. This Delphi interface is badly designed and not callable from C++ without some trickery.
A Delphi instance method is passed as two pointers, one to the code and one to the instance. You can fake this in C++ by declaring the
RegisterCallback
function like this:Then, once you have loaded it with
GetProcAddress
, call it like this:It doesn't matter what the
instance
parameter is since we are going to ignore that when it is passed on toOnACLNeeded
.The final step is to arrange that your function behaves like a Delphi instance method. Do that by adding an extra parameter to represent the instance.
You will receive in the
instance
parameter, whatever you passed as theinstance
parameter when you calledRegisterCallback
.With these changes you should be able to fake out the Delphi DLL into believing that your code is a Delphi instance method!
For reference I suggest your read the Program Control topic of the Delphi Language Guide.
Finally, good luck!
Update
The latest edit to the question sheds some new light on what is happening. Specifically this code:
The
@TSNAPICongigF.OnACLNeeded
parameter results in just the code part of the method. That is it is just a single pointer. The instance is passed in the following parameter withDWORD(Self)
. The assumption that the author of this code has made is that the callback function will be passed three parameters, the user data followed by the two by var bytes. The trick is that such a function is equivalent to a method call because a method call is implemented behind the scenes by passing the instance as a hidden, implicit, parameter before the actual parameters.So, I believe you can solve the problem quite easily. Simply roll all the way back to where your code was when you asked the question. Then change your callback function to accept this extra parameter:
I am now confident that this will work.
You call the register function like this:
and your callback function should see the value of
1
in theUserData
parameter.