天天看點

MFC的dll中控制資源問題

有程式EXE和DLL,其中DLL中有1個函數用來顯示對話框,被EXE調用。

當EXE和DLL都為Release或Debug時,沒有任何問題,但EXE為Release、

DLL為Debug時,就會出錯。該DLL是用VC提供的MFC Extension DLL,

函數的代碼如下:

HINSTANCE hInstOld = AfxGetResourceHandle(); // 該句出錯

HINSTANCE hInstNew = GetModuleHandle("T01Test.dll");

AfxSetResourceHandle(hInstNew);

CHahaDlg dlg;

dlg.DoModal();

AfxSetResourceHandle(hInstOld);

長久以來,把界面的資訊單獨存為一個DLL一直是很多商業軟體的作法,比如VC、InstallShield等等,這樣做的好處是,如果要做多語言版本,隻要寫出不同的DLL來,在主程式中使用時調用不同的DLL就行,當然現在還有一種流行的方法是使用INI,讀存也非常友善。最近在網上轉了轉,發現竟沒有一篇關于如何讀取DLL中資源的文章,雖然Iczelion的Win32ASM教程中第26課"Splash Screen"講到了讀取DLL中的圖檔,但不知是這種問題太簡單了還是其它什麼原因,Iczelion沒有講解這段代碼的意思,于是乎決定寫一篇關于DLL資源讀取的文章,望大家不要$%@%@^%@%。

我們看一下這些函數:

HBITMAP LoadBitmap(HINSTANCE hInstance,LPCTSTR lpBitmapName)

HICON LoadIcon(HINSTANCE hInstance,LPCTSTR lpIconName)

HMENU LoadMenu(HINSTANCE hInstance,LPCTSTR lpMenuName)

int LoadString(HINSTANCE hInstance,UINT uID,LPTSTR lpBuffer,int BufferMax)

int DialogBoxParam(

HINSTANCE hInstance, // handle to application instance

LPCTSTR lpTemplateName, // identifies dialog box template

HWND hWndParent, // handle to owner window

DLGPROC lpDialogFunc, // pointer to dialog box procedure 

LPARAM dwInitParam // initialization value

);

HWND CreateDialogParam(

這些都是常用的讀取資源的函數,它們都有一個共同點:第一個參數需要的是要讀取的包含資源的程式的子產品句柄,那麼,關鍵就在這個句柄,因為我們在讀取本身程式資源的時候,肯定是提供用GetModuleHandle函數獲得的句柄,這個句柄就是目前程式的執行個體句柄,如果要讀取DLL中的資源,很顯然的,我們需要提供DLL的句柄,那麼這個DLL句柄怎麼得到呢?很簡單,我們在使用LoadLibrary函數時,傳回的值就是讀取的DLL的句柄,于是,我們讀取DLL中的資源,隻需要這樣:

invoke LoadLibrary,DLL_FILENAME

mov DLL_HANDLE,eax

invoke LoadBitmap,DLL_HANDLE,BITMAP_ID

invoke LoadIcon,DLL_HANDLE,ICON_ID

invoke LoadMenu,DLL_HANDLE,MENU_ID

invoke LoadString,DLL_HANDLE,STRING_ID,StrBuffer,sizeof StrBuffer

invoke DialogBoxParam,DLL_HANDLE,DLG_NAME,hParent,DlgProc,lParam

其它的函數就不多說,着重講一下DialogBoxParam與CreateDialogParam,因為其它函數不需要回調函數,讀取之後句柄可以一直到程式結束才釋放。我們讨論的就是DialogBoxParam與CreateDialogParam回調函數的方法。

我曾上過當,把DialogBoxParam與CreateDialogParam的回調函數寫在主程式中,相信有很多的朋友也是寫在主程式中,然後直接把回調過程位址傳給DialogBoxParam與CreateDialogParam,其實,這是一種錯誤的方法,正确的方法是,我們必須把回調函數寫在對話框資源本身的DLL中,在主程式用DialogBoxParam與CreateDialogParam顯示對話框時提供DLL中的回調函數位址,當然,對純提供資源的DLL,它們不同的隻是界面語言文字,這個把回調函數寫在主程式中更加好,如果是插件呢?如果主程式使用了很多的DLL呢?對于插件而言,回調函數是必須在DLL中的,主程式使用很多DLL時,把回調函數都寫在主程式中,就算能正常運作,但是DLL有變動,就算是一個小修改,也不得不重新更改主程式,是以,我的建議是:除了純資源DLL,編寫DLL時,對話框的回調函數一定要寫在DLL本身中。

可是,如果在主程式中就這樣子使用DLL對話框,那麼,DLL對話框的回調函數就必須引出,這樣主程式才能獲得回調函數位址,就像這樣:

invoke GetProcAddress,DLL_HANDLE,DlgProcName

invoke DialogBoxParam,DLL_HANDLE,DLG_NAME,hWnd,eax,NULL

;DlgProcName就是DLL中引出的回調函數

這段代碼看起來非常簡潔,也完全能正常工作,可是想一想,如果在程式其它的地方要不停的使用DLL中的對話框,不僅上述工作很煩人,更煩的是,我們必須把所有的回調函數全部引出,其實我們完全可以這樣做:

在DLL中編寫一個函數LoadDialog,如下:

LoadDialog proc hInstance,hWnd,ID

.if ID==100

mov eax,offset DlgProc0

.elseif ID==101

mov eax,offset DlgProc1

.elseif ID==102

mov eax,offset DlgProc2

.elseif ID==103

mov eax,offset DlgProc3

.end if

invoke DialogBoxParam,hInstance,ID,hWnd,eax,NULL

ret

LoadDialog endp

;DlgProc0、DlgProc1、DlgProc2、DlgProc3都是DLL中的回調函數

那麼,我們在主程式中調用時就隻需這樣:

invoke GetProcAddress,DLL_HANDLE,DlgProcName ;DlgProcName="LoadDialog"

mov LoadDialog,eax

push ID

push hWnd

push DLL_HANDLE

call [LoadDialog]

隻需在程式開頭擷取到LoadDialog的位址後,在任何地方調用不同的對話框隻需要提供不同的ID即可,就像這樣:

push 101

這樣做,不僅DLL中的回調函數不需要引出,在主程式中使用時也比每次讀回調函數位址友善得多

繼續閱讀