extern "C"int add(int x,int y);
extern "C"int _declspec(dllimport) add(int x,int y);
使用DLL导出函数的声明有两种方式:
(1)在函数声明中加上__declspec(dllexport),这里不再举例说明;
(2)采用模块定义(.def) 文件声明,.def文件为链接器提供了有关被链接程序的导出、属性及其他方面的信息。.def文件的基本语法为:
① LIBRARY语句说明.def文件相应的DLL;
②EXPORTS语句后列出要导出函数的名称。可以在.def文件中的导出函数名后加@n,表示要导出函数的序号为n(在进行函数调用时,这个序号将发挥其作用);
③.def 文件中的注释由每个注释行开始处的分号 (;) 指定,且注释不能与语句共享一行。例如:
;DllTestDef.lib : 导出DLL函数
;作者:----
LIBRARY DllTestDef
EXPORTS
ADD @ 1
有两种方式可以导出 C++ 文件中的函数:使用.def文件和使用extern "C"
将修饰名放到 .def 文件中,可以通过使用 DUMPBIN 工具或 /MAP 链接器选项来获取修饰名。请注意,编译器产生的修饰名是编译器特定的。如果将 Visual C++ 编译器产生的修饰名放到 .def 文件中,则链接到 DLL 的应用程序必须也是用相同版本的 Visual C++ 生成的,这样调用应用程序中的修饰名才能与 DLL 的 .def 文件中的导出名相匹配。
另外还需要将下列代码放在包含导出类的头文件的开头和结尾:
#undefAFX_DATA
#define AFX_DATA AFX_EXT_DATA
<bodyof your header file>
#undefAFX_DATA[1]
#defineAFX_DATA
这些代码行确保内部使用的 MFC 变量或添加到类的变量是从扩展 DLL 导出(或导入)的。例如,当使用 DECLARE_DYNAMIC 派生类时,该宏扩展以将 CRuntimeClass 成员变量添加到类。省去这四行代码可能会导致不能正确编译或链接 DLL,或在客户端应用程序链接到 DLL 时导致错误。
当生成 DLL 时,链接器首先使用 .def 文件创建导出 (.exp) 文件和导入库 (.lib) 文件,然后使用导出文件生成 DLL 文件。隐式链接到 DLL 的可执行文件在生成时链接到导入库。请注意,MFC 本身使用 .def 文件从 MFCx0.dll 导出函数和类。
使用extern “C”定义具有标准 C 链接的导出函数具有以下使用要点:
①可以是单一语句也可以是复合语句
extern"C" double sqrt(double);
extern"C"
{
double sqrt(double);
int min(int, int);
}
② 可以包含头文件
extern "C"
{
#include <cmath>
}
③ 如果函数有多个声明,可以都加extern "C", 也可以只出现在第一次声明中,后面的声明会接受第一个链接指示符的规则。
④ 不能将extern "C"放到函数内部
⑤ 除extern"C"外还有extern "FORTRAN"等
外部调用dll内函数的机制:
通过调用 GetProcAddress,可以获取 DLL 导出函数的地址,返回值是一个指向DLL中函数的函数指针函数。
GetProcAddress的参数由两部分组成:
① DLL 模块句柄,由 LoadLibrary、AfxLoadLibrary 或 GetModuleHandle 返回;
②要调用的函数名或函数的导出序号。
使用导出序号方式调用 GetProcAddress 中指定的函数时,需要满足下列条件:
1)要链接到的 DLL 是用模块定义 (.def) 文件生成;
2)序号在 DLL 的 .def 文件的 EXPORTS 部分中与函数一起列出;
3)有权控制 .def 文件中导出函数的序号分配,否则外部改变序号分配会导致不可预料错误。
使用导出序号方式调用的优势:GetProcAddress 可直接定位函数,而不是将指定名称与 DLL 导出表中的函数名进行比较。如果 DLL 具有许多导出函数,则相对于使用函数名,使用导出序号调用 GetProcAddress 的速度稍快一些,因为导出序号是 DLL 导出表的索引。
注意事项:
需确保函数的参数的正确性,避免超出在堆栈上分配的内存和导致访问冲突,原因是DLL 函数在外部被以函数指针的形式调用,编译时没有类型检查。帮助提供类型安全的一种方法是查看导出函数的函数原型,并创建函数指针的匹配 typedef。