零、前言:

  该篇博客的Title原计划是“在VC++中调用libmemcached的设计技巧”,可结果却事与原违,原因很简单,移植失败了。尽管结果如此,然而这3天的付出却是非常值得的,原因也很简单,收获非常大。事实上,我曾经在6月份的时候成功移植了当时的最新版本0.49,并写出了下面的博客:

  http://www.cnblogs.com/stephen-liu74/archive/2011/06/20/2084882.html

  这次移植的目标非常明确,就是基于上次的经验,对libmemcached进行基于C++的封装,以便其可以更好的集成到我的底层服务框架中,使我的程序在Windows平台也可以享受memcached服务器带来的性能优势。带着这份憧憬开始了我的艰苦移植历程。首先需要说明的是0.49和最新版0.53之间的差异是非常大的,这一点也让我始料不及,因此走了一些弯路,好在及时做出调整,才没有耽搁更多的时间去验证一些不可能的事情。下面是我在动手移植libmemcached之前的设计思路,移植过程中遇到的问题,以及移植失败后的经验总结。

一、最初的设计思路:

  为什么不在VC中直接调用编译后的libmemcached库呢?原因非常简单,我们无法直接调用。由于目标库(libmemcached)是在Mingw32环境下通过gcc编译的,而gcc在Windows下编译动态链接库时(DLL)并没有生成相应的lib文件,这样在VC中也就无法通过静态链接动态库的方式将libmemcached链接到调用程序中。这样我们只能利用Windows中提供的另一种方式,即通过LoadLibrary和GetProcAddress等Win32 API来动态加载该动态库,因为该方法不需要.lib文件。尽管该方式在技术上被认为是可能行的,然而libmemcached中存在大量的导出函数,以及这些函数所依赖的结构体(struct)。为了保证GetProcAddress返回的函数指针能够正常的被调用,如memcached_create等,我们不得不在当前工程中定义(typedef)大量的函数指针以及相关的结构体。鉴于之前的移植经验,由于这些结构体嵌套了大量的内部子结构体,因此如果全部定义就需要大量的工作。而这些结构体的成员很多都是用于libmemcached内部,因此一旦在未来的版本中修改了该结构体的成员,那么我们的程序也不得不要随之改变,可以想象,这样的同步是相当痛苦的。除此之外,还有一个非常致命的缺陷,即结构体成员的字节对齐问题。如果不是一字节对齐,如VC缺省的8字节对齐,那么gcc和VC编译器在处理该类问题时就可能存在一定的差异,一旦如此,VC中定义的结构体填充的数据就不能被gcc正确的取出,从而导致libmemcached中函数无法正常的工作。

  为了避免以上问题的发生,下面我就来介绍一种通用的设计技巧用于解决该类问题。

  1. 定义一个C++的纯虚接口和两个C的导出函数,其中C++的纯虚接口用于之后的程序调用,该接口中定义了部分libmemcached中提供的功能,如add、set、replace、get、delete和CAS等。然而需要注意的是该接口中并未包含或暴露任何和libmemcached相关的信息,如memcached_st结构体、memcached_create函数等。见如下代码:

 1 class MemcachedClientWrapper 2 { 3 protected: 4     virtual ~MemcachedClientWrapper() {} 5  6 public: 7     virtual bool initialize(bool consistentHash = false, bool supportCAS = false) = 0; 8     virtual int addServer(const char* serverIP, const int port) = 0; 9     virtual void release() = 0;10     virtual bool set(void* key,int klength,void* data,int dlength) = 0;11     virtual bool add(void* key,int klength,void* data,int dlength) = 0;12     virtual bool replace(void* key,int klength,void* data,int dlength) = 0;13     virtual bool append(void* key,int klength,void* data,int dlength) = 0;14     virtual bool get(void* key,int klength,MCFetchedData*& fetchedData) = 0;15     virtual bool gets(void** keys,int* keysLength,int count) = 0;16     virtual bool fetchNext(MCFetchedData*& fetchedData,bool* isEof = 0) = 0;17     virtual bool remove(void* key,int klength) = 0;18     virtual bool exists(void* key,int klength,bool* ok = 0) = 0;19     virtual bool updateWithCAS(void* key,int klength,void* data,int dlength) = 0;20     virtual bool updateWithAtomicIncrement(void* key,int klength,int step21             ,uint64& value,int* defaultValue = 0) = 0;22     virtual bool updateWithAtomicDecrement(void* key,int klength,int step23             ,uint64& value,int* defaultValue = 0) = 0;24     virtual bool clean() = 0;25 };

  两个导出的C函数主要用于创建该接口的实现子类,以及在使用完毕后释放该接口的指针,从而保证资源释放的可靠性,见如下代码:

