使用dllexport从DLL导出功能


105

我想要一个从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),声明(vb6)并GetProcAddress可以预期?(我猜GetProcAddress这取决于调用者创建的函数指针)。

我希望此DLL在没有头文件的情况下使用,因此我真的不需要花哨的钱就#defines可以使调用者使用该头。

我可以接受的答案是,我必须使用*.def文件。


我可能会记错了,但我认为:a)extern C将删除描述函数参数类型的修饰符,而不是描述函数调用约定的修饰符;b)要删除所有修饰,您需要在DEF文件中指定(未修饰的)名称。
ChrisW

这也是我所看到的。也许您应该将此添加为完整的答案?
Aardvark

Answers:


134

如果要纯C导出,请使用C项目而不是C ++。C ++ DLL依赖于所有C ++语言(名称空间等)的名称处理。您可以进入C / C ++-> Advanced下的项目设置,将代码编译为C,其中有一个选项“ Compile As”,对应于编译器开关/ 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")
);

1
extern“ C”似乎删除了c ++样式名称修饰。整个导入与导出的事情(我试图建议不包括在问题中)并不是我要问的(但它的好信息)。我认为这将使问题蒙上阴影。
Aardvark

我可以认为您需要的唯一原因是LoadLibrary和GetProcAddress ...已经解决了,我将在答复正文中进行阐述……
joshperry

是EXTERN_DLL_EXPORT == extern“ C” __declspec(dllexport)吗?是SDK中的吗?
Aardvark

3
不要忘记将模块定义文件添加到项目的链接器设置中-仅“将现有项目添加到项目”是不够的!
Jimmy

1
我用它用VS编译DLL,然后使用.C从R调用它。大!
Juancentro'2

33

对于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):

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

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

#pragma EXPORT

完整示例:

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

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


5
感谢您的重要提示。我已经想知道为什么我的64位DLL与32位DLL不同。我发现您的答案比被接受的答案有用得多。
Elmue

1
我真的很喜欢这种方法。我唯一的建议是将宏重命名为EXPORT_FUNCTION,因为该__FUNCTION__宏仅在函数中起作用。
路易斯

3

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


请注意其他人:使用.def模块导出文件确实可以,但是要以能够extern在头文件中提供例如全局数据的定义为代价,在这种情况下,必须extern内部使用时手动提供定义该数据。(是的,有时需要。)通常,特别是对于跨平台代码,最好__declspec()与宏一起使用,这样才能更好地处理数据,这通常会更好。
克里斯·克里乔

2
究其原因,可能是因为如果你正在使用__stdcall,那么__declspec(dllexport)不会删除装饰品。但是,将功能添加到.def遗嘱中。
比约恩·林德奎斯特(BjörnLindqvist)

1
@BjörnLindqvist+1,请注意,这仅适用于x86。看我的答案。
马里克

-1

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

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

更大的问题是为什么要这么做?乱七八糟的名字怎么了?


当使用LoadLibrary / GetProcAddress或其他不依赖于ac / c ++标头的方法调用时,整齐的名称很难看。
Aardvark

4
这将无济于事-您只想在非常特殊的情况下删除编译器生成的堆栈管理代码。(仅使用__cdecl丢失装饰是一种危害较小的方法-默认情况下__declspec(dllexport)似乎不包含带有__cdecl方法的常用_前缀。)
Ian Griffiths

我并不是真的在说这会有所帮助,因此我对其他效果提出了警告,并质疑他为什么想要这样做。
罗伯K
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.