DLL輸出類使用研究手記 ZZ

2021-04-01 23:17:04 字數 3484 閱讀 2378

標 題: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中這樣...