动态链接提供了一种方法,使进程可以调用不属于其可执行代码的函数。函数的可执行代码位于一个 DLL 中,该 DLL 包含一个或多个已被编译、链接并与使用它们的进程分开存储的函数。
1. DLL入口点
在创建 DLL 时,可以有选择地指定入口点函数。当进程或线程将它们自身附加到 DLL 或者将它们自身从 DLL 分离时,将调用入口点函数。您可以使用入口点函数根据 DLL 的需要来初始化数据结构或者销毁数据结构。此外,如果应用程序是多线程的,则可以在入口点函数中使用线程本地存储 (TLS) 来分配各个线程专用的内存。
2. 导出 DLL 函数
要导出 DLL 函数,您可以向导出的 DLL 函数中添加函数关键字,也可以创建模块定义文件 (.def) 以列出导出的 DLL 函数。
l 方法一、向导出的 DLL 函数中添加函数关键字
要使用函数关键字,您必须使用以下关键字来声明要导出的各个函数: __declspec(dllexport)
要在应用程序中使用导出的 DLL 函数,您必须使用以下关键字来声明要导入的各个函数:__declspec(dllimport)
通常情况下,您最好使用一个包含 define 语句和 ifdef 语句的头文件,以便分隔导出语句和导入语句。
l 创建模块定义文件 (.def) 以列出导出的 DLL 函数
使用模块定义文件来声明导出的 DLL 函数。当您使用模块定义文件时,您不必向导出的 DLL 函数中添加函数关键字。在模块定义文件中,您可以声明 DLL 的 LIBRARY 语句和 EXPORTS 语句。
l 特别调用
关于特定情况下的调用,比如DLL函数中使用到了win32 API或者将C++生成的DLL供标准C语言使用,则需要注意以下一些情况:
如果使用到了win32 API,则应该使用调用方式为“__stdcall”。
在将C++生成的DLL供标准C语言使用,输出文件需要用“extern"C"”修饰,否则不能被标准C语言调用。如果使用“__stdcall”调用方式,可能产生C不识别的修饰名,所以设置导出函数时要采用.def文件形式,而不是__declspec(dllexport)形式。后者会进行修饰名转换,C语言无法识别函数。
3. VC动态链接库的分类
Visual C++支持三种DLL,它们分别是Non-MFC DLL(非MFC动态库)、MFC Regular DLL(MFC规则DLL)、MFCExtension DLL(MFC扩展DLL)。
非MFC动态库不采用MFC类库结构,其导出函数为标准的C接口,能被非MFC或MFC编写的应用程序所调用;MFC规则DLL 包含一个继承自CWinApp的类,但其无消息循环;MFC扩展DLL采用MFC的动态链接版本创建,它只能被用MFC类库所编写的应用程序所调用。
4. 共享MFC DLL的规则DLL的模块切换
共享MFC DLL(或MFC扩展DLL)的规则DLL涉及到HINSTANCE句柄问题,HINSTANCE句柄对于加载资源特别重要。EXE和DLL都有其自己的资源,而且这些资源的ID可能重复,应用程序需要通过资源模块的切换来找到正确的资源。
产生这个问题的根源在于应用程序与MFC规则DLL共享MFC DLL(或MFC扩展DLL)的程序总是默认使用EXE的资源,我们必须进行资源模块句柄的切换,其实现方法有三:
方法一 在DLL接口函数中使用:
void ShowDlg(void)
{
//方法1:在函数开始处变更,在函数结束时恢复
//将AFX_MANAGE_STATE(AfxGetStaticModuleState());作为接口函数的第一//条语句进行模块状态切换
AFX_MANAGE_STATE(AfxGetStaticModuleState());
CDialogdlg(IDD_DLL_DIALOG);//打开ID为2000的对话框
dlg.DoModal();
}
方法二 在DLL接口函数中使用:
AfxGetResourceHandle用于获取当前资源模块句柄,而AfxSetResourceHandle则用于设置程序目前要使用的资源模块句柄。
void ShowDlg(void)
{
HINSTANCE save_hInstance= AfxGetResourceHandle();//方法2的状态变更
AfxSetResourceHandle(theApp.m_hInstance);
CDialogdlg(IDD_DLL_DIALOG);//打开ID为2000的对话框
dlg.DoModal();
AfxSetResourceHandle(save_hInstance);//方法2的状态还原
}
通过AfxGetResourceHandle和AfxSetResourceHandle的合理变更,我们能够灵活地设置程序的资源模块句柄,而方法一则只能在DLL接口函数退出的时候才会恢复模块句柄。
方法三 由应用程序自身切换
资源模块的切换除了可以由DLL接口函数完成以外,由应用程序自身也能完成。
现在我们把DLL中的接口函数改为最简单的:
void CSharedDllCallDlg::OnCalldllButton()
{
//方法3:由应用程序本身进行状态切换
HINSTANCE exe_hInstance =GetModuleHandle(NULL); //获取EXE模块句柄
//或者HINSTANCE exe_hInstance = AfxGetResourceHandle();
HINSTANCE dll_hInstance =GetModuleHandle("SharedDll.dll"); //获取DLL模块句柄
AfxSetResourceHandle(dll_hInstance);//切换状态
ShowDlg(); //此时显示的是DLL的对话框
AfxSetResourceHandle(exe_hInstance);//恢复状态
//资源模块恢复后再调用ShowDlg
ShowDlg(); //此时显示的是EXE的对话框
}
方法三中的Win32函数GetModuleHandle可以根据DLL的文件名获取DLL的模块句柄。如果需要得到EXE模块的句柄,则应调用带有Null参数的GetModuleHandle。
方法三与方法二的不同在于方法三是在应用程序中利用AfxGetResourceHandle和AfxSetResourceHandle进行资源模块句柄切换的。
5. 加载方式
l 隐式加载
#include "..\ExtDialog.h"
#pragma comment( lib,"ExtDll.lib" )
为提供给用户隐式加载,DLL需要提供三个文件:(1)描述DLL中扩展类的头文件;(2)与动态链接库对应的.LIB文件;(3)动态链接库.DLL文件本身。
l 显示加载
HINSTANCE hInst;
hInst=LoadLibrary("Dll3.dll");
typedef int (/*_stdcall*/ *ADDPROC)(inta,int b);
//ADDPROCAdd=(ADDPROC)GetProcAddress(hInst,"?add@@YAHHH@Z");
ADDPROCAdd=(ADDPROC)GetProcAddress(hInst,MAKEINTRESOURCE(1));
if(!Add){
MessageBox("获取函数地址失败!");
return;
}
CString str;
str.Format("5+3=%d",Add(5,3));
FreeLibrary(hInst);