【问题标题】:Exporting functions from a DLL with dllexport使用 dllexport 从 DLL 导出函数
【发布时间】:2021-09-17 06:40:18
【问题描述】:

我想要一个从 C++ Windows DLL 导出函数的简单示例。

我想查看标题、.cpp 文件和.def 文件(如果绝对需要)。

我希望导出的名称不加修饰。我想使用最标准的调用约定(__stdcall?)。我想使用__declspec(dllexport) 而不必使用.def 文件。

例如:

  //header
  extern "C"
  {
   __declspec(dllexport) int __stdcall foo(long bar);
  }

  //cpp
  int __stdcall foo(long bar)
  {
    return 0;
  }

我试图避免链接器在名称中添加下划线和/或数字(字节数?)。

我可以不支持 dllimportdllexport 使用相同的标头。我不想要任何关于导出 C++ 类方法的信息,只想要 c 风格的全局函数。

更新

不包括调用约定(并使用extern "C")为我提供了我喜欢的导出名称,但这意味着什么?我得到的 pinvoke (.NET)、declare (vb6) 和 GetProcAddress 所期望的默认调用约定是什么? (我猜对于GetProcAddress,这将取决于调用者创建的函数指针)。

我希望在没有头文件的情况下使用这个 DLL,所以我真的不需要很多花哨的 #defines 来使头可供调用者使用。

我可以回答我必须使用*.def 文件。

【问题讨论】:

  • 我可能记错了,但我认为:a) extern C 将删除描述函数参数类型的装饰,但不会删除描述函数调用约定的装饰; b) 要删除 all 装饰,您需要在 DEF 文件中指定(未装饰的)名称。
  • 这也是我所看到的。也许您应该将此添加为完整的答案?

标签: winapi visual-c++ dll name-decoration


【解决方案1】:

如果您想要纯 C 导出,请使用 C 项目而不是 C++。 C++ DLL 依赖于所有 C++isms(命名空间等)的名称修饰。您可以通过进入 C/C++->Advanced 下的项目设置将代码编译为 C,有一个选项“Compile As”对应于编译器开关 /TP 和 /TC。

如果您仍想使用 C++ 编写您的库的内部结构,但导出一些未修改的函数以在 C++ 之外使用,请参阅下面的第二部分。

在 VC++ 中导出/导入 DLL 库

您真正想要做的是在头文件中定义一个条件宏,该宏将包含在您的 DLL 项目的所有源文件中:

#ifdef LIBRARY_EXPORTS
#    define LIBRARY_API __declspec(dllexport)
#else
#    define LIBRARY_API __declspec(dllimport)
#endif

然后在要导出的函数上使用LIBRARY_API

LIBRARY_API int GetCoolInteger();

在您的库构建项目中创建一个定义LIBRARY_EXPORTS,这将导致您的函数被导出以用于您的 DLL 构建。

由于LIBRARY_EXPORTS 不会在使用 DLL 的项目中定义,因此当该项目包含您的库的头文件时,所有函数都将被导入。

如果您的库是跨平台的,您可以在不在 Windows 上时将 LIBRARY_API 定义为空:

#ifdef _WIN32
#    ifdef LIBRARY_EXPORTS
#        define LIBRARY_API __declspec(dllexport)
#    else
#        define LIBRARY_API __declspec(dllimport)
#    endif
#elif
#    define LIBRARY_API
#endif

使用 dllexport/dllimport 时不需要使用 DEF 文件,如果使用 DEF 文件则不需要使用 dllexport/dllimport。这两种方法以不同的方式完成相同的任务,我认为 dllexport/dllimport 是两者中推荐的方法。

从 C++ DLL 中为 LoadLibrary/PInvoke 导出未修改的函数

如果您需要它来使用 LoadLibrary 和 GetProcAddress,或者可能从另一种语言导入(即来自 .NET 的 PInvoke,或 Python/R 中的 FFI 等),您可以使用 extern "C" inline 与您的 dllexport 告诉 C++ 编译器不要破坏名称。由于我们使用的是 GetProcAddress 而不是 dllimport,我们不需要从上面执行 ifdef 舞蹈,只需一个简单的 dllexport:

代码:

#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport)

EXTERN_DLL_EXPORT int getEngineVersion() {
  return 1;
}

EXTERN_DLL_EXPORT void registerPlugin(Kernel &K) {
  K.getGraphicsServer().addGraphicsDriver(
    auto_ptr<GraphicsServer::GraphicsDriver>(new OpenGLGraphicsDriver())
  );
}

下面是使用 Dumpbin /exports 导出的样子:

  Dump of file opengl_plugin.dll

  File Type: DLL

  Section contains the following exports for opengl_plugin.dll

    00000000 characteristics
    49866068 time date stamp Sun Feb 01 19:54:32 2009
        0.00 version
           1 ordinal base
           2 number of functions
           2 number of names

    ordinal hint RVA      name

          1    0 0001110E getEngineVersion = @ILT+265(_getEngineVersion)
          2    1 00011028 registerPlugin = @ILT+35(_registerPlugin)

所以这段代码可以正常工作:

m_hDLL = ::LoadLibrary(T"opengl_plugin.dll");

