移植最新版libmemcached到VC++的艰苦历程和经验总结(下)

来源:岁月联盟 编辑:exp 时间:2011-10-27

 

结果如何呢?我的VC++测试用例还是不能调用该接口的接口方法,只是这次的报错方式有所改变,提示是每个C/C++程序员最不愿意看到的“内存地址访问违规”,这一次我确实被郁闷了,这是为什么呢?

五、gccVC++对象模型的差异分析:

  在VC++中,C++对象(含有虚函数)在编译后将生成属于自己的对象模型,虚拟表vtable和虚拟指针vptr均被包含在该模型中(关于该问题,可以参考Stanley Lippman的《深度探索C++对象模型》)。而我们目前的设计方式恰恰是充分利用了vptr和vtable来定位每个接口函数,不幸的是,VC++生成的C++对象的vptr在该对象模型的最开始处,即该对象模型的前4个字节,而gcc则不是这样存储vptr的。当我们通过vptr定位vtable,再通过vtable的slot定位接口函数时,也将无法得到我们期望的接口函数的入口地址,因此调用结果可想而知,而且还给人一种关公战秦琼的感觉。

六、传统的设计思路和技巧:

  一条路已经走到了死胡同,唯一的办法就是掉过头来重新来过。这次我想到的设计思路非常简单,也更加传统。通过之前的失败经验总结,尽管通过VC调用gcc的纯虚接口是不能正常工作的,然而gcc导出的那两个C函数却是可以正常调用的,同时也得到了正确的调用结果。鉴于此,我将设计一个只是包含封装函数(封装libmemcached的导出函数)的动态库,见如下代码:

 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 }

  通过封装函数的签名可以看出,所有和libmemcached相关的结构体指针在此均被定义为void*类型,这样就可以规避上一篇中提到的结构体由不同编译器生成的字节对齐问题。最后,在封装函数的内部只需要将void*转换回其封装的libmemcached导出函数参数期望的结构体指针类型即可,实现代码如下:

  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的环境下编译该代码文件,并生成相应的动态库文件(MemcachedFunctionsWrapper.dll),需要说明的是该动态库将静态依赖之前生成的libmemcached-8.dll,这一点可以在Mingw32下通过ldd命令予以验证。

  剩下需要做的是去除之前声明的纯虚接口,直接导出一个C++的封装实现类,在该类的实现中,同样利用Windows API中的LoadLibrary和GetProcAddress方法动态加载之前生成的libmemcached函数封装动态库(MemcachedFunctionsWrapper.dll)。该C++类的类声明和上一篇中实现类的声明完全一致,这里就不在重复给出了。测试用例的代码也基本相同,只是不需要再通过C接口函数获取该C++类的对象指针了,而是可以直接将该C++类的头文件包含进测试用例所在的工程,目前之所以这样做是为了测试方便,一旦顺利通过测试用例后,可以再考虑将该C++类放到一个独立的VC工程中,并生成相应的dll文件,以便该模块可以被更多其他的VC程序调用,从而提高了程序整体的复用性。

  在执行测试用例之前,这次的心情不再像上一次那样兴奋,只是默默的等待测试结果的正确返回。然而此时,在控制台窗口突然打印出一条libmemcached中的错误提示信息,看到该信息后,立刻切换到VMWire虚拟机中运行的Linux,查看memcached守护进程打印出的结果。出人意料的是,memcached没有任何反应,再结合libmemcached刚刚输出的错误信息,使我马上意识到测试用例并没有驱动libmemcached正常的工作,甚至根本就没有连接到Linux中的memcached服务器。想到这里,我首先关闭了Linux中iptables的防火墙,然后在我的Windows主机中通过telnet的方式,直接登录memcached服务器监听的端口,最后再切换回Linux查看memcached服务器的反应,这次memcached输出了一条客户端连接的信息,基于此可以证明主机(Windows)和VMWire虚拟机中的Linux之间的Socket通讯是正常的。带着忐忑的心情,再次执行了我的测试用例,果不其然,libmemcached输出了同样的错误信息,Linux端的memcached服务器也同样是没有任何反应。

  这时已经是深夜了,人的大脑也进入了一种麻木的状态,于是决定上床休息,因为之前的经验告诉我,在这个时候离开电脑冷静的思考往往会分析到问题的本质,并做出正确的判断。

七、最后的决策:

  这一次我的选择是暂时放弃移植libmemcached到VC的想法,原因如下:

  1. libmemcached版本更新过快,从0.52到0.53的发布仅仅时隔两周,而且每两个版本之间的代码差异也非常大,甚至代码的目录组织结构也是如此,这在我移植0.49和0.53时,体现得非常充分。

  2. 在移植过程中,为了保证顺利通过编译,每个版本都需要进行多处修改,而且每个版本修改的地方也不一样,有意思的是,和0.53相比,0.49需要修改的地方相对较少,移植过程也更加容易。

  3. 修改的方式五花八门,最简单的就是gcc和VC在C++语法支持上的细节差异,这个相对简单,相信每一个有代码平台迁移经验的人都会遇到这样的问题,再有就是冲突问题,比如libmemcached在一个枚举中包含TRUE、FALSE、ERROR、FLOAT和SOCKET这样的枚举成员,不幸的是,它们与windef.h中定义的宏和typedef冲突了,因此我不得不将枚举中的TRUE改为TRUE1,以此类推。在修改中最让我担心的是需要直接注释掉一些Windows中不包含的头文件,而这些文件在Mingw32中也没有提供。

  4. 调试相对困难,事实上在这次迁移0.53的最后,我通过在libmemcached相应的函数中添加printf函数,输出调试信息,最终定位并修复了Socket连接的问题,但是整个过程非常繁琐,因为libmemcached是通过gcc编译的,因此无法在VC的工程中进行调试。当然,基于VC的测试用例在gdb下调试libmemcached也是可以的,对于习惯使用IDE的人来说,通过命令行方式调试第三方类库,其难度可想而知。

  5. 说了这么多,最终的结果只有一个,Linux下继续享用libmemcached和memcached服务器给我们带来的成就感,也感谢他们的开发者无私的奉献。至于libmemcached for Windows?希望他的作者Mrs Brian Aker能够在未来的版本中予以足够的关注,也期望libmemcached 1.0版本在发布时能够提供VC++的工程文件。

八、结束语:

  希望这两篇文章不仅仅是让您了解了更多关于libmemcached的细节,授人以鱼,不如授人以渔,更希望的是与您分享我在基于不同平台迁移C/C++代码的经验。如果您有更好的方法或技巧,欢迎指正,让我们来共同提高。总之,分享是快乐的。

 

作者 Stephen_Liu