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

简介: 结果如何呢?我的VC++测试用例还是不能调用该接口的接口方法,只是这次的报错方式有所改变,提示是每个C/C++程序员最不愿意看到的“内存地址访问违规”,这一次我确实被郁闷了,这是为什么呢?五、gcc和VC++对象模型的差异分析:  在VC++中,C++对象(含有虚函数)在编译后将生成属于自己的对象模型,虚拟表vtable和虚拟指针vptr均被包含在该模型中(关于该问题,可以参考Stanley Lippman的《深度探索C++对象模型》)。

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

五、gcc和VC++对象模型的差异分析:

  在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++代码的经验。如果您有更好的方法或技巧,欢迎指正,让我们来共同提高。总之,分享是快乐的。

目录
相关文章
|
21天前
|
存储 Linux Android开发
开源!时间管理大师必备工具
【10月更文挑战第13天】
|
6月前
|
算法 前端开发 语音技术
推荐6款2023年爆火的开源项目,你值得一试!
推荐6款2023年爆火的开源项目,你值得一试!
推荐6款2023年爆火的开源项目,你值得一试!
|
6月前
|
关系型数据库 数据库 PostgreSQL
已知成长器软件用户手册
已知成长器软件用户手册
|
6月前
|
SQL Java API
《Java开发手册灵魂13问》正式上线,带你剖析阿里巴巴的开发细节
一线大厂怎么用Java?看阿里技术专家给你分析!《〈Java开发手册(泰山版)〉灵魂13问》电子书正式上线带你剖析阿里巴巴一线团队开发思维。
|
11月前
|
编译器 程序员 Linux
嵌入式软件开发第一讲笔记
嵌入式软件开发第一讲笔记
54 0
|
Web App开发 存储 缓存
5款精挑细选的软件,助你事半功倍
在工作的时候,大家都喜欢通过一些好用有效率的工具,来让工作更加快速地完成,今天给大家带来的这5款软件,更是一款比一款还要惊喜!
85 0
|
网络协议 数据可视化 Java
衣带渐宽终不悔!嵌入式大牛10年调Bug经验总结
衣带渐宽终不悔!嵌入式大牛10年调Bug经验总结
81 0
|
存储 小程序 前端开发
最新版小程序开发白皮书解读系列(1) — 由来
本系列文章将会解读 W3C 小程序白皮书第 2 版(最新版),这份白皮书是制定小程序的标准。
205 0
最新版小程序开发白皮书解读系列(1) — 由来
|
数据可视化 数据安全/隐私保护 Python
开发了一款小众开源工具,没想到还有这么多人用!
由于 Gitee Pages 的访问速度很快,很多朋友会选择 Gitee Pages 部署项目(如:个人博客、开源项目国内镜像站点)。但是它不像 GitHub Pages 那样,一提交代码就能自动更新 Pages,因为 Gitee 的自动部署属于 Gitee Pages Pro 的服务。
303 0
开发了一款小众开源工具,没想到还有这么多人用!
|
SQL Java 程序员
独家下载!《Java开发手册》灵魂13问,深度剖析一线大厂开发思维
《〈Java开发手册(泰山版)〉灵魂13问》独家首发!全网千万阅读量技术博主深度剖析Java规约背后的原理,从“问题重现”到“原理分析”再到“问题解决”,下载《Java开发手册》必备的伴读书目!
249160 2
独家下载!《Java开发手册》灵魂13问,深度剖析一线大厂开发思维