首先我们知道有几种VC可以创建的DLL:
第一种 非MFC的DLL,这是通过DLL形式的win32 project来创建的,这种DLL的入口函数形如:
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
这种DLL可以被MFC或者非MFC的程序使用,可以认为是系统级的win32 DLL。当然可以在这种DLL加入MFC的头文件在内部使用MFC的类。
第二种 MFC规则DLL,通过MFC DLL模板里选中regular DLL来创建,这种DLL会定义一个派生自CWinApp的类,由这个类的InitInstance()完成DLL的初始化,内部提供DllMain函数,比如:
CCalcModuleRgApp theApp; // CCalcModuleRgApp initialization BOOL CCalcModuleRgApp::InitInstance() { CWinApp::InitInstance(); return TRUE; }
这种DLL可以是动态链接也可以是静态链接到主程序,其输出函数可以被所有win32的程序使用。
第三种 MFC扩展DLL,通过MFC DLL模板里选中extension DLL来创建,主要用于输出可以被MFC程序可以使用的类,它没有一个从CWinApp继承的类,入口函数形如:
extern "C" int APIENTRY DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) { // Remove this if you use lpReserved UNREFERENCED_PARAMETER(lpReserved); if (dwReason == DLL_PROCESS_ATTACH) { TRACE0("CalcModule.DLL Initializing!\n"); // Extension DLL one-time initialization if (!AfxInitExtensionModule(CalcModuleDLL, hInstance)) return 0; new CDynLinkLibrary(CalcModuleDLL); } else if (dwReason == DLL_PROCESS_DETACH) { TRACE0("CalcModule.DLL Terminating!\n"); // Terminate the library before destructors are called AfxTermExtensionModule(CalcModuleDLL); } return 1; // ok }
这种DLL只被用MFC类库编写的程序调用,应用程序必须有一个从CWinApp派生的类。我们知道在使用常规DLL的资源时,必须使用 AFX_MANAGE_STATE(AfxGetStaticModuleState( )) 来切换资源句柄到DLL模块,但是这个调用在扩展DLL是不能使用的,否则编译器会提示
error LNK2005: _DllMain@12 already defined in dllmain.obj
扩展dll在DllMain初始化时会将资源链入主程序,一般不会出现找不到资源的情况。
回到正题,我们的plugin DLL要求是能够被动态装载的,通过检查DLL是否输出预定义格式的函数来检查DLL是否是我们的plugin,下面使用规则DLL来实现一个简单的例子:
HWND hWndCaller=NULL; CCalcEventLog* pCalcEventLogCaller=NULL; extern "C" __declspec(dllexport) void QueryModule(__out LPTSTR szDescription,__in int nMaxCount) { TCHAR szModuleDesc[]=_T("算法1"); int n=_tcslen(szModuleDesc); n=n>nMaxCount-1?nMaxCount-1:n; _tcsncpy(szDescription,szModuleDesc,n); szDescription[n]='\0'; } extern "C" __declspec(dllexport) void ConfigModule(void) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); CDllDialog dllDialog; dllDialog.DoModal(); } extern "C" __declspec(dllexport) void InitModule(HWND hWnd,CCalcEventLog* pCalcEventLog) { hWndCaller=hWnd; pCalcEventLogCaller=pCalcEventLog; } extern "C" __declspec(dllexport) void DoCalc() { CString strMsg; for(int i=1;i<100;i++) { strMsg.Format(_T("%d"),i); if(pCalcEventLogCaller!=NULL) pCalcEventLogCaller->LogMessge(strMsg); Sleep(100); } }
在主程序中这样来调用:
HINSTANCE hDll = AfxLoadLibrary(_T("CalcModuleRg.dll")); if(NULL == hDll) { AfxMessageBox(_T("DLL动态加载失败")); return; } //查找querymodule函数得到算法描述 QueryModule QueryModuleFunc=(QueryModule) GetProcAddress(hDll,"QueryModule"); if(QueryModuleFunc!=NULL) { TCHAR szModuleDesc[255]; QueryModuleFunc(szModuleDesc,255); AfxMessageBox(szModuleDesc); //调用配置函数 ConfigModule ConfigModuleFunc=(ConfigModule) GetProcAddress(hDll,"ConfigModule"); if(ConfigModuleFunc!=NULL) ConfigModuleFunc(); //初始化算法模块 CMyCalcEventLog myCalcEventLog; InitModule InitModuleFunc=(InitModule) GetProcAddress(hDll,"InitModule"); if(InitModuleFunc!=NULL) InitModuleFunc(NULL,&myCalcEventLog); //调用计算方法 DoCalc DoCalcFunc=(DoCalc) GetProcAddress(hDll,"DoCalc"); if(DoCalcFunc!=NULL) DoCalcFunc(); } AfxFreeLibrary(hDll);
那么使用MFC扩展DLL是否也可以呢?首先我们在扩展DLL中输出一个函数来返回我们运算类的一个对象指针:
extern "C" BOOL AFX_EXT_API GetCalcObj(void** calcObj) { *calcObj=new MyCalcObj(); return TRUE; }
在主程序中我们试图动态装载这个扩展DLL来获取这个MyCalcObj对象:
typedef BOOL (*GETCALCOBJ)(void**); void LoadDllAndRunExt() { HINSTANCE hDll = AfxLoadLibrary(_T("CalcModule.dll"));//其实已经在装载EXE时调入 if(NULL == hDll) { AfxMessageBox(_T("MFC扩展DLL动态加载失败")); return; } MyCalcObj* pCalcObj=NULL; GETCALCOBJ GetCalcObjFunc=(GETCALCOBJ) GetProcAddress(hDll,"GetCalcObj"); if(GetCalcObjFunc!=NULL) { GetCalcObjFunc((void**) &pCalcObj); if(pCalcObj!=NULL) { pCalcObj->Do(); delete pCalcObj; } } AfxFreeLibrary(hDll); }
这样程序是能正常运行的,但是如果我们把这个扩展DLL从EXE的运行目录中移除,程序在启动时就会提示找不到动态链接库,而移除掉规则DLL主程序EXE仍然是能够正常启动的,这意味着扩展DLL是被exe启动时隐式加载的,这和我们要求plugin DLL能够被动态检测到是不一致的。实际上如果我们删除掉扩展DLL的lib文件,主程序是不能编译通过的,想想其实原因很简单,链接程序是要去搜索pCalcObj->Do()这样函数的地址的,既然不是通过GeProdAddress来动态获得的,编译器就必须在链接时通过lib来查找了,这就是为什么扩展DLL会被隐式加载的原因。