1 #define WRAPPER_CC __attribute__((cdcel))2 3 extern "C" {4     //工厂方法创建Wrapper的实现子类,但是返回接口的指针5     MemcachedClientWrapper* WRAPPER_CC createMCWrapper();6     //资源释放函数,通过上面函数返回的接口指针,需要通过该函数释放7     void WRAPPER_CC releaseMCWrapper(MemcachedClientWrapper*);8 }

这里之所以使用cdecl的调用规范,而不是Windows API常用的stdcall,主要是因为gcc在编译基于stdcall调用规范的C导出函数时,在导出函数名的后面添加了一个@和该函数参数所占的字节数作为后缀,因此当我们通过GetProcAddress传入函数名获取函数指针时,由于名称不匹配,所以返回的函数指针将为NULL。和stdcall不同的是,基于cdcel调用规范生成的导出函数不存在这样的问题。我们可以通过Windows提供的Dependency工具予以证明。

  2. 定义一个实现类继承自上面定义的纯虚接口,该类将包含大量和libmemcached相关的细节信息,同时也需要在该类中include和libmemcached相关的头文件,而不是再自行重新定义和libmemcached相关的细节信息,最后再通过动态加载的方式进行加载,见如下代码声明:

 1 #include <MemcachedClientWrapper.h> 2 #include <libmemcached/memcached.h> 3  4 class MemcachedClientWrapperImpl : public MemcachedClientWrapper 5 { 6 public: 7     MemcachedClientWrapperImpl(); 8     virtual ~MemcachedClientWrapperImpl(); 9 10 public:11     virtual bool initialize(bool consistentHash = false, bool supportCAS = false);12     void release();13     int addServer(const char* serverIP, const int port);14     bool set(void* key,int klength,void* data,int dlength);15     bool add(void* key,int klength,void* data,int dlength);16     bool replace(void* key,int klength,void* data,int dlength);17     bool append(void* key,int klength,void* data,int dlength);18     bool get(void* key,int klength,MCFetchedData*& fetchedData);19     bool gets(void** keys,int* keysLength,int count);20     bool fetchNext(MCFetchedData*& fetchedData,bool* isEof = 0);21     bool remove(void* key,int klength);22     bool exists(void* key,int klength,bool* ok = NULL);23     bool updateWithCAS(void* key,int klength,void* data,int dlength);24     bool updateWithAtomicIncrement(void* key,int klength,int step,uint64& value,int* defaultValue = 0);25     bool updateWithAtomicDecrement(void* key,int klength,int step,uint64& value,int* defaultValue = 0);26     bool clean();27 28 private:29     memcached_st*    _mc;30     memcached_return _mr;31     uint64_t _cas;32     bool _initialized;33     bool _supportCAS;34 };

  3. 还是在Mingw32环境下,编写makefile文件,生成新的动态库,该库将依赖libmemcached库。见如下makefile:

all:
      g++ -I/usr/local/include -I"/home/Administrator/MemcachedClientWrapper" ../MemcachedClientWrapperImpl.cpp -o ./MemcachedClientWrapperImpl.o -O3 -w -c -fmessage-length=0 -MMD -MP -MF"MemcachedClientWrapperImpl.d" -MT"MemcachedClientWrapperImpl.d"
      g++ -L/usr/local/bin -shared -o "./MemcachedClientWrapper.dll" ./MemcachedClientWrapperImpl.o -lmemcached-8

见以下说明:

1) MemcachedClientWrapperImpl.cpp文件中包含接口的实现部分已经两个导出C函数的实现。

2) /usr/local/include: 中包含libmemcached的相关头文件,是在libmemcached的make install中copy过去的。

