动态链接库是“运行时”模块,而类是“创建时”模块。在编写大的EXE程序时,我们要在每次修改后重新编译并测试。但,我们可以创建较小的DLL模块,并单独进行测试,客户程序可以在运行时很快的装载并连接这些DLL。
DLL的基础理论
我们知道,进程是在磁盘上以exe文件启动的正在运行的程序示例。一般来说,DLL是磁盘上的文件,它由全局数据、编译的函数及资源组成,并成为进程的一部分。
导入与导出
DLL包含一个导出函数表,这个表中同时包含了DLL中函数的地址。一个特殊的DLL既可以有导出函数,又有导入函数。
在DLL代码中,必需如此声明导出函数:
__declspec(dllexport) int FunctionName(int n);
而在客户程序,则应这样声明相应的导入函数:
__declspec(dllimport) int FunctionName(int n);
如果正在使用的是C,则编译程序会为FunctionName产生其他语言不能使用的根据类型、函数名和参数类型生产的修饰名,这些修饰名比较长,并在工程MAP文件中列出。如果要使用简易名FunctionName,必需如下声明:
Extern “C” __declspec(dllexport) int FunctionName(int n);
Extern “C” __declspec(dllimport) int FunctionName(int n);
仅有导出声明并不能使客户链接到DLL,客户的工程必需对链接程序知道导入库(lib)。
隐式链接及显式链接
Lib文件与隐式链接:在建立DLL时由链接程序产生,是添加到客户程序工程中的DLL的替代文件。该文件包含了每一个DLL的导出符号和序号(可选),但没有代码。建立静态链接的客户文件时,输入符号与lib文件中的导出符号相匹配,这些符号或序号进入exe文件。Lib文件名也包含了DLL文件名(但不是完整的路径名),将其存在exe文件内部,当加载开会文件是,windows查找并加载DLL,然后根据符号或序号将其进行动态链接。
显式链接:我们可以使用不需要导入文件的显式链接。我们可以在调用GetProcAddress时,通过调用Win32 的LoadLibary函数,以DLL的路径名作为参数,并返回HINSTANCE参数。GetProcAddress可将符号或序号转换成DLL的内部地址。
例如,我们有包含如下导出函数的DLL:
Extern “C” __declspec(dllexport) int FunctionName(int n);
可以如此让客户链接到函数:
Typedef int (SQRTPROC(int);
HINSTANCE hInstance;
SQRTPROC* pFunction;
VERIFY(hInstance = ::LoadLibary(“C://winnt/system32//mydll.dll”));
VERIFY(pFunction = (SQRTPROC*)::GetProcAddress(hInstance, “FuncitonName”));
Int i = (*pFunction)(90);//call the DLL Function
注意:如果使用隐式链接,则所有的DLL在客户文件加载是都被加载;但是隐式链接则可以灵活地在运行时决定是否加载DLL。
DLL的入口点:DllMain
当Windows加载DLL是,首先调用全局对象的构造函数,然后调用全局函数DllMain。
扩展DLL与常规DLL
扩展DLL:支持C++接口,能够导出整个类,客户能够建立这些类或由其派生的类对象。它动态链接到MFC可的DLL版本中的代码上,因而也要求客户程序动态连接到MFC库。扩展DLL非常小,简单的扩展DLL大小为10KB,能快速地加载。
常规DLL:可被任意Win32编程环境加载,但只能导出C样式函数,不能导出C++类、成员函数或重载函数。静态链接的常规DLL是自包含的,它包括所有需要的MFC代码的一个拷贝,典型的Release版本的静态链接常规DLL约144KB,而动态链接的将减小到约17KB。
创建只包含C++导出类的扩展DLL
1. 创建MFC AppWizard(dll)。运行AppWizard并选中MFC Extension DLL(using shared MFC DLL)选项。
2. 新建类并在类名的定义前加上AFX_EXT_CLASS如:
class AFX_EXT_CLASS CMyFrame : public CFrameWnd{//codes};
3. 编译DLL工程,并在工程目录Debug文件夹下将生产的DLL文件(My22a.lib)拷贝到系统目录(windows/system或winnt/system32)
4. 创建客户程序。一定要在AppWizard的第五步选定As A Shared DLL选项。
5. 将CMyFrame类的头文件MyFrame.h拷贝到客户工程文件夹下。
6. 将CMainFrame的基类改变成CMyFrame(将.h和.cpp文件中的CFrameWnd全部用CMyFrame代替。并在MainFrm.h中加包含CMyFrame的头文件。
7. 添加导入库。Project->settings…->setting for->All Configuration。在Link选项卡下的Object/Libary modules填入:
D:/Visual Workspace/Inside VC/My22a/Debug/My22a.lib
最后,编译并运行程序。
!!!!连接过程中出现了如下错误:
Linking...
LINK : fatal error LNK1104: cannot open file "D:/Visual.obj"
Error executing link.exe.
这是为什么呢?
(1) 把My22a.lib文件拷到工程目录下,可以顺利通过链接。
(2) 在settings里边配置多个文件时,首先在link的Category下拉列表框中选择Input,然后在Object/library modules下面填入库文件名xxx.lib,多个文件以空格隔开。其次,在Additional library path下面填入库文件所在的目录路径,多个路径间以分号隔开。
(3) Clear。重新编译。
继续:
8. 建立一个正规DLL
新建DLL工程,选责Regular DLL Using Shared MFC。
在自动生成的App文件中添加导出的函数:
extern "C" __declspec(dllexport) double SquareRoot(double d)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
if (d >= 0.0)
{
return sqrt(d);
}
AfxMessageBox("Cannot take square root of a negative number.");
return 0.0;
}
编译,并把生产的DLL文件拷到系统文件夹下。
9. 修改4-7步建立的客户程序,测试第8步生成的DLL
添加对话框资源和类CTestMy22cDlg
数据成员及消息处理函数:
IDC_INPUT m_dInput double
IDC_OUTPUT m_dOutput double
IDC_OUTPUT OnCompute
void CTestMy22cDlg::OnCompute()
{
// TODO: Add your control notification handler code here
UpdateData(TRUE);
m_dOutput = SquareRoot(m_dInput);
UpdateData(FALSE);
}
必需声明SquareRoot函数为导入函数:添加下列代码到TestMy22cDlg.h
extern "C" __declspec(dllimport) double SquareRoot(double d);
将CTestMy22cDlg类集成到客户程序中:在客户程序添加相应的菜单及菜单消息处理函数
最后导入库:第8步产生的lib文件(多个库文件条目之间用空格隔开)。