Delphi DLL call from C ++ using GetProcAddress: callback function does not work with invalid parameter

I have a third-party Delphi DLL that I call from C ++. Unfortunately, I do not have access to the Pascal DLL file, and I am not a Pascal programmer.

There is no lib file, so I use GetProcAddress to call many DLL functions that successfully pass parameters by value, address and link. I also register a callback function that is 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, a 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 for callback function

 int __stdcall OnACLNeeded(byte& keySettings, byte& numOfKeys) { keySettings = 0x0F; numOfKeys = 1; return 1; } 

This is my C ++ call 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 starting in the debugger, I reach the breakpoint in the first line of the callback function keySettings = 0x0F;
I believe numOfKeys valid, but keySettings has the address 0x00000001 and cannot be assigned.
The application will crash with AccessViolation if I continue.

 int __stdcall OnACLNeeded(byte& keySettings, byte& numOfKeys) { keySettings = 0x0F; 

I tried declaring as __cdecl without effect.
I tried declaring the byte parameters as byte *, and I get the same invalid parameter.

I am using Visual Studio 2010, running on a 64-bit version of Win7, but compiling as Win32.
These are my compilation commands and links.

 /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 are greatly appreciated. Thanks.

------ edit -----

I added structure

 struct Bodge { void* code; void* instance; }; Bodge bodge; bodge.code = OnACLNeeded; bodge.instance = (void*)0x99; // just to test 

My callback is getting

 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 so called

 ret = (*pfnRegisterCallback)( h, bodge, (DWORD)1); 

This call causes an error.

The ESP value was not properly stored in the function call. This is usually the result of calling a function declared with one call, with a function pointer declared with another calling convention.

which may also indicate a damaged stack. BUT, if I ignore the error and continue, I fall into the body of the callback function, and now the BOTH parameters apply!
Thus, success of this kind, but also, the void* instance parameter has a value of 0, not 0x99, which I set.
I feel like we are getting there!

----- edit -----

This is a function call to register a callback from the source code of a Pascal call.

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

Pay attention to the link "self". What is the C ++ equivalent? (I do not use classes)

+4
source share
1 answer

The problem is that the Delphi callback version is an instance method. C ++ callback is not. This is a significant discrepancy. This Delphi interface is poorly designed and cannot be called from C ++ without any trickery.

The Delphi instance method is passed as two pointers, one for the code and one for the instance. You can fake this in C ++ by declaring the RegisterCallback function as follows:

 typedef int (__stdcall *FnRegisterCallback)( HANDLE hKeyProvider, void* code, void* instance, DWORD ); 

Then, once you have downloaded it using GetProcAddress , call it as follows:

 int ret = (*pfnRegisterCallback)(h, OnACLNeeded, NULL, (DWORD)1); 

It does not matter what the instance parameter is, since we will ignore it when it is passed to OnACLNeeded .

The final step is to arrange your function as a Delphi instance method. Do this by adding an extra parameter to represent the instance.

 int __stdcall OnACLNeeded(void* instance, byte& keySettings, byte& numOfKeys) { keySettings = 0x0F; numOfKeys = 1; return 1; } 

You will get in the instance parameter everything that you passed as the instance parameter when you called RegisterCallback .

With these changes, you can fake the Delphi DLL, assuming your code is a Delphi instance method!

For reference, I suggest you read "Program Control" in the Delphi Language Guide.

Finally, good luck!


Update

Last editing of the question sheds some new light on what is happening. In particular, this code:

 RegisterCallback( hKeyProv, @TSNAPICongigF.OnACLNeeded, DWORD(Self) ); 

The @TSNAPICongigF.OnACLNeeded displays only part of the method code. That is, this is just one pointer. The instance is passed in the next parameter using DWORD(Self) . The assumption made by the author of this code is that the callback function will be passed in three parameters, user data, followed by two var bytes. The trick is that such a function is equivalent to a method call, because the method call is implemented behind the scenes, passing the instance as a hidden, implicit parameter in front of the actual parameters.

So, I believe that you can solve the problem quite easily. Just throw it all the way back to where your code was when you asked the question. Then change the callback function to accept this additional parameter:

 int __stdcall OnACLNeeded(DWORD UserData, byte& keySettings, byte& numOfKeys) { keySettings = 0x0F; numOfKeys = 1; return 1; } 

Now I'm sure it will work.

You call the register function as follows:

 int ret = (*pfnRegisteraCllback)(h, OnACLNeeded, (DWORD)1); 

and your callback function should see the value 1 in the UserData parameter.

+3
source

Source: https://habr.com/ru/post/1401640/


All Articles