3) /home/Administrator/MemcachedClientWrapper: 该目录包含该Wrapper工程头文件所在目录。

4) /usr/local/bin: 该目录包含libmemcached生成的dll文件。

5) libmemcached-8.dll: 当前版本libmemcached生成的动态库。

6) MemcachedClientWrapper.dll: 为最终生成的动态库。

  4. 在执行完步骤3之后,我们将得到两个dll,一个是原有的libmemcached.dll,另一个则为我们封装后的MemcacheClientWrapper.dll。在此之后,我们的VC程序将只是面对封装后的动态库及其导出接口,而libmemcached中的导出信息已经被很好的封装在Wrapper的内部了。我们现在需要做的便是在我们的工程中将纯虚接口所在的头文件包含进我们的工程中,然后在定义两个C函数指针,其函数签名和Wrapper中导出的两个C函数保持一致,见如下代码:

  typedef MemcachedClientWrapper* (*createWrapper)();
     typedef void (*releaseWrapper)(MemcachedClientWrapper*);

  5. 最后我们需要做的是在我们的测试用例中利用该Wrapper导出的纯虚接口来操作libmemcached,以便和Memcached服务器进行通讯和数据存储,见如下用例代码:

 1 #include <MemcachedClientWrapper.h> 2  3 int main() 4 { 5     MemcachedClientWrapper* wrapper = createMCWrapper(); 6     void* key = malloc(20); 7     void* data = malloc(20); 8     memcpy(key,"helloworld",10); 9     memcpy(data,"i love you, my baby.",20);10     assert(wrapper->add(key,10,data,20));11     free(key);12     free(data);13     releaseMCWrapper(wrapper);14     printf("All Over.\n");15     return 0;16 }

三、原理分析:

  该技巧有些类似于Windows中的COM技术,通过编译器生成的C++虚拟表,以达到这种跨编译器的二进制接口形式。该技巧有以下几个注意事项:

  1. 定义纯虚接口,该接口中不能定义任何成员变量,否则在编译器之间无法做到二进制兼容。

  2. 定义C接口形式的工厂方法createMCClient(),用于创建实际的子类,这样使用者便可以通过动态链接的方式加载该库,如Windows中LoadLibrary和Linux中的dlopen。

  3. 定义C接口的对象指针释放方法releaseMCClient(),在接口中已经将接口的析构方法定义为protected类型,因此调用者无法直接delete该指针,而是必须通过该接口指针导出的release()方法或者该C导出函数来释放,尽管两者都可以达到释放资源的目的,然而我们在实践中还是推荐使用该C导出函数,以便保证使用方式的一致性和未来的扩展性。

  4. 在定义纯虚接口所在的头文件中不要包含任何和实现细节相关的信息。

  总之,一切都是这样的美好,我对我的设计也是非常自信,甚至有一点点的得意,因为我曾经使用该方式完成了很多库在C++ Builder和VC++之间的迁移,从而达到这种跨编译器的效果。可以想象,就连极为复杂的webkit也被我在短短的一周内成功迁移,libmemcached应该是不在话下的。然而事实却是残酷的,我的VC测试用例无法正常调用该纯虚接口,每次都会报出和寄存器相关的错误。这让我立刻想到了之前代码中存在的问题,一定是我在声明纯虚接口时,没有为每个接口函数明确声明调用规范,而gcc和VC++的缺省调用规范恰恰又是不同的(VC++缺省为cdcel)。想到这里,我立即做出修改,将所有接口函数的调用规范都指定为cdcel,然后用gcc重新编译该Wrapper库。结果如何呢?请看下篇。

转载于:https://www.cnblogs.com/xumaojun/p/8541620.html

