長久以來,把界面的信息單獨存為一個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(
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
);
這些都是常用的讀取資源的函數,它們都有一個共同點:第一個參數需要的是要讀取的包含資源的程序的模塊句柄,那麼,關鍵就在這個句柄,因為我們在讀取本身程序資源的時候,肯定是提供用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
push hWnd
push DLL_HANDLE
call [LoadDialog]
這樣做,不僅DLL中的回調函數不需要引出,在主程序中使用時也比每次讀回調函數地址方便得多。