使用 __stdcall & 调用 DLLVS2013 中的 GetProcAddress()

2021-12-25 00:00:00 dll c++ visual-studio-2013

我正在尝试从我自己的 DLL 中调用一个函数,但是根据 DLL 项目中的调用约定,我要么找不到 ProcAddress,要么我的堆栈已损坏.它非常适用于 3rd Party DLL,因此如果那里没有大问题,我不想更改加载代码本身的任何内容.一个最小的例子:

I'm trying to call a function from my own DLL, but depending on the calling convention in the DLL project either I can't find the ProcAddress or my stack is getting corrupted. It works perfectly for 3rd Party DLLs so I would like to not change anything in the loading code itself if there is no major problem there. A minimal example:

#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() 失败

Result: GetProcAddress() fails

垃圾箱/EXPORTS -> _myFunction@4 = _myFunction@4

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

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

结果:运行时检查失败 #0 - ESP 的值未在函数调用中正确保存.这通常是调用使用一个调用约定声明的函数以及使用不同调用声明的函数指针的结果惯例."(因为我在加载代码时使用 __stdcall,在 DLL 中使用 __cdecl).

Result: "Run-Time Check Failure #0 - 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." (Because I use __stdcall in loading code and __cdecl in DLL).

垃圾箱/EXPORTS -> _myFunction = _myFunction

dumpbin /EXPORTS -> _myFunction = _myFunction

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

In 3rd party DLLs, I can see, that "dumpbin /EXPORTS" only shows myFunction (no underscores, no @4) What can I do to accomplish the same and still be able to load it with the above defined type (typedef long (__stdcall* tMyFunction)(int);)? My compiler is "Visual Studio 2013".

推荐答案

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

First, the calling convention used by the DLL function must match the function pointer definition you are using. Since it didn't match, you get the error that you've corrupted the stack.

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

Second, When you use GetProcAddress the function name you use in the call to GetProcAddress must match exactly the exported DLL function name. It has to match not only on characters, but casing also i.e. myFunction is not the same as MyFunction.

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

The exported name in your example is _myFunction@4. which means that accessing the function using GetProcAddress would be:

GetProcAddress(myModuleHandle, "_myFunction@4");

没有必要以这种方式指定名称,因为就是函数的名称.

There is no getting around having to specify the name this way, since that is the name of the function.

所以你有两个选择:

  1. 按照上面的描述更改代码,即使用实际名称或
  2. 更改 DLL 以便导出的名称实际上是 myFunction

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

Since we covered the first option, for the second option, you have to rebuild the DLL to use a Module Definition File (or simply known as a .DEF file) to redefine the name.

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

Here is a link to what a module definition file is:

模块定义文件

所以典型的 .DEF 文件将包含以下内容:

So a typical .DEF file would contain this:

LIBRARY MyDLL

EXPORTS
    myFunction  @2   

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

The @2 is the ordinal number. For your purposes, it is not important what this number is since there is only one function. I chose @2, but you can choose any number (@3, @4, or even @1000 if you desired). However, if there is more than 1 exported function, the ordinal numbers should be unique, i.e., you can't have two exported functions that have the same ordinal number.

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

If you save the above contents to a MyDll.DEF and added it to the project that builds the DLL (not the project that will use the DLL), you will then need to rebuild the DLL. Once that's done, the DLL will now have an exported name of myFunction without the @4 decoration and without the underscore.

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

(Note: As mentioned by the comment above, the extern "C" used does not turn off the decoration that Windows uses (the additional underscore and the @x appended to the name). All extern "C" does is turn off the C++ name mangling. To turn off the Windows name mangling, that requires the .DEF file.)

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

P.S. I use a tool called Dependency Walker to easily determine what the exported function names are in a DLL. Since Dependency Walker is a GUI app, the output is a little friendlier than dumpbin.exe

依赖行者

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

Just to add, you mention that the DLL works flawlessly in other applications. If those other applications use import libraries instead of using LoadLibrary and GetProcAddress to access the function, then those import libraries automatically handle the translation of myFunction to _myFunction@4.

这就是为什么它适用于这些类型的应用程序没有问题的原因.但是,当您采用LoadLibraryGetProcAddress 的路线时,您无法获得翻译名称的帮助",您基本上是靠自己的.

That's why it works without issues for these types of applications. However, when you take the route of LoadLibrary and GetProcAddress, you are not afforded this "help" in getting the name translated, and you're basically on your own.

相关文章