C++ 使用stdcall&;调用DLL;VS2013中的GetProcAddress()

C++ 使用stdcall&;调用DLL;VS2013中的GetProcAddress(),c++,dll,visual-studio-2013,C++,Dll,Visual Studio 2013,我试图从自己的DLL调用函数,但根据DLL项目中的调用约定,我可能找不到ProcAddress,或者堆栈已损坏。它适用于第三方DLL,因此如果没有重大问题,我不想更改加载代码本身的任何内容。一个简单的例子: #include <windows.h> #include <cstdlib> #include <iostream> typedef long (__stdcall* tMyFunction)(int); int main(int argc, cha

我试图从自己的DLL调用函数,但根据DLL项目中的调用约定,我可能找不到ProcAddress,或者堆栈已损坏。它适用于第三方DLL,因此如果没有重大问题,我不想更改加载代码本身的任何内容。一个简单的例子:

#include <windows.h>
#include <cstdlib>
#include <iostream>

typedef long (__stdcall* tMyFunction)(int);

int main(int argc, char* argv[]){
  HINSTANCE m_dllHandle = LoadLibrary("MyDll.dll");
  if (m_dllHandle != NULL){
    tMyFunction function = (tMyFunction)GetProcAddress(m_dllHandle, "myFunction");
    if (function != NULL){
      long value = function(1);
      std::cout << value << std::endl;
    }else{
      std::cout << "GetProcAddress() failed" << std::endl;
    }

    FreeLibrary(m_dllHandle);
    m_dllHandle = NULL;
  }else{
    std::cout << "LoadLibrary() failed" << std::endl;
  }
  system("pause");
  return EXIT_SUCCESS;
}
结果:GetProcAddress()失败

垃圾箱/出口->_myFunction@4 = _myFunction@4

extern "C" __declspec(dllexport) long __cdecl myFunction(int a){
  return 10;
}
结果:“运行时检查失败#0-ESP的值未在函数调用中正确保存。这通常是调用使用一个调用约定声明的函数,而函数指针使用另一个调用约定声明的结果。”(因为我在加载代码中使用u stdcall,在DLL中使用u cdecl)

dumpbin/EXPORTS->\u myFunction=\u myFunction

在第三方DLL中,我可以看到,“dumpbin/EXPORTS”只显示
myFunction(没有下划线,没有@4)我可以做些什么来实现同样的功能,并且仍然能够用上面定义的类型(
typedef long(u stdcall*tMyFunction)(int);
)加载它?我的编译器是“Visual Studio 2013”。

首先,DLL函数使用的调用约定必须与您使用的函数指针定义匹配。由于它不匹配,您会得到一个错误,即损坏了堆栈


其次,使用
GetProcAddress
时,调用
GetProcAddress
时使用的函数名必须与导出的DLL函数名完全匹配。它不仅要匹配字符,还要匹配大小写,即
myFunction
myFunction
不同

示例中导出的名称是
_myFunction@4
。这意味着使用
GetProcAddress
访问函数将是:

GetProcAddress(myModuleHandle, "_myFunction@4");
不必这样指定名称,因为这是函数的名称

所以你有两个选择:

  • 如上所述更改代码,即使用实际名称或
  • 更改DLL,使导出的名称实际上是
    myFunction
  • 由于我们讨论了第一个选项,对于第二个选项,您必须重建DLL以使用
    模块定义文件
    (或简称为
    .DEF
    文件)重新定义名称

    以下是模块定义文件的链接:

    因此,典型的.DEF文件将包含以下内容:

    LIBRARY MyDLL
    
    EXPORTS
        myFunction  @2   
    
    @2
    序号
    。出于您的目的,这个数字是多少并不重要,因为只有一个函数。我选择了
    @2
    ,但你可以选择任何数字(
    @3
    @4
    ,甚至
    @1000
    ,如果你愿意的话)。但是,如果有多个导出函数,则序号应该是唯一的,即不能有两个具有相同序号的导出函数

    如果将上述内容保存到
    MyDll.DEF
    并将其添加到构建DLL的项目(而不是将使用DLL的项目),则需要重建DLL。完成后,DLL现在将有一个导出名称
    myFunction
    ,没有
    @4
    装饰,也没有下划线

    (注意:如上所述,使用的
    extern“C”
    不会关闭Windows使用的装饰(名称后面附加的下划线和
    @x
    )。所有
    extern“C”确实关闭C++名称。要关闭Windows名称,必须使用<代码> .DEF< /COD>文件> 

    另外,我使用一个名为
    Dependency Walker
    的工具来轻松确定DLL中导出的函数名。由于Dependency Walker是一个GUI应用程序,因此输出比
    dumpbin.exe

    补充一点,您提到DLL在其他应用程序中可以完美地工作。如果这些其他应用程序使用
    导入库
    而不是使用
    加载库
    GetProcAddress
    访问函数,那么这些导入库将自动处理
    myFunction
    的转换_myFunction@4


    这就是为什么它对这些类型的应用程序没有问题。但是,当您选择
    LoadLibrary
    GetProcAddress
    时,您在翻译名称时没有得到这种“帮助”,您基本上只能靠自己。

    您导出修饰名称,但将未修饰的名称传递给
    GetProcAddress
    。我猜这是由于符号名称不匹配造成的。也许您可以尝试使用DEF文件来指定导出的函数?参见FYI,<代码>外部C < /COD>不停止名称修饰,只有C++名称的修改。要完全没有装饰,您需要使用@KingsleyChen建议的DEF文件。
    LIBRARY MyDLL
    
    EXPORTS
        myFunction  @2