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

移植最新版libmemcached到VC++的艰苦历程和经验总结(下)的更多相关文章

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

    零.前言: 该篇博客的Title原计划是“在VC++中调用libmemcached的设计技巧”,可结果却事与原违,原因很简单,移植失败了.尽管结果如此,然而这3天的付出却是非常值得的,原因也很简单,收 ...

  2. 【转】 VC++6.0 在Win7 64位下调试,Shift+F5无法退出

    Win7 64位VC++6.0调试代码无法关闭窗口解决方法 VC++6.0 在64位Windows7下调试的时候,再结束调试,程序无法退出,只能关闭VC++6.0 IDE环境. 问题描述:当我击F5开 ...

  3. VC++6.0在win8.1系统下运行失败的解决办法

    在win8.1系统下安装了VC++6,.0编译软件之后,发现打不开.出现下面的错误: 解决办法: 安装文件目录:Microsoft Visual Studio--common--MSDev98--Bi ...

  4. Django从无到有的艰苦历程

    1, django项目下的各个文件的介绍 1.1, 项目的根目录: 实Django项目的总目录, 所有的子项目, 和需要进行的操作都在其中进行. 1.2

  5. Ubuntu16.04CPU下安装caffe的艰苦历程

    我选用的是anaconda安装,符上我参照的三个有用的教程. 1 http://www.linuxdiyf.com/linux/22442.html 主要讲anaconda的安装和python路径配置 ...

  6. [百度空间] [原]跨平台编程注意事项(三): window 到 android 的 移植

    大的问题 先记录一下跨平台时需要注意的大方向. 1.OS和CPU 同一个操作系统, CPU也可能是不一样的, 比如windows也有基于arm CPU的版本,而android目前有x86,arm,mi ...

  7. Systemc在VC++2010安装方法及如何在VC++2010运行Noxim模拟器

    Systemc在VC++2010的安装方法可以参考文档"Systemc with Microsoft Visual Studio 2008.pdf".本文档可以在"htt ...

  8. mini2440移植uboot 2011.03(上)

    参考博文: <u-boot-2011.03在mini2440/micro2440上的移植> 本来我想移植最新版的uboot,但是移植却不太成功,所以先模仿他人的例子重新执行一遍,对uboo ...

  9. FreeRTOS学习及移植笔记之一:开始FreeRTOS之旅

    1.必要的准备工作 工欲善其事,必先利其器,在开始学习和移植之前,相应的准备工作必不可少.所以在开始我们写要准备如下: 测试环境:我准备在STM32F103平台上移植和测试FreeRTOS系统 准备F ...

随机推荐

  1. Redhat hadoop2.7.2安装笔记

    本次安装是在windows7环境下安装redhat虚拟机进行的,所须要的软件例如以下: VirtualBox-5.0.16-105871-Win.exe rhel-server-5.4-x86_64- ...

  2. 通俗易懂,什么是.NET?什么是.NET Framework?什么是.NET Core? .Net Web开发技术栈

    通俗易懂,什么是.NET?什么是.NET Framework?什么是.NET Core?   什么是.NET?什么是.NET Framework?本文将从上往下,循序渐进的介绍一系列相关.NET的概念 ...

  3. 读取配置文件(configparser,.ini文件)

    使用configparser来读取配置信息config.ini 读取的信息(config.ini)如下: [baseconf]host=127.0.0.1port=3306user=rootpassw ...

  4. 升级iOS8和iOS9系统后,保险箱Pro、私人保险箱、私密相冊打开就闪退的官方解决方式

    升级iOS8和iOS9.iOS10系统后,保险箱Pro.私人保险箱.私密相冊打开就闪退的官方解决方式 查看设备iOS操作系统版本号号办法:iPhone/iPad->设置->通用->关 ...

  5. 【CUDA】CUDA开发环境搭建

    http://blog.csdn.net/tracer9/article/details/50484764 标签: CUDA并行计算NVIDIAlinux 2016-01-08 18:35 637人阅 ...

  6. MongoDB的一些操作技巧

    去年三月底入职上海的一家互联网公司,由于项目使用的是MongoDB数据库所以有机会接触了MongoDB.在项目的开发过程中使用系统原有的一些方法查询MongoDB感觉很费力,用起来也不爽,所以私下里就 ...

  7. Cisco策略路由(policy route)精解(转载)

    原文:http://www.guanwei.org/post/Cisconetwork/07/Cisco-policy-route_8621.html 注:PBR以前是CISCO用来丢弃报文的一个主要 ...

  8. POSIX标准中的 “ 限制 ”

    前言 在POSIX标准中,定义了许多限制.这些限制大约分为五类,不同类型的限制获取的方式不一样. 限制值分类 1. 不变的最小值 这类型的限制值是静态的,固定的. 2. 不变值 同上 3. 运行时可以 ...

  9. MVC——分页

    添加类PageBar.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; ...

  10. Js 模拟鼠标点击事件

    var obj = document.getElementById('go'); if(document.all){ obj.click(); }else{ var e = document.crea ...