使用dllexport从DLL导出函数

时间:2009-02-11 18:28:23

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

我想要一个从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;
  }

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

我没有使用相同的标头支持dllimport和dllexport。我不想要任何有关导出C ++类方法的信息,只需要c风格的全局函数。

更新

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

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

我很满意答案是我必须使用DEF文件。

4 个答案:

答案 0 :(得分:119)

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

在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是两者中推荐的方法。

从LoadLibrary / PInvoke的C ++ DLL导出未编码的函数

如果您需要使用LoadLibrary和GetProcAddress,或者从.NET执行PInvoke,您可以使用extern "C"与dllexport内联。因为我们使用的是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 :(得分:21)

对于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架构,因为   在x64架构上的x64(msdn)上忽略__stdcall约定,按照惯例,参数在可能的情况下在寄存器中传递,后续参数在堆栈上传递。)

如果您同时定位x86和x64平台,这一点尤其棘手。

两个解决方案

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

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

  3.   

    #define EXPORT注释(链接器,“/ EXPORT:”__ FUNCTION__“=”   __FUNCDNAME __)

    然后在函数体中包含以下pragma:

    #pragma EXPORT
    

    完整示例:

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

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

答案 2 :(得分:3)

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

答案 3 :(得分:-1)

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

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

更大的问题是你为什么要那样做?被破坏的名字有什么问题?