111

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

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

我希望导出的名称是undecorated。我想使用最标准的调用约定(__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)、声明 (vb6) 和GetProcAddress期望?(我猜GetProcAddress这将取决于调用者创建的函数指针)。

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

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

4

4 回答 4

150

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

如果您仍想使用 C++ 编写 lib 的内部结构,但导出一些未损坏的函数以在 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"在 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")
);
于 2009-02-11T18:39:15.117 回答
46

对于 C++:

我刚刚遇到了同样的问题,我认为值得一提的是,当一个人同时使用 __stdcall(or 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 架构上,因为__stdcallx64 上忽略了约定(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 目标导出未修饰的函数,同时保留__stdcallx86 的约定。在这种情况下__declspec(dllexport) 不需要

于 2017-01-28T13:40:01.923 回答
3

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

于 2013-09-23T14:34:09.823 回答
-1

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

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

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

于 2009-02-11T18:40:03.040 回答