m_pfnGetEngineVersion = reinterpret_cast<fnGetEngineVersion *>(
  ::GetProcAddress(m_hDLL, "getEngineVersion")
);
m_pfnRegisterPlugin = reinterpret_cast<fnRegisterPlugin *>(
  ::GetProcAddress(m_hDLL, "registerPlugin")
);

【讨论】:

  • extern "C" 似乎删除了 c++ 样式名称修饰。整个导入与导出的事情(我试图建议不包括在问题中)并不是我真正要问的(但它的信息很好)。我认为这会使问题变得模糊。
  • 我认为您需要它的唯一原因是 LoadLibrary 和 GetProcAddress ......这已经处理好了,我将在我的答案正文中阐述......
  • 是 EXTERN_DLL_EXPORT == extern "C" __declspec(dllexport) 吗?是在 SDK 中吗?
  • 不要忘记将模块定义文件添加到项目的链接器设置中——仅仅“将现有项添加到项目中”是不够的!
  • 我用它用VS编译了一个DLL,然后用.C从R调用它。太好了!
【解决方案2】:

对于 C++:

我刚刚遇到了同样的问题,我认为当一个人同时使用__stdcall(或WINAPI extern "C"时出现了一个问题:

如您所知,extern "C" 删除了装饰,以便代替:

__declspec(dllexport) int Test(void)                        --> dumpbin : ?Test@@YaHXZ

你得到一个未修饰的符号名称:

extern "C" __declspec(dllexport) int Test(void)             --> dumpbin : Test

不过,_stdcall(= 宏 WINAPI,它改变了调用约定)也修饰了名称,因此如果我们同时使用两者,我们将获得:

   extern "C" __declspec(dllexport) int WINAPI Test(void)   --> dumpbin : _Test@0

并且extern "C" 的好处丢失了,因为符号被装饰(带有_ @bytes)

请注意,这发生在 x86 架构中,因为 __stdcall 约定在 x64 上被忽略(msdn在 x64 架构上,按照约定,参数尽可能在寄存器中传递,随后的参数在堆栈上传递。)。

如果您同时针对 x86 和 x64 平台,这尤其棘手。


两种解决方案

  1. 使用定义文件。但这会迫使您维护 def 文件的状态。

  2. 最简单的方法:定义宏(见msdn):

#define EXPORT 注释(链接器,“/EXPORT:” __FUNCTION__ “= __FUNCDNAME__)

然后在函数体中包含以下编译指示:

#pragma EXPORT

完整示例:

 int WINAPI Test(void)
{
    #pragma EXPORT
    return 1;
}

这将为 x86 和 x64 目标导出未修饰的函数,同时保留 x86 的 __stdcall 约定。在这种情况下,__declspec(dllexport) 不需要

【讨论】:

  • 感谢您的重要提示。我已经想知道为什么我的 64 位 DLL 与 32 位的不同。我发现你的答案比被接受的答案有用得多。
  • 我真的很喜欢这种方法。我唯一的建议是将宏重命名为 EXPORT_FUNCTION,因为 __FUNCTION__ 宏仅适用于函数。
  • 购买 我可以使用 extern "C"{ __declspec(dllexport) void test(); } 在 VS2019
【解决方案3】:

我遇到了完全相同的问题,我的解决方案是使用模块定义文件 (.def) 而不是 __declspec(dllexport) 来定义导出 (http://msdn.microsoft.com/en-us/library/d91k01sh.aspx)。我不知道为什么会这样,但确实如此

【讨论】:

  • 请注意遇到此问题的其他人:使用.def 模块导出文件确实 工作,但代价是能够在标题中提供extern 定义例如文件全局数据——在这种情况下,您必须在该数据的内部使用中手动提供extern 定义。 (是的,有时您需要这样做。)一般情况下,尤其是对于跨平台代码而言,只需将__declspec() 与宏一起使用会更好,这样您就可以正常处理数据。
  • 原因可能是因为如果您使用__stdcall,那么__declspec(dllexport)不会删除装饰。但是,将函数添加到 .def 会。
  • @BjörnLindqvist +1,请注意,这仅适用于 x86。看我的回答。
【解决方案4】:

我认为 _naked 可能会得到你想要的,但它也会阻止编译器为函数生成堆栈管理代码。 extern "C" 导致 C 样式名称装饰。删除它,那应该摆脱你的_。链接器不添加下划线,编译器会。 stdcall 导致参数堆栈大小被追加。

有关更多信息,请参阅: http://en.wikipedia.org/wiki/X86_calling_conventions http://www.codeproject.com/KB/cpp/calling_conventions_demystified.aspx

更大的问题是您为什么要这样做?乱七八糟的名字有什么问题?

【讨论】:

  • 使用 LoadLibrary/GetProcAddress 或其他不依赖于 c/c++ 标头的方法调用时,错位名称很难看。
  • 这将无济于事-您只想在非常特殊的情况下删除编译器生成的堆栈管理代码。 (仅使用 __cdecl 将是一种减少装饰的危害较小的方式 - 默认情况下 __declspec(dllexport) 似乎不包含 __cdecl 方法的常用 _ 前缀。)
  • 我并不是真的说这会有所帮助,因此我对其他影响提出警告并质疑他为什么要这样做。
猜你喜欢
  • 1970-01-01
  • 2013-03-01
  • 2020-01-22
  • 2012-04-14
  • 2011-03-28
  • 1970-01-01
  • 2011-02-22
  • 1970-01-01
相关资源
最近更新 更多