移植最新版libmemcached到VC++的艰苦历程和经验总结(上)相关推荐

  1. 安装Fedora Core 4的艰苦历程

    安装Fedora Core 4的艰苦历程  http://blog.csdn.net/shaohui  shaohui_1983#163.com 接触Linux已经2年多的时间了,对Linux的印象2 ...

  2. 移植uboot第一步:下载,编译,烧到板子上试验

    写在前面: 我的博客已迁移至自建服务器:博客传送门,CSDN博客暂时停止,如有机器学习方面的兴趣,欢迎来看一看. 此外目前我在gitHub上准备一些李航的<统计学习方法>的实现算法,目标将 ...

  3. 移植QT5.6到韦东山JZ2240嵌入式开发板(史上最最最最最详细的QT移植教程)

    首先感谢http://blog.csdn.net/lizuobin2/article/details/52673494的博主,应该是韦东山团队的老师,说实话,拿到JZ2440的板子很长时间了,每次都有 ...

  4. vc项目开发:俄罗斯方块制作日志 [上]

    vc项目日志,俄罗斯方块开发 ----July mfc初学一个月时,所写的俄罗斯方块.共享下源码. July.2010/10/21 ---------------------------------- ...

  5. 分享技术创业三年多的艰苦历程和体会

    本来不想全转载的,但是太精彩了,很多东西都感同身受. 原文:http://www.cnblogs.com/baihmpgy/p/3508229.html 转眼我们团队的创业已经有3年9个月了,2010 ...

  6. 2020年新版Java学习路线图最全更新!囊括史上最全面104个知识点

    黑马程序员视频库 播妞QQ号:3077485083 传智播客旗下互联网资讯.学习资源免费分享平台 2020年,你有没有被突如其来的疫情打破原定的安排和计划? "停课不停学,延期不延工&quo ...

  7. mysql移植海思_nniefacelib是一个在海思35xx系列芯片上运行的人脸算法库

    nniefacelib nniefacelib是一个在海思35xx系列芯片上运行的人脸算法库,目前集成了mobilefacenet和retinaface. 后期也会融合一些其他经典的模型,目的也是总结 ...

  8. VC++程序开机自启动(注册表上注册)

    //实现程序开机自启动,在注册表上注册 BOOL CClientTracerDlg::autoRun() {  HKEY   RegKey;  LPCSTR KeyPath="Softwar ...

  9. Ice-E(Embedded Internet Communications Engine)移植到s3c2440A(arm9)linux(2.6.12)上的 -转

    1.前言 ICE-E是ICE在嵌入式上运行的一个版本,与ICE比较如下: Category Ice 3.3.0 Ice-E 1.3.0 Thread Pool concurrency model Bi ...

最新文章

  1. IPv6 — Multi-homing(多宿主/多链路/多归属)
  2. STL中的栈结构和队列结构
  3. vue_prop注册及验证
  4. 腾讯云全面更新数据智能服务全景图!
  5. 高通:骁龙将成为独立的产品品牌
  6. Python在一个文件夹下遍历得到所有的子文件路径和子文件后缀名
  7. 爬取上千个年度基金后,才知道这有多坑,千万别被人当韭菜给割了
  8. 单表的更新UPDATE和删除记录DELETE(二十六)
  9. 去除字符串中的html标记
  10. velocity 将数字转为以万为单位,保留2位小数
  11. Roslyn 入门:使用 Roslyn 静态分析现有项目中的代码
  12. java中打印俄罗斯方块游戏_java实现俄罗斯方块小游戏
  13. 命令行录制工具 asciinema
  14. 面试复盘系列:从象棋提升之道趣说面试提升之法
  15. hdu5769Substring
  16. 一起来捉妖找不到服务器,一起来捉妖妖怪分布大全 所有妖灵不同地点位置详解...
  17. POI设置和读取excel分组信息,多级分组设置
  18. 抢滩抖音、B站,快手港股IPO进程加速
  19. involution:比卷积、自注意力机制更好的神经网络新算子
  20. 盒图(N-S图)级相关例子

热门文章

  1. 7天内新闻前加 new
  2. oracle存储过程id递增,oracle存储过程——按id更新相关信息
  3. mysql---存储过程和函数
  4. 两次被简书签约作者拉黑的经历
  5. ArcGIS水文分析实战教程(12)河网分级流程
  6. Java中的List、Set、Map
  7. AS:Flash AS3中获取浏览器信息及URL相关参数(并非swf url地址)
  8. Xna环境在PC平台下的功能扩展
  9. Xna游戏编辑器开发(WinForm内嵌Xna)
  10. php按条件修改xml,php 修改、增加xml结点属性的实现代码