結(jié)果如何呢?我的VC++測(cè)試用例還是不能調(diào)用該接口的接口方法,只是這次的報(bào)錯(cuò)方式有所改變,提示是每個(gè)C/C++程序員最不愿意看到的“內(nèi)存地址訪問(wèn)違規(guī)”,這一次我確實(shí)被郁悶了,這是為什么呢?
五、gcc和VC++對(duì)象模型的差異分析:
在VC++中,C++對(duì)象(含有虛函數(shù))在編譯后將生成屬于自己的對(duì)象模型,虛擬表vtable和虛擬指針vptr均被包含在該模型中(關(guān)于該問(wèn)題,可以參考Stanley Lippman的《深度探索C++對(duì)象模型》)。而我們目前的設(shè)計(jì)方式恰恰是充分利用了vptr和vtable來(lái)定位每個(gè)接口函數(shù),不幸的是,VC++生成的C++對(duì)象的vptr在該對(duì)象模型的最開(kāi)始處,即該對(duì)象模型的前4個(gè)字節(jié),而gcc則不是這樣存儲(chǔ)vptr的。當(dāng)我們通過(guò)vptr定位vtable,再通過(guò)vtable的slot定位接口函數(shù)時(shí),也將無(wú)法得到我們期望的接口函數(shù)的入口地址,因此調(diào)用結(jié)果可想而知,而且還給人一種關(guān)公戰(zhàn)秦瓊的感覺(jué)。
六、傳統(tǒng)的設(shè)計(jì)思路和技巧:
一條路已經(jīng)走到了死胡同,唯一的辦法就是掉過(guò)頭來(lái)重新來(lái)過(guò)。這次我想到的設(shè)計(jì)思路非常簡(jiǎn)單,也更加傳統(tǒng)。通過(guò)之前的失敗經(jīng)驗(yàn)總結(jié),盡管通過(guò)VC調(diào)用gcc的純虛接口是不能正常工作的,然而gcc導(dǎo)出的那兩個(gè)C函數(shù)卻是可以正常調(diào)用的,同時(shí)也得到了正確的調(diào)用結(jié)果。鑒于此,我將設(shè)計(jì)一個(gè)只是包含封裝函數(shù)(封裝libmemcached的導(dǎo)出函數(shù))的動(dòng)態(tài)庫(kù),見(jiàn)如下代碼:
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 }
通過(guò)封裝函數(shù)的簽名可以看出,所有和libmemcached相關(guān)的結(jié)構(gòu)體指針在此均被定義為void*類(lèi)型,這樣就可以規(guī)避上一篇中提到的結(jié)構(gòu)體由不同編譯器生成的字節(jié)對(duì)齊問(wèn)題。最后,在封裝函數(shù)的內(nèi)部只需要將void*轉(zhuǎn)換回其封裝的libmemcached導(dǎo)出函數(shù)參數(shù)期望的結(jié)構(gòu)體指針類(lèi)型即可,實(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 }
通過(guò)gcc在Mingw32的環(huán)境下編譯該代碼文件,并生成相應(yīng)的動(dòng)態(tài)庫(kù)文件(MemcachedFunctionsWrapper.dll),需要說(shuō)明的是該動(dòng)態(tài)庫(kù)將靜態(tài)依賴之前生成的libmemcached-8.dll,這一點(diǎn)可以在Mingw32下通過(guò)ldd命令予以驗(yàn)證。
剩下需要做的是去除之前聲明的純虛接口,直接導(dǎo)出一個(gè)C++的封裝實(shí)現(xiàn)類(lèi),在該類(lèi)的實(shí)現(xiàn)中,同樣利用Windows API中的LoadLibrary和GetProcAddress方法動(dòng)態(tài)加載之前生成的libmemcached函數(shù)封裝動(dòng)態(tài)庫(kù)(MemcachedFunctionsWrapper.dll)。該C++類(lèi)的類(lèi)聲明和上一篇中實(shí)現(xiàn)類(lèi)的聲明完全一致,這里就不在重復(fù)給出了。測(cè)試用例的代碼也基本相同,只是不需要再通過(guò)C接口函數(shù)獲取該C++類(lèi)的對(duì)象指針了,而是可以直接將該C++類(lèi)的頭文件包含進(jìn)測(cè)試用例所在的工程,目前之所以這樣做是為了測(cè)試方便,一旦順利通過(guò)測(cè)試用例后,可以再考慮將該C++類(lèi)放到一個(gè)獨(dú)立的VC工程中,并生成相應(yīng)的dll文件,以便該模塊可以被更多其他的VC程序調(diào)用,從而提高了程序整體的復(fù)用性。
在執(zhí)行測(cè)試用例之前,這次的心情不再像上一次那樣興奮,只是默默的等待測(cè)試結(jié)果的正確返回。然而此時(shí),在控制臺(tái)窗口突然打印出一條libmemcached中的錯(cuò)誤提示信息,看到該信息后,立刻切換到VMWire虛擬機(jī)中運(yùn)行的Linux,查看memcached守護(hù)進(jìn)程打印出的結(jié)果。出人意料的是,memcached沒(méi)有任何反應(yīng),再結(jié)合libmemcached剛剛輸出的錯(cuò)誤信息,使我馬上意識(shí)到測(cè)試用例并沒(méi)有驅(qū)動(dòng)libmemcached正常的工作,甚至根本就沒(méi)有連接到Linux中的memcached服務(wù)器。想到這里,我首先關(guān)閉了Linux中iptables的防火墻,然后在我的Windows主機(jī)中通過(guò)telnet的方式,直接登錄memcached服務(wù)器監(jiān)聽(tīng)的端口,最后再切換回Linux查看memcached服務(wù)器的反應(yīng),這次memcached輸出了一條客戶端連接的信息,基于此可以證明主機(jī)(Windows)和VMWire虛擬機(jī)中的Linux之間的Socket通訊是正常的。帶著忐忑的心情,再次執(zhí)行了我的測(cè)試用例,果不其然,libmemcached輸出了同樣的錯(cuò)誤信息,Linux端的memcached服務(wù)器也同樣是沒(méi)有任何反應(yīng)。
這時(shí)已經(jīng)是深夜了,人的大腦也進(jìn)入了一種麻木的狀態(tài),于是決定上床休息,因?yàn)橹暗慕?jīng)驗(yàn)告訴我,在這個(gè)時(shí)候離開(kāi)電腦冷靜的思考往往會(huì)分析到問(wèn)題的本質(zhì),并做出正確的判斷。
七、最后的決策:
這一次我的選擇是暫時(shí)放棄移植libmemcached到VC的想法,原因如下:
1. libmemcached版本更新過(guò)快,從0.52到0.53的發(fā)布僅僅時(shí)隔兩周,而且每?jī)蓚€(gè)版本之間的代碼差異也非常大,甚至代碼的目錄組織結(jié)構(gòu)也是如此,這在我移植0.49和0.53時(shí),體現(xiàn)得非常充分。
2. 在移植過(guò)程中,為了保證順利通過(guò)編譯,每個(gè)版本都需要進(jìn)行多處修改,而且每個(gè)版本修改的地方也不一樣,有意思的是,和0.53相比,0.49需要修改的地方相對(duì)較少,移植過(guò)程也更加容易。
3. 修改的方式五花八門(mén),最簡(jiǎn)單的就是gcc和VC在C++語(yǔ)法支持上的細(xì)節(jié)差異,這個(gè)相對(duì)簡(jiǎn)單,相信每一個(gè)有代碼平臺(tái)遷移經(jīng)驗(yàn)的人都會(huì)遇到這樣的問(wèn)題,再有就是沖突問(wèn)題,比如libmemcached在一個(gè)枚舉中包含TRUE、FALSE、ERROR、FLOAT和SOCKET這樣的枚舉成員,不幸的是,它們與windef.h中定義的宏和typedef沖突了,因此我不得不將枚舉中的TRUE改為T(mén)RUE1,以此類(lèi)推。在修改中最讓我擔(dān)心的是需要直接注釋掉一些Windows中不包含的頭文件,而這些文件在Mingw32中也沒(méi)有提供。
4. 調(diào)試相對(duì)困難,事實(shí)上在這次遷移0.53的最后,我通過(guò)在libmemcached相應(yīng)的函數(shù)中添加printf函數(shù),輸出調(diào)試信息,最終定位并修復(fù)了Socket連接的問(wèn)題,但是整個(gè)過(guò)程非常繁瑣,因?yàn)閘ibmemcached是通過(guò)gcc編譯的,因此無(wú)法在VC的工程中進(jìn)行調(diào)試。當(dāng)然,基于VC的測(cè)試用例在gdb下調(diào)試libmemcached也是可以的,對(duì)于習(xí)慣使用IDE的人來(lái)說(shuō),通過(guò)命令行方式調(diào)試第三方類(lèi)庫(kù),其難度可想而知。
5. 說(shuō)了這么多,最終的結(jié)果只有一個(gè),Linux下繼續(xù)享用libmemcached和memcached服務(wù)器給我們帶來(lái)的成就感,也感謝他們的開(kāi)發(fā)者無(wú)私的奉獻(xiàn)。至于libmemcached for Windows?希望他的作者M(jìn)rs Brian Aker能夠在未來(lái)的版本中予以足夠的關(guān)注,也期望libmemcached 1.0版本在發(fā)布時(shí)能夠提供VC++的工程文件。
八、結(jié)束語(yǔ):
希望這兩篇文章不僅僅是讓您了解了更多關(guān)于libmemcached的細(xì)節(jié),授人以魚(yú),不如授人以漁,更希望的是與您分享我在基于不同平臺(tái)遷移C/C++代碼的經(jīng)驗(yàn)。如果您有更好的方法或技巧,歡迎指正,讓我們來(lái)共同提高??傊?,分享是快樂(lè)的。
作者 Stephen_Liu
聯(lián)系客服