使用GetProcAddress从C ++调用Delphi DLL:回调函数失败,参数无效 [英] Calling Delphi DLL from C++ using GetProcAddress: callback function fails with invalid parameter

查看:615
本文介绍了使用GetProcAddress从C ++调用Delphi DLL:回调函数失败,参数无效的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个第三方Delphi DLL,我从C ++调用。不幸的是,我没有访问Pascal DLL代码,我不是一个Pascal程序员。



没有lib文件,所以我使用GetProcAddress调用许多DLL函数,按值,地址和引用成功传递参数。我还要注册一个回调函数,在需要时调用。



我的问题是,在回调函数中,两个参数之一不能被计算(地址0x000001)。



这里是Pascal DLL函数声明

  type 
HANDLE = Pointer; /// handle

(**此函数注册回调函数OnACLNeeded
*)
函数RegisterCallback(
h:HANDLE;
OnACLNeeded:MyCallbackFunc;
UserData:DWORD):Integer; stdcall;

这是调用应用程序的pascal版本,即回调函数。
两个参数都通过引用(var)传递。

  function TSNAPICongigF.OnACLNeeded(var keySettings,numOfKeys:Byte) : 整数; stdcall; 
begin
keySettings:= $ 0F;
numOfKeys:= 1;
结果:= 0;
end;

这是我的C ++版本的回调函数

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

这是我的C ++调用代码

  int _tmain()
{
HMODULE hLib = LoadLibrary(PASCAL_DLL);

// DLL函数指针
typedef int(__stdcall * FnRegisterCallback)(HANDLE hKeyProvider,
int(__stdcall *)(byte& amp;),
DWORD );

FnRegisterCallback pfnRegisterCallback =
(FnRegisterCallback)GetProcAddress(hLib,RegisterCallback);

//注册我的回调函数
int ret =(* pfnRegisteraCllback)(h,OnACLNeeded,(DWORD)1);
}

在调试器中运行时,我到达第一行的断点回调函数 keySettings = 0x0F;

我发现 numOfKeys 有效,但 keySettings 的地址为0x00000001,无法分配给。

如果我继续,应用程序将与AccessViolation崩溃。

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

我尝试声明为__cdecl无效。

尝试声明字节参数为字节*,我得到相同的无效参数。



我使用的Visual Studio 2010运行在Win7 64位,但编译为Win32。

这些是我的编译和链接命令



  / ZI / nologo / W3 / WX- / Od / Oy- / DWIN32/ D_DEBUG/ D_CONSOLE / D_UNICODE/ DUNICODE/ Gm / EHsc / RTC1 / GS / fp:precise / Zc:wchar_t / Zc:forScope /Fp\"Debug\CallPascal.pch/ FaDebug \/ Fo Debug \/Fd\"Debug\vc100.pdb/ Gd / analyze- / errorReport:queue 

/OUT:\"...\Debug\CallPascal.exe/ INCREMENTAL / NOLOGOkernel32.libuser32.libgdi32.libwinspool.libcomdlg32.libadvapi32.libshell32.libole32.liboleaut32.libuuid。 libodbc32.libodbccp32.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

任何建议非常感谢。非常感谢。



------编辑-----



/ p>

  struct Bodge {
void * code;
void * instance;
};

Bodge bodge;

bodge.code = OnACLNeeded;
bodge.instance =(void *)0x99; //只是测试

我的回调变成

  Integer __stdcall OnACLNeeded(void * instance,Byte& keySettings,Byte& numOfKeys); 

我的注册回调的函数调用成为

  typedef Integer(__stdcall * FnRegisterCallback)(
HANDLE,
Bodge,
DWORD);

并像这样调用

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

此调用产生错误


ESP的值没有在函数调用中正确保存。这通常是使用一个调用约定声明的函数调用以不同调用约定声明的函数指针的结果。


也表示一个损坏的堆栈,我想。

如果我忽略错误,继续,我进入回调函数体,BOTH参数现在有效!

所以成功一个类,而且, void * instance param的值为零,而不是我设置的0x99。

我觉得我们到达那里尽管!



-----编辑-----



这是函数调用寄存器

  * @param hKey提供先前使用LASSeOKeyProvider_CreateHandle创建的Desfire卡的密钥提供程序句柄b $ b * @param OnACLNeeded提供一个回调,用于查询PICC主密钥设置的主机应用程序
* @param UserData无符号整数值指定在调用回调时返回的任何自定义提供的数据
* @return 0 - 成功; <> 0 - 表示错误代码

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

注意self引用。什么是C ++等价物? (我不使用类)

解决方案

问题是Delphi版本的回调是一个实例方法。你的C ++回调不是。这是一个显着的不匹配。这个Delphi接口设计得很糟,不能从C ++中调用而没有一些技巧。



Delphi实例方法作为两个指针传递,一个传递给代码,一个传递给实例。你可以在C ++中通过声明 RegisterCallback 函数来伪造这个函数:

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

然后,一旦你加载了 GetProcAddress ,调用它像这样:

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

实例参数是因为我们将忽略当它传递到 OnACLNeeded



最后一步是安排你的函数像一个Delphi实例方法。这是通过添加一个额外的参数来表示实例。

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

您将收到实例参数,当您调用 RegisterCallback 时,无论您作为实例参数传递。



通过这些更改,您应该能够假定Delphi DLL相信您的代码是一个Delphi实例方法!



建议您阅读Delphi语言指南的计划控制主题。



最后,祝你好运!






更新 p>

对问题的最新编辑揭示了一些新的发现。具体来说,代码如下:

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

参数 @ TSNAPICongigF.OnACLNeeded 只是方法的代码部分。这就是它只是一个指针。该实例在 DWORD(Self)中的以下参数中传递。这个代码的作者的假设是,回调函数将传递三个参数,用户数据后跟两个由var字节。诀窍是,这样的函数相当于一个方法调用,因为方法调用是通过在实际参数之前传递实例作为隐藏的隐式参数来实现的。



所以,我相信你可以很容易地解决这个问题。简单地滚动返回到你的代码是当你问的问题。然后改变回调函数以接受这个额外的参数:

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



我现在相信这将有效。



你这样调用寄存器函数:

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

并且您的回调函数应该看到 1 UserData 参数中。


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:

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

Then, once you have loaded it with GetProcAddress, call it like this:

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

It doesn't matter what the instance parameter is since we are going to ignore that when it is passed on to OnACLNeeded.

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.

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

You will receive in the instance parameter, whatever you passed as the instance parameter when you called RegisterCallback.

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:

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

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 with DWORD(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:

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

I am now confident that this will work.

You call the register function like this:

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

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

这篇关于使用GetProcAddress从C ++调用Delphi DLL:回调函数失败,参数无效的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