C中DLL函数的导出与导入

合集下载
相关主题
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

1.使用DEF 文件从DLL 导出
模块定义(.def) 文件是包含一个或多个描述DLL 各种属性的Module 语句的文本文件。

如果不使用__declspec(dllexport)关键字导出DLL 的函数,则DLL 需要 .def 文件。

.def 文件必须至少包含下列模块定义语句:
文件中的第一个语句必须是LIBRARY 语句。

此语句将 .def 文件标识为属于DLL。

LIBRARY 语句的后面是DLL 的名称。

链接器将此名称放到DLL 的导入库中。

EXPORTS 语句列出名称,可能的话还会列出DLL 导出函数的序号值。

通过在函数名的后面加上@ 符和一个数字,给函数分配序号值。

当指定序号值时,序号值的范围必须是从1 到N,其中N 是DLL 导出函数的个数。

如果希望按序号导出函数,请参见按序号而不是按名称从DLL 导出函数以及本主题。

例如,包含实现二进制搜索树的代码的DLL 看上去可能像下面这样:
LIBRARY BTREE
EXPORTS
Insert @1
Delete @2
Member @3
Min @4
如果使用MFC DLL 向导创建MFC DLL,则向导将为您创建主干 .def 文件并将其自动添加到项目中。

添加要导出到此文件的函数名。

对于非MFC DLL,必须亲自创建 .def 文件并将其添加到项目中。

如果导出C++ 文件中的函数,必须将修饰名放到 .def 文件中,或者通过使用外部“C”定义具有标准C 链接的导出函数。

如果需要将修饰名放到 .def
文件中,则可以通过使用DUMPBIN 工具或/MAP 链接器选项来获取修饰名。

请注意,编译器产生的修饰名是编译器特定的。

如果将Visual C++ 编译器产生的修饰名放到 .def 文件中,则链接到DLL 的应用程序必须也是用相同版本的Visual C++ 生成的,这样调用应用程序中的修饰名才能与DLL 的 .def 文件中的导出名相匹配。

如果生成扩展DLL 并使用 .def 文件导出,则将下列代码放在包含导出类的头文件的开头和结尾:
#undef AFX_DATA
#define AFX_DATA AFX_EXT_DATA
// <body of your header file>
#undef AFX_DATA
#define AFX_DATA
这些代码行确保内部使用的MFC 变量或添加到类的变量是从扩展DLL 导出(或导入)的。

例如,当使用DECLARE_DYNAMIC派生类时,该宏扩展以将CRuntimeClass成员变量添加到类。

省去这四行代码可能会导致不能正确编译或链接DLL,或在客户端应用程序链接到DLL 时导致错误。

当生成DLL 时,链接器使用 .def 文件创建导出(.exp) 文件和导入库(.lib) 文件。

然后,链接器使用导出文件生成DLL 文件。

隐式链接到DLL 的可执行文件在生成时链接到导入库。

请注意,MFC 本身使用 .def 文件从MFCx0.dll 导出函数和类。

2.使用_declspec(dllexport) 从DLL 导出
Microsoft 在Visual C++ 的16 位编译器版本中引入了_export,使编译器得以自动生成导出名并将它们放到一个 .lib 文件中。

然后,此 .lib 文件就可以像静态 .lib 那样用于与DLL 链接。

在更新的编译器版本中,可以使用_declspec(dllexport)关键字从DLL 导出数据、函数、类或类成员函数。

_declspec(dllexport)会将导出指令添加到对象文件中,因此您不需要使用 .def 文件。

当试图导出C++ 修饰函数名时,这种便利最明显。

由于对名称修饰没有标准规范,因此导出函数的名称在不同的编译器版本中可能有所变化。

如果使用_declspec(dllexport),仅当解决任何命名约定更改时才必须重新编译DLL 和依赖 .exe 文件。

许多导出指令(如序号、NONAME 和PRIVATE)只能在 .def 文件中创建,并且必须使用 .def 文件来指定这些属性。

不过,在 .def 文件的基础上另外使用_declspec(dllexport)不会导致生成错误。

若要导出函数,_declspec(dllexport)关键字必须出现在调用约定关键字的左边(如果指定了关键字)。

例如:
_declspec(dllexport) void _cdecl Function1(void);
若要导出类中的所有公共数据成员和成员函数,关键字必须出现在类名的左边,如下所示:
class _declspec(dllexport) CExampleExport : public CObject
{ ... class definition ... };
生成DLL 时,通常创建一个包含正在导出的函数原型和/或类的头文件,并将_declspec(dllexport)添加到头文件中的声明中。

若要提高代码的可读性,请为_declspec(dllexport)定义一个宏并对正在导出的每个符号使用该宏:
#define DllExport _declspec( dllexport )
_declspec(dllexport)将函数名存储在DLL 的导出表中。

如果希望优化表的大小,请参见按序号而不是按名称从DLL 导出函数。

注意:将DLL 源代码从Win16 移植到Win32 时,请用
_declspec(dllexport)替换_export的每个实例。

作为参考,请在Win32 Winbase.h 头文件中搜索。

它包含
_declspec(dllimport)的用法示例。

3.使用AFX_EXT_CLASS 导出和导入
扩展DLL 使用AFX_EXT_CLASS宏导出类;链接到扩展DLL 的可执行文件使用该宏导入类。

用于生成扩展DLL 的相同头文件可通过
AFX_EXT_CLASS宏与链接到DLL 的可执行文件一起使用。

