標 題:dll輸出類使用研究手記
發信人:
softworm
時 間:2003/08/04 09:54pm
詳細資訊:
貼一篇我以前寫的文章,改頭換面在雜誌上登過,感覺還有點意思。在寫乙個程式時,我想使用乙個共享軟體中的c++類。該類名為crypt,封裝在乙個dll中,檔名為crypt.dll。通過softice和ida pro,我已基本弄清了其成員函式的用法。現在的問題是,沒有相應的.h檔案及.lib檔案(當然更沒有原始碼)。另外,其成員函式顯然不能以getprocaddress取得位址後直接呼叫。
該軟體是用borland c++寫的。
先用borland c++提供的工具獲取必要的檔案
c:/bc5/bin/impdef crypt.def crypt.dll//得到crypt.def檔案
c:/bc5/bin/implib crypt.lib crypt.dll//得到crypt.lib檔案
@crypt@$bctr$qpxc@1; crypt::crypt(const char*)
@crypt@$bctr$qpxuci@2; crypt::crypt(const unsigned char*,int)
@crypt@decodefrom$qpuct1l @3; crypt::decodefrom(unsigned char*,unsigned
char*,long)
@crypt@encodeto$qpuct1l @4; crypt::encodeto(unsigned char*,unsigned
char*,long)
從分號後的注釋,可以得到demangled後的成員函式原型,但是沒有類的定義,我們不知道這個類包含什麼資料成員(以及別的未exported的成員函式,這一點不重要,因為原來的程式設計者在使用這個封裝在dll中的類時,也只能使用exported的函式)。如何構造乙個正確的標頭檔案?先來看看原來的**是如何使用這個類的。以下為ida pro的輸出:
00453e3e 0f0push 8
00453e40 0f4lea eax, [ebp+var_8]
00453e43 0f4push eax
00453e44 0f8lea ecx, [ebp+var_e0]
00453e4a 0f8push ecx
00453e4b 0fccall crypt::crypt(uchar *,int)
00453e50 0fcadd esp, 0ch
這段**呼叫crypt::crypt(uchar *,int)成員函式,使用__cdecl呼叫規則,由呼叫者維護堆疊。函式有2個引數,向堆疊中壓入了3個值,最後乙個push入棧的是指向當前crypt物件的this指標,即變數var_e0就是在棧上分配的crypt類物件。從ida pro中可看到,var_e0覆蓋了從ffffff20到ffffff80共96位元組的空間。我們知道,c++類的成員函式、靜態資料成員是不放在物件內的,物件只含有資料成員(若類中或其基類中定義有虛函式,還包含vptr)。也就是說,crypt類的所有資料成員共佔據96位元組。具體細節請參照stanley lippman的《深度探索c++物件模型》。
由此,我們可以自己定義crypt類的資料成員(使用位元組陣列),使其佔據同樣的記憶體空間,與原來的類在記憶體布局上一致即可。實際上,只要我們給出的類定義保證能分配足夠的記憶體空間,原來的建構函式就可以在分配的記憶體中建立出正確的物件。這種方法與com的思想有相似之處,都是在二進位制的級別上保證記憶體布局的相容。我寫的標頭檔案如下:
class _import crypt
; 將此標頭檔案及前面的crypt.lib檔案加入專案,證明此方法是可行的。測試**如下:
將原來的dll改名為olddll.dll。源**如下:
標頭檔案crypt.h:
#ifdef crypt_exports
#define crypt_api __declspec(dllexport)
#else
#define crypt_api __declspec(dllimport)
#endif
class crypt_api crypt ;
實現檔案crypt.cpp:
#include "stdafx.h"
#include "crypt.h"
static hinstance holddll=null;
static dword dwret;
static dword dwretaddr;
static farproc lpcrypt1;//帶1個引數的建構函式
static farproc lpcrypt2;//帶2個引數的建構函式
static farproc lpencodeto;
static farproc lpdecodefrom;
bool apientry dllmain( handle hmodule, dword ul_reason_for_call, lpvoid lpreserved )
break;
case dll_thread_attach:
break;
case dll_thread_detach:
break;
case dll_process_detach:
if(holddll)
break;
}return bret;
} __declspec(naked) crypt::crypt(const char *lpszpassword)
} __declspec(naked) crypt::crypt(const unsigned char* lpszpassword,int cbbuffer)
} void __declspec(naked) __cdecl crypt::encodeto(unsigned char* lpsource,
unsigned char* lpdestination,int nsize)
void __declspec(naked) __cdecl crypt::decodefrom(unsigned char* lpsource,
unsigned char* lpdestination,int nsize)
有幾處需要注意:首先,這裡使用了naked呼叫規則(borland c++不支援),以便於直接操作堆疊及用內嵌的組合語言程式設計。另外,雖然我們的類中並沒有包含虛函式或物件成員,vc++編譯器卻仍生成了member-wise的拷貝建構函式和bit-wise賦值運算子,並導致原來的dll中的建構函式不能正確建立物件。為了禁止編譯器自動生成不必要的**,在標頭檔案中定義了賦值運算子和拷貝建構函式,但並未提供實現。兩個建構函式有點特殊,我發現無論指定何種呼叫規則,生成的**總是使用thiscall呼叫規則,即在ecx暫存器中傳遞this指標,為此建構函式需要特殊處理,用彙編**手工模仿__cdecl呼叫規則去呼叫原來dll中的函式,包括維護棧指標。
其餘的**已作了注釋,易於理解,不再贅述。
動態載入類(動態載入DLL檔案) zz
本人剛剛開始編寫程式不久,開發中發現一非常好的方法。大家共享。我們在編寫程式的時候經常會遇到這樣的情況 程式中要用到某種計算,而且這種計算的計算方式很多,我們不得不在編寫程式時就要考慮的十分全面,將各種情況到考慮到。但是這樣做又非常的費力,因為我們無法 到程式編好後,還會出現什麼樣的計算方式。如果計...
怎麼在dll中新增類,和使用dll中的類
dll.h ifdef dll hiddevice class declspec dllexport chiddevice 匯出類 else class declspec dllimport chiddevice 匯入類po endif main.cpp include dll.h pragma c...
EXE中使用DLL的模板類
模板類是乙個編譯鏈結期間才例項化的類。只有用到才例項化。標準沒有支援對模板類的匯出,從另外一種意義上來說,模板類的實現全部放在標頭檔案中,也就不需要匯出了。但是對於一些特別情況。模板類中有靜態變數和函式。這個時候dll中使用的,以及和其他鏈結這個dll的模組他們是使用的兩份拷貝。比如,在dll中這樣...