結(jié)果如何呢?我的VC++測試用例還是不能調(diào)用該接口的接口方法,只是這次的報錯方式有所改變,提示是每個C/C++程序員最不愿意看到的“內(nèi)存地址訪問違規(guī)”,這一次我確實(shí)被郁悶了,這是為什么呢?
五、gcc和VC++對象模型的差異分析:
在VC++中,C++對象(含有虛函數(shù))在編譯后將生成屬于自己的對象模型,虛擬表vtable和虛擬指針vptr均被包含在該模型中(關(guān)于該問題,可以參考Stanley Lippman的《深度探索C++對象模型》)。而我們目前的設(shè)計方式恰恰是充分利用了vptr和vtable來定位每個接口函數(shù),不幸的是,VC++生成的C++對象的vptr在該對象模型的最開始處,即該對象模型的前4個字節(jié),而gcc則不是這樣存儲vptr的。當(dāng)我們通過vptr定位vtable,再通過vtable的slot定位接口函數(shù)時,也將無法得到我們期望的接口函數(shù)的入口地址,因此調(diào)用結(jié)果可想而知,而且還給人一種關(guān)公戰(zhàn)秦瓊的感覺。
六、傳統(tǒng)的設(shè)計思路和技巧:
一條路已經(jīng)走到了死胡同,唯一的辦法就是掉過頭來重新來過。這次我想到的設(shè)計思路非常簡單,也更加傳統(tǒng)。通過之前的失敗經(jīng)驗(yàn)總結(jié),盡管通過VC調(diào)用gcc的純虛接口是不能正常工作的,然而gcc導(dǎo)出的那兩個C函數(shù)卻是可以正常調(diào)用的,同時也得到了正確的調(diào)用結(jié)果。鑒于此,我將設(shè)計一個只是包含封裝函數(shù)(封裝libmemcached的導(dǎo)出函數(shù))的動態(tài)庫,見如下代碼:
1#define MCWRAPPER_CALL __attribute__((cdecl))
2
3 extern "C" {
4 void* MCWRAPPER_CALL wrapped_memcached_create();
5 void MCWRAPPER_CALL wrapped_memcached_free(void* p);
6 void MCWRAPPER_CALL wrapped_memcached_result_free(void* result);
7 const char* MCWRAPPER_CALL wrapped_memcached_strerror(void* p, int error);
8 int MCWRAPPER_CALL wrapped_memcached_behavior_set(void *p, const int flag, uint64 data);
9 int MCWRAPPER_CALL wrapped_memcached_behavior_set_distribution(void* p, int type);
10 int MCWRAPPER_CALL wrapped_memcached_server_add(void* p,const char* hostname, uint16 port);
11 uint32 MCWRAPPER_CALL wrapped_memcached_server_count(const void* p);
12 int MCWRAPPER_CALL wrapped_memcached_set(void* p,const char* key
13 ,size_t klength,const char* data,size_t dlength);
14 int MCWRAPPER_CALL wrapped_memcached_add(void* p,const char *key
15 ,size_t klength,const char* data,size_t dlength);
16 int MCWRAPPER_CALL wrapped_memcached_replace(void* p,const char* key
17 ,size_t klength,const char* data,size_t dlength);
18 int MCWRAPPER_CALL wrapped_memcached_append(void* p,const char* key
19 ,size_t klength,const char* data,size_t dlength);
20 char* MCWRAPPER_CALL wrapped_memcached_get(void* p,const char* key,size_t klength
21 ,size_t* dlength,uint32* flags,int* error);
22 int MCWRAPPER_CALL wrapped_memcached_mget(void* p,const char* const* keys
23 ,const size_t* keysLength,size_t numberOfkeys);
24 void* MCWRAPPER_CALL wrapped_memcached_fetch_result(void* p,void* result,int* error);
25 const char* MCWRAPPER_CALL wrapped_memcached_result_value(const void* self);
26 size_t MCWRAPPER_CALL wrapped_memcached_result_length(const void* self);
27 int MCWRAPPER_CALL wrapped_memcached_delete(void* p,const char* key,size_t klength);
28 int MCWRAPPER_CALL wrapped_memcached_exist(void* p,const char *key,size_t klength);
29 int MCWRAPPER_CALL wrapped_memcached_flush(void* p);
30 int MCWRAPPER_CALL wrapped_memcached_cas(void* p,const char* key,size_t klength
31 ,const char* data,size_t dlength,uint64 cas);
32 int MCWRAPPER_CALL wrapped_memcached_increment(void* p,const char* key
33 ,size_t klength,uint32 step,uint64* value);
34 int MCWRAPPER_CALL wrapped_memcached_decrement(void* p,const char* key
35 ,size_t klength,uint32 step,uint64* value);
36 int MCWRAPPER_CALL wrapped_memcached_increment_with_initial(void* p
37 ,const char* key,size_t klength,uint64 step,uint64 initial,uint64* value);
38 int MCWRAPPER_CALL wrapped_memcached_decrement_with_initial(void* p
39 ,const char* key,size_t klength,uint64 step,uint64 initial,uint64* value);
40 }
通過封裝函數(shù)的簽名可以看出,所有和libmemcached相關(guān)的結(jié)構(gòu)體指針在此均被定義為void*類型,這樣就可以規(guī)避上一篇中提到的結(jié)構(gòu)體由不同編譯器生成的字節(jié)對齊問題。最后,在封裝函數(shù)的內(nèi)部只需要將void*轉(zhuǎn)換回其封裝的libmemcached導(dǎo)出函數(shù)參數(shù)期望的結(jié)構(gòu)體指針類型即可,實(shí)現(xiàn)代碼如下:
1#include <MemcachedFunctionsWrapper.h>
2 #include <libmemcached/memcached.h>
3
4 void* MCWRAPPER_CALL wrapped_memcached_create()
5 {
6 return (void*)memcached_create(NULL);
7 }
8
9 void MCWRAPPER_CALL wrapped_memcached_free(void* p)
10 {
11 memcached_free((memcached_st*)p);
12 }
13
14 const char* MCWRAPPER_CALL wrapped_memcached_strerror(void* p, int error)
15 {
16 return memcached_strerror((memcached_st*)p,(memcached_return)error);
17 }
18
19 int MCWRAPPER_CALL wrapped_memcached_behavior_set(void *p, const int flag, uint64 data)
20 {
21 return memcached_behavior_set((memcached_st*)p,(memcached_behavior_t)flag,data);
22 }
23
24 int MCWRAPPER_CALL wrapped_memcached_behavior_set_distribution(void* p, int type)
25 {
26 return memcached_behavior_set_distribution((memcached_st*)p
27 ,(memcached_server_distribution_t)type);
28 }
29
30 int MCWRAPPER_CALL wrapped_memcached_server_add(void* p,const char* hostname, uint16 port)
31 {
32 return memcached_server_add((memcached_st*)p,hostname,port);
33 }
34
35 uint32 MCWRAPPER_CALL wrapped_memcached_server_count(const void* p)
36 {
37 return memcached_server_count((memcached_st*)p);
38 }
39
40 int MCWRAPPER_CALL wrapped_memcached_set(void* p,const char* key,size_t klength
41 ,const char* data,size_t dlength)
42 {
43 return memcached_set((memcached_st*)p,key,klength,data,dlength,0,0);
44 }
45
46 int MCWRAPPER_CALL wrapped_memcached_add(void* p,const char *key,size_t klength
47 ,const char* data,size_t dlength)
48 {
49 return memcached_add((memcached_st*)p,key,klength,data,dlength,0,0);
50 }
51
52 int MCWRAPPER_CALL wrapped_memcached_replace(void* p,const char* key,size_t klength
53 ,const char* data,size_t dlength)
54 {
55 return memcached_replace((memcached_st*)p,key,klength,data,dlength,0,0);
56 }
57
58 int MCWRAPPER_CALL wrapped_memcached_append(void* p,const char* key,size_t klength
59 ,const char* data,size_t dlength)
60 {
61 return memcached_append((memcached_st*)p,key,klength,data,dlength,0,0);
62 }
63
64 char* MCWRAPPER_CALL wrapped_memcached_get(void* p,const char* key,size_t klength
65 ,size_t* dlength,uint32* flags,int* error)
66 {
67 return memcached_get((memcached_st*)p,key,klength,dlength,flags,(memcached_return_t*)error);
68 }
69
70 int MCWRAPPER_CALL wrapped_memcached_mget(void* p,const char* const* keys
71 ,const size_t* keysLength,size_t numberOfkeys)
72 {
73 return memcached_mget((memcached_st*)p,keys,keysLength, numberOfkeys);
74
75 }
76
77 void* MCWRAPPER_CALL wrapped_memcached_fetch_result(void* p,void* result,int* error)
78 {
79 return (void*)memcached_fetch_result((memcached_st*)p,NULL,(memcached_return_t*)error);
80 }
81
82 const char* MCWRAPPER_CALL wrapped_memcached_result_value(const void* self)
83 {
84 return memcached_result_value((const memcached_result_st*)self);
85 }
86
87 size_t MCWRAPPER_CALL wrapped_memcached_result_length(const void* self)
88 {
89 return memcached_result_length((const memcached_result_st*)self);
90 }
91
92 int MCWRAPPER_CALL wrapped_memcached_delete(void* p,const char* key,size_t klength)
93 {
94 return memcached_delete((memcached_st*)p,key,klength,0);
95 }
96
97 int MCWRAPPER_CALL wrapped_memcached_exist(void* p,const char *key,size_t klength)
98 {
99 return memcached_exist((memcached_st*)p,key,klength);
100 }
101
102 int MCWRAPPER_CALL wrapped_memcached_flush(void* p)
103 {
104 return memcached_flush((memcached_st*)p,0);
105 }
106
107 int MCWRAPPER_CALL wrapped_memcached_cas(void* p,const char* key,size_t klength
108 ,const char* data,size_t dlength,uint64 cas)
109 {
110 return memcached_cas((memcached_st*)p,key,klength,data,dlength,0,0,cas);
111 }
112
113 int MCWRAPPER_CALL wrapped_memcached_increment(void* p,const char* key
114 ,size_t klength,uint32 step,uint64* value)
115 {
116 return memcached_increment((memcached_st*)p,key,klength,step,value);
117 }
118
119 int MCWRAPPER_CALL wrapped_memcached_decrement(void* p,const char* key
120 ,size_t klength,uint32 step,uint64* value)
121 {
122 return memcached_decrement((memcached_st*)p,key,klength,step,value);
123 }
124
125 int MCWRAPPER_CALL wrapped_memcached_increment_with_initial(void* p
126 ,const char* key,size_t klength,uint64 step,uint64 initial,uint64* value)
127 {
128 return memcached_increment_with_initial((memcached_st*)p,key,klength,step,initial,0,value);
129 }
130
131 void MCWRAPPER_CALL wrapped_memcached_result_free(void* result)
132 {
133 memcached_result_free((memcached_result_st*)result);
134 }
135
136 int MCWRAPPER_CALL wrapped_memcached_decrement_with_initial(void* p
137 ,const char* key,size_t klength,uint64 step,uint64 initial,uint64* value)
138 {
139 return memcached_decrement_with_initial((memcached_st*)p,key,klength,step,initial,0,value);
140 }
通過gcc在Mingw32的環(huán)境下編譯該代碼文件,并生成相應(yīng)的動態(tài)庫文件(MemcachedFunctionsWrapper.dll),需要說明的是該動態(tài)庫將靜態(tài)依賴之前生成的libmemcached-8.dll,這一點(diǎn)可以在Mingw32下通過ldd命令予以驗(yàn)證。
剩下需要做的是去除之前聲明的純虛接口,直接導(dǎo)出一個C++的封裝實(shí)現(xiàn)類,在該類的實(shí)現(xiàn)中,同樣利用Windows API中的LoadLibrary和GetProcAddress方法動態(tài)加載之前生成的libmemcached函數(shù)封裝動態(tài)庫(MemcachedFunctionsWrapper.dll)。該C++類的類聲明和上一篇中實(shí)現(xiàn)類的聲明完全一致,這里就不在重復(fù)給出了。測試用例的代碼也基本相同,只是不需要再通過C接口函數(shù)獲取該C++類的對象指針了,而是可以直接將該C++類的頭文件包含進(jìn)測試用例所在的工程,目前之所以這樣做是為了測試方便,一旦順利通過測試用例后,可以再考慮將該C++類放到一個獨(dú)立的VC工程中,并生成相應(yīng)的dll文件,以便該模塊可以被更多其他的VC程序調(diào)用,從而提高了程序整體的復(fù)用性。
在執(zhí)行測試用例之前,這次的心情不再像上一次那樣興奮,只是默默的等待測試結(jié)果的正確返回。然而此時,在控制臺窗口突然打印出一條libmemcached中的錯誤提示信息,看到該信息后,立刻切換到VMWire虛擬機(jī)中運(yùn)行的Linux,查看memcached守護(hù)進(jìn)程打印出的結(jié)果。出人意料的是,memcached沒有任何反應(yīng),再結(jié)合libmemcached剛剛輸出的錯誤信息,使我馬上意識到測試用例并沒有驅(qū)動libmemcached正常的工作,甚至根本就沒有連接到Linux中的memcached服務(wù)器。想到這里,我首先關(guān)閉了Linux中iptables的防火墻,然后在我的Windows主機(jī)中通過telnet的方式,直接登錄memcached服務(wù)器監(jiān)聽的端口,最后再切換回Linux查看memcached服務(wù)器的反應(yīng),這次memcached輸出了一條客戶端連接的信息,基于此可以證明主機(jī)(Windows)和VMWire虛擬機(jī)中的Linux之間的Socket通訊是正常的。帶著忐忑的心情,再次執(zhí)行了我的測試用例,果不其然,libmemcached輸出了同樣的錯誤信息,Linux端的memcached服務(wù)器也同樣是沒有任何反應(yīng)。
這時已經(jīng)是深夜了,人的大腦也進(jìn)入了一種麻木的狀態(tài),于是決定上床休息,因?yàn)橹暗慕?jīng)驗(yàn)告訴我,在這個時候離開電腦冷靜的思考往往會分析到問題的本質(zhì),并做出正確的判斷。
七、最后的決策:
這一次我的選擇是暫時放棄移植libmemcached到VC的想法,原因如下:
1. libmemcached版本更新過快,從0.52到0.53的發(fā)布僅僅時隔兩周,而且每兩個版本之間的代碼差異也非常大,甚至代碼的目錄組織結(jié)構(gòu)也是如此,這在我移植0.49和0.53時,體現(xiàn)得非常充分。
2. 在移植過程中,為了保證順利通過編譯,每個版本都需要進(jìn)行多處修改,而且每個版本修改的地方也不一樣,有意思的是,和0.53相比,0.49需要修改的地方相對較少,移植過程也更加容易。
3. 修改的方式五花八門,最簡單的就是gcc和VC在C++語法支持上的細(xì)節(jié)差異,這個相對簡單,相信每一個有代碼平臺遷移經(jīng)驗(yàn)的人都會遇到這樣的問題,再有就是沖突問題,比如libmemcached在一個枚舉中包含TRUE、FALSE、ERROR、FLOAT和SOCKET這樣的枚舉成員,不幸的是,它們與windef.h中定義的宏和typedef沖突了,因此我不得不將枚舉中的TRUE改為TRUE1,以此類推。在修改中最讓我擔(dān)心的是需要直接注釋掉一些Windows中不包含的頭文件,而這些文件在Mingw32中也沒有提供。
4. 調(diào)試相對困難,事實(shí)上在這次遷移0.53的最后,我通過在libmemcached相應(yīng)的函數(shù)中添加printf函數(shù),輸出調(diào)試信息,最終定位并修復(fù)了Socket連接的問題,但是整個過程非常繁瑣,因?yàn)閘ibmemcached是通過gcc編譯的,因此無法在VC的工程中進(jìn)行調(diào)試。當(dāng)然,基于VC的測試用例在gdb下調(diào)試libmemcached也是可以的,對于習(xí)慣使用IDE的人來說,通過命令行方式調(diào)試第三方類庫,其難度可想而知。
5. 說了這么多,最終的結(jié)果只有一個,Linux下繼續(xù)享用libmemcached和memcached服務(wù)器給我們帶來的成就感,也感謝他們的開發(fā)者無私的奉獻(xiàn)。至于libmemcached for Windows?希望他的作者M(jìn)rs Brian Aker能夠在未來的版本中予以足夠的關(guān)注,也期望libmemcached 1.0版本在發(fā)布時能夠提供VC++的工程文件。
八、結(jié)束語:
希望這兩篇文章不僅僅是讓您了解了更多關(guān)于libmemcached的細(xì)節(jié),授人以魚,不如授人以漁,更希望的是與您分享我在基于不同平臺遷移C/C++代碼的經(jīng)驗(yàn)。如果您有更好的方法或技巧,歡迎指正,讓我們來共同提高。總之,分享是快樂的。
作者 Stephen_Liu