在DLL 的头文件中,将AFX_EXT_CLASS关键字添加到类的声明中,如下所示:
class AFX_EXT_CLASS CMyClass : public CDocument
{
// <body of class>
};
当定义了预处理器符号_AFXDLL和_AFXEXT时,该宏被MFC 定义为__declspec(dllexport)。

但当定义了_AFXDLL而未定义_AFXEXT时,该宏被定义为__declspec(dllimport)。

定义后,预处理器符号_AFXDLL指示共享MFC 版本正在由目标可执行文件(DLL 或应用程序)使用。

当_AFXDLL 和_AFXEXT都定义了时,这指示目标可执行文件是扩展DLL。

由于从扩展DLL 导出时,AFX_EXT_CLASS被定义为
__declspec(dllexport),因此可以导出整个类,而不必将该类的所有符号的修饰名放到 .def 文件中。

此方法由MFC 示例DLLHUSK使用。

虽然使用此方法可以避免创建 .def 文件和类的所有修饰名,但由于名称可以按序号导出,因此创建 .def 文件的效率更高。

若要使用 .def 文件导出方法,请将下列代码放在头文件的开头和结尾处:
#undef AFX_DATA
#define AFX_DATA AFX_EXT_DATA
// <body of your header file>
#undef AFX_DATA
#define AFX_DATA
警告:导出内联函数时要小心,因为它们有可能导致版本冲突。

内联函数扩展到应用程序代码中;因此,如果以后重写内联函数,除非重新编译应用程序本身,否则内联函数不会被更新。

通常,不用重新生成使用DLL 函数的应用程序就可以更新DLL 函数。

4.导出类中的个别成员
有时,您可能希望导出类中的个别成员。

例如,如果导出CDialog派生类,可能只需要导出构造函数和DoModal调用。

可以对需要导出的个别成员使用AFX_EXT_CLASS。

例如:
class CExampleDialog : public CDialog
{
public:
AFX_EXT_CLASS CExampleDialog();
AFX_EXT_CLASS int DoModal();
...
// rest of class definition
...
};
您不再导出类的所有成员,但由于MFC 宏的工作方式,您可能会遇到其他问题。

几个MFC 的Helper 宏实际声明或定义数据成员。

因此,还必须从DLL 导出这些数据成员。

例如,当生成扩展DLL 时,DECLARE_DYNAMIC宏的定义如下:
#define DECLARE_DYNAMIC(class_name)
protected:
static CRuntimeClass* PASCAL _GetBaseClass();
public:
static AFX_DATA CRuntimeClass class##class_name;
virtual CRuntimeClass* GetRuntimeClass() const;
以static AFX_DATA打头的行声明类的内部静态对象。

若要正确导出该类并从客户端可执行文件访问运行时信息,必须导出此静态对象。

由于静态对象是用AFX_DATA修饰符声明的,因此只需在生成DLL 时将AFX_DATA定义为__declspec(dllexport),并在生成客户端可执行文件时将AFX_DATA 定义为__declspec(dllimport)。

由于已经以此方式定义了
AFX_EXT_CLASS,因此只需参考类定义,将AFX_DATA重定义为与
AFX_EXT_CLASS相同。

例如:
#undef AFX_DATA
#define AFX_DATA AFX_EXT_CLASS
class CExampleView : public CView
{
DECLARE_DYNAMIC()
// ... class definition ...
};
#undef AFX_DATA
#define AFX_DATA
MFC 总是在其宏的内部定义的数据项上使用AFX_DATA符号,因此此技术适用于所有这类情况。

例如,它适用于DECLARE_MESSAGE_MAP。

注意:如果导出整个类而非选定的类成员,静态数据成员将自动导出。

5.使用 .DEF 文件的优缺点
在 .def 文件中导出函数使您得以控制导出序号。

当将附加的导出函数添加到DLL 时,可以给它们分配更高的序号值(高于任何其他导出函数)。

当您进行此操作时,使用隐式链接的应用程序不必与包含新函数的新导入库重新链接。

这非常重要,例如,在设计将由许多应用程序使用的第三方DLL 时。

可以通过添加附加功能不断地增强DLL,同时确保现有应用程序继续正常使用新的DLL。

MFC DLL 是使用 .def 文件生成的。

使用 .def 文件的另一个优点是:可以使用NONAME 属性导出函数,该属性仅将序号放到DLL 的导出表中。

对具有大量导出函数的DLL,使用NONAME 属性可以减小DLL 文件的大小。

有关编写模块定义语句的信息,请参见模块定义语句的规则。

有关序号导出的更多信息,请参见按序号而不是按名称从DLL 导出函数。

使用 .def 文件的主要缺点是:在C++ 文件中导出函数时,必须将修饰名放到 .def 文件中,或者通过使用外部“C”用标准C 链接定义导出函数,以避免编译器进行名称修饰。

如果需要将修饰名放到 .def 文件中,则可以通过使用DUMPBIN 工具或/MAP 链接器选项来获取修饰名。

请注意,编译器产生的修饰名是编译器特定的。

如果将Visual C++ 编译器产生的修饰名放到 .def 文件中,则链接到DLL
的应用程序必须也是用相同版本的Visual C++ 生成的,这样调用应用程序中的修饰名才能与DLL 的 .def 文件中的导出名相匹配。

6.使用__declspec(dllexport) 的优缺点
使用__declspec(dllexport)非常方便,因为不必考虑维护 .def 文件和获取导出函数的修饰名。

例如,如果您设计的DLL 供自己控制的应用程序使用,则此方法很适用。

如果通过新的导出函数重新生成DLL,还必须重新生成应用程序,因为如果使用不同版本的编译器进行重新编译,则导出的C++ 函数的修饰名可能会发生变化。

相关文档
最新文档