__declspec(dllexport)导出到dll
__declspec(dllimport)从dll导入
如果不使用这两个,需要自己编写.df文件
------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------
1、解决的问题:
考虑下面的需求,使用一个方法,一个是提供者,一个是使用者,二者之间的接口是头文件。头文件中声明了方法,在提供者那里方法应该被声明为__declspec(dllexport),在使用者那里,方法应该被声明为__declspec(dllimport)。二者使用同一个头文件,作为接口,怎么办呢?
2、解决办法:
使用条件编译:定义一个变量,针对提供者和使用者,设置不同的值。
1 #ifndef DLL_H_ 2 #define DLL_H_ 3 4 #ifdef DLLProvider 5 #define DLL_EXPORT_IMPORT __declspec(dllexport) 6 #else 7 #define DLL_EXPORT_IMPORT __declspec(dllimport) 8 #endif 9 10 DLL_EXPORT_IMPORT int add(int ,int); 11 12 #endif
------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------
.def 文件必须至少包含下列模块定义语句:
1.文件中的第一个语句必须是 LIBRARY 语句。此语句将 .def 文件标识为属于 DLL。LIBRARY 语句的后面是 DLL 的名称。链接器将此名称放到 DLL 的导入库中。
2.EXPORTS 语句列出名称,可能的话还会列出 DLL 导出函数的序号值。通过在函数名的后面加上 @ 符和一个数字,给函数分配序号值。当指定序号值时,序号值的范围必须是从 1 到 N,其中 N 是 DLL 导出函数的个数。
例如,包含实现二进制搜索树的代码的 DLL 看上去可能像下面这样:
LIBRARY BTREE
EXPORTS
Insert @1
Delete @2
Member @3
Min @4
提示: |
---|
如果希望优化 DLL 文件的大小,请对每个导出函数使用 NONAME 属性。使用 NONAME 属性时,序号存储在 DLL 的导出表中而非函数名中。如果导出许多函数,这样做可以节省相当多的空间。 |
其实__declspec(dllexport)的作用就是让编译器按照某种预定的方式(前面大致解释了这种方式的规则)来输出导出函数及变量的符号,而def文件则是自己为每一个函数和变量指定导出符号,所以def是一个非自动化,手工很强的方式,不是特殊情况的话,实在没有必要浪费这些时间。
还有一个问题,就是使用def会把调用方式和__declspec(dllexport)的作用全部覆盖掉,所以还需要自己处理调用方式不同产生的错误。
一般使用def文件的情况是你需要使用运行时加载,并且需要使用GetProcAddress函数获得函数地址,这个函数需要直接指明函数产生的导出符号,而可以自己指定导出符号的方式就是使用def。
def文件的具体语法可以看看msdn。
------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------
总结了各位大虾的发言,特得出如下结论:
1. 在导入动态链接库中的全局变量方面起作用:
使用类似
[cpp] view plaincopy
- #ifdef _EXPORTING
- #define API_DECLSPEC __declspec(dllexport)
- #else
- #define API_DECLSPEC __declspec(dllimport)
- #endif
可以更好地导出dll中的全局变量,比如按照的宏,可以在dll中这样导出全局变量:
[cpp] view plaincopy
- API_DECLSPEC CBtt g_Btt;
然后在调用程序这样导入:
[cpp] view plaincopy
- API_DECLSPEC CBtt g_Btt;
当然也可以使用extern关键字,比如在dll中这样导出全局变量:
[cpp] view plaincopy
- CBtt g_Btt;
然后在调用程序这样导入:
[cpp] view plaincopy
- extern CBtt g_Btt;
但据说使用__declspec(dllimport)更有效。
2. __declspec(dllimport)的作用主要体现在导出类的静态成员方面,
比如在动态链接库中定义这样一个导出类:
[cpp] view plaincopy
- class __declspec(dllexport) CBtt
- {
- public:
- CBtt(void);
- ~CBtt(void);
- public:
- CString m_str;
- static int GetValue()
- {
- return m_nValue;
- }
- private:
- static int m_nValue;
- };
照上面这样声明,外部虽然可以使用CBtt类,但不能使用CBtt类的GetValue函数,一使用就会出现无法解析的外部符号 "public: static int CBtt::m_nValue" (?m_nValue@CBtt@@2HA)。只有如下声明才能使用CBtt类的GetValue函数:
[cpp] view plaincopy
- #ifdef _EXPORTING
- #define API_DECLSPEC __declspec(dllexport)
- #else
- #define API_DECLSPEC __declspec(dllimport)
- #endif
- class API_DECLSPEC CBtt
- {
- public:
- CBtt(void);
- ~CBtt(void);
- public:
- CString m_str;
- static int GetValue()
- {
- return m_nValue;
- }
- private:
- static int m_nValue;
- };
3. 使用隐式使用dll时,不加__declspec(dllimport)完全可以,使用上没什么区别,只是在生成的二进制代码上稍微有点效率损失。
a、 不加__declspec(dllimport)时,在使用dll中的函数时,编译器并不能区别这是个普通函数,还是从其它dll里导入的函数,所以其生 成的代码如下:
call 地址1
地址1:
jmp 实际函数地址
b、有 __declspec(dllimport)时,编译器知道这是要从外部dll导入的函数,从而在生成的exe的输入表里留有该项,以便在运行 exe,PE载入器加载exe时对输入地址表IAT进行填写,这样生成的代码如下:
call dword ptr[输入表里哪项对应的内存地址] (注意:现在就不需要jmp stub了)。这里
有兴趣的朋友可以参看《编译原理》和 PE文件格式。
4.使用__declspec(dllimport)体现了语言的一种对称美,比如虽然!true就是表示false,但是我们还是需要false这个关键字,这里体现了一种对称美。
在此特别感谢CSDN的众位大侠:superdiablo、wltg2001、ccpaishi、jszj、WizardK、hurryboylqs、jingzhongrong、jameshooo、glacier3d、winnuke、starnight1981、laiyiling、yang79tao、ForestDB、zhouzhipen、lxlsymbome、Beyond_cn。
参考文献: