最近恰好需要用 C++ 实现一个供 C# .NET 调用的模块,用dllexport
导出符号的时候出现了一点问题,明明已经看到了导出符号,但是 .NET 在调用的时候,就是找不到方法。然后用 def 文件的方式导出符号就正常,突然对这两种方式的区别产生兴趣,之前一直没有研究过,就仔细了查阅一番。
以导出名为 TESTFUNC 方法为例:
def 文件方式
,正常在程序中定义方法,然后新建一个 .def 文件,内容类似
LIBRARY
EXPORT
TESTFUNC
在其他工程使用这个方法的时候需要头文件,然后在连接时指定 .def 文件目录。
dllexport方式
在定义 TESTFUNC 方法时,在声明前加上 __declspec(dllexport)
。
区别
这里就不得不说导出符号在 DLL 中的形式。实际上对于 C++ 来说,当导出的时候,不会以原名导出,因为会加上一些符号字母后缀,实际上如果了解 C++ 的人,也会知道 C++ 在处理函数重载的时候,其实也用了这个套路,实际上编译之后就没有重载的概念了,而是根据参数生成了独一无二的方法名。
那说回来既然名字不同,那为什么其他模块调用还没问题呢。回答这个问题之前要先知道其他模块如何引用。
调用导出函数的方式
一般有三种形式:
- .h 提供声明之后,直接调用,在连接的时候指定 .def 文件目录;
- .h 提供声明之后,直接调用,链接的时候指定 .lib 文件地址
- 内部声明要调用函数的函数指针,loadlibrary 之后,直接取到函数地址,调用函数;
了解了这三种方式之后,就可以回答上边的问题。
- 对于1、2两种方式,由编译器自动转换函数名,寻找到正确的地址,链接之;
- 对于第3种方式,如果不把真正的函数方法名写对,就找不到函数了。
所以其他模块调用没问题。
话说回来,那也不可能每次都把@
那些符号写对。所以会看到有时候导出的时候_extern "C" _declspec(dllexport)
这样写,这是为了让函数以 C 的方式来编译,这样导出的方法就是没有那些符号的了,但这样有个问题,就是函数必须以 C 方式调用,而且也不能用来导出类对象,原因是显而易见的……
其实当了解上边之后,不难发现,1、2才是我们最想要的,3就很局限。这样问题就来了,1、2两种方式又有什么区别呢:
区别就在这个 .lib 上,如果在 C++ 或者 C 工程这个范围来说,确实没区别。但是假如调用工程不是 C++ 工程呢,他就是个 C# 工程呢,他是没办法用 .lib 的。
综上所述:.def 文件的方式才是最通用的做法。那回到我最初的问题,我的 C# 工程之所以在调用使用 dllexport
导出的方法失败,就是因为,我没有写对真正的方法名(带一堆符号的那个)。而使用 .def 文件的话,就没有这个问题了。