【问题标题】:DLL call with __stdcall & GetProcAddress() in VS2013在 VS2013 中使用 __stdcall 和 GetProcAddress() 调用 DLL
【发布时间】:2015-09-25 18:34:51
【问题描述】:

我正在尝试从我自己的 DLL 中调用一个函数,但根据 DLL 项目中的调用约定,要么我找不到 ProcAddress,要么我的堆栈已损坏。它非常适合第 3 方 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;
}

在 DLL 中:

extern "C" __declspec(dllexport) long __stdcall myFunction(int a){
  return 10;
}

结果:GetProcAddress() 失败

dumpbin /EXPORTS -> _myFunction@4 = _myFunction@4

extern "C" __declspec(dllexport) long __cdecl myFunction(int a){
  return 10;
}

结果:“运行时检查失败 #0 - ESP 的值未在函数调用中正确保存。这通常是调用使用一种调用约定声明的函数而使用另一种调用声明的函数指针的结果习俗。” (因为我在加载代码时使用了 __stdcall,在 DLL 中使用了 __cdecl)。

dumpbin /EXPORTS -> _myFunction = _myFunction

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

【问题讨论】:

  • 您正在导出修饰的名称,但将未修饰的名称传递给GetProcAddress
  • 我猜是因为符号名称不匹配。也许您可以尝试使用 DEF 文件来指定导出的函数?见msdn.microsoft.com/en-us/library/d91k01sh.aspx
  • 仅供参考,extern C 不会停止名称修饰,只会停止 C++ 名称修饰。要完全没有装饰,您需要使用@KingsleyChen 建议的 DEF 文件。

标签: c++ dll visual-studio-2013


【解决方案1】:

首先,DLL 函数使用的调用约定必须与您正在使用的函数指针定义相匹配。由于它不匹配,您会收到您已损坏堆栈的错误。


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

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

GetProcAddress(myModuleHandle, "_myFunction@4");

不必以这种方式指定名称,因为函数的名称。

所以你有两个选择:

  1. 按上述方式更改代码,即使用实际名称或
  2. 更改DLL,使导出的名称实际上是myFunction

由于我们介绍了第一个选项,对于第二个选项,您必须重新构建 DLL 以使用 Module Definition File(或简称为 .DEF 文件)重新定义名称。

这是一个模块定义文件的链接:

Module Definition File

所以一个典型的 .DEF 文件会包含这个:

LIBRARY MyDLL

EXPORTS
    myFunction  @2   

@2ordinal number。出于您的目的,这个数字是什么并不重要,因为只有一个功能。我选择了@2,但您可以选择任何数字(@3@4,或者如果您愿意,甚至可以选择@1000)。但是,如果导出函数超过 1 个,则序号应该是唯一的,即不能有两个具有相同序号的导出函数。

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

(注意:正如上面的评论所提到的,使用的extern "C" 不会关闭Windows 使用的装饰(附加的下划线和附加在名称后面的@x)。所有extern "C" 确实是关闭 C++ 名称修改。要关闭 Windows 名称修改,需要 .DEF 文件。)

附:我使用名为Dependency Walker 的工具轻松确定导出的函数名称在DLL 中。由于 Dependency Walker 是一个 GUI 应用程序,因此输出比 dumpbin.exe 友好一点

Dependency Walker

补充一点,您提到 DLL 在其他应用程序中可以完美运行。如果那些其他应用程序使用import libraries 而不是使用LoadLibraryGetProcAddress 来访问该函数,那么那些导入库会自动处理myFunction_myFunction@4 的转换。

这就是为什么它适用于这些类型的应用程序而不会出现问题的原因。然而,当你走LoadLibraryGetProcAddress 的路线时,你在翻译名字时得不到这个“帮助”,你基本上是靠自己。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-11
    • 1970-01-01
    • 1970-01-01
    • 2012-03-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多