1 事件起因

出于安全考虑,Web服务器需要启用SSL,需要使用HTTPS方式访问,Web服务器端又使用了SNI技术,然而Windows XP不支持SNI技术,导致Windows XP系统下使用WinINet API实现的HTTPS接口访问不了Web服务器。

查阅相关资料后确定使用libcurl+openssl实现HTTP请求接口替换掉WinINet API实现的接口。选择以纯静态库方式调用,编译了支持openssl、zlib的libcurl静态库。

写了个使用curl easy接口获取下载文件大小的函数,在主程序中进行测试的时候,发现和CxImage库中使用的zlib发生了冲突。

搜索了下发现可以通过FORCE:MULTIPLE让编译器忽略这种冲突,但是微软官方给出了提示,这样处理可能会导致不可预期的问题,所以不能使用这种方式解决冲突。

2 寻求解决方案

2.1 提取共用zlib

编译时发现CxImage中使用的zlib和libcurl使用的zlib冲突了,一个想法是把zlib单独提取出来,CxImage和libcurl引用相同的zlib库。

CxImage库是以源码方式使用,把其中zlib部分代码删除,删除头文件中所有内容,修改为包含到引用的zlib库的头文件。Libcurl库头文件中删除有关zlib库的部分,包含到引用zlib库的头文件。修改后重新编译主程序,可以正常运行。

上述方式看起来是解决了问题,但是另外一个程序中使用的OpenCV库中也使用了zlib库,同时还使用了CxImage库,由于上面修改了CxImage库,重新编译这个程序,不出意外zlib库产生了冲突。OpenCV库也是以纯静态库方式调用,编译OpenCV库时会自动生成zlib库,把libcurl中的zlib库文件替换到OpenCV下编译错误。这里可以得出一个结论,依赖静态库A编译得到的静态库B,A库是不可替换的,否则编译失败。

到这里可以把OpenCV中的zlib替换为新版本,重新编译OpenCV,使用产生的zlib库再去编译libcurl,而CxImage直接引用这个zlib,应该是可以解决问题的。但是这种解决方式有很大问题,依赖层次太深,一旦当有一个地方需要升级zlib库的版本就需要全部重新编译一次,另外当后续再次引入第三方开源库中依赖zlib库时会更加麻烦,这里放弃了这种单独提取zlib库的解决方式。

原来的程序中同时使用CxImage和OpenCV没有产生冲突,原因可能是CxImage库中是以源码方式使用zlib,编译链接时在生成的目标文件中查找而不是库文件,而OpenCV中是在库文件中查找。

2.2 DLL方式使用libcurl

静态库方式使用libcurl会导致zlib冲突,把使用libcurl的接口封装到DLL中就可以解决这个问题,由于已经有了编译好的libcurl的静态库,所以只需要写一个DLL,提供需要使用的函数,把libcurl的使用限定在此DLL中。

一个简单的HTTP请求接口如下图所示:

这个看似简单的函数隐藏了很大的问题,因为此函数在DLL内部被执行,往string里追加数据会导致string释放原有内存,重新在堆上申请更大的内存以便容纳追加的数据,当时直觉是不确定这种在DLL内部申请内存,在调用程序中释放会不会有问题。

搜索了一下才发现这是一个很严重的问题,当DLL和主程序使用不同的堆时,这种方式肯定会出问题,当主程序和DLL使用同一个堆时没有问题。只有DLL和主程序都是DLL CRT方式时,DLL和主程序共用一个堆,内存申请和释放才不会有问题。所以这种DLL导出函数中使用string输出数据方式不能使用。

可以在DLL内部申请内存,DLL同时提供一个用于释放内存的接口,这样外部通过调用接口释放内存,这里也会存在问题,为了保持一致性,所有返回的地址必须都能够调用接口释放,否则使用者还需要去判断哪个地址需要释放,哪个地址不需要释放,这种接口对使用者来说不是一种好的方式。

作为提供接口的一方有一个不能更改的原则,因为你不能确定使用者的情况,只能确保提供的接口无论怎么使用都不会导致崩溃。

参考libcurl中的源码,可以靠传入一个回调函数实现数据的输出,其中回调函数在主程序中实现,类似如下代码:

如果需要按照这种传入回调函数的方式得到输出数据,那么也就不需要自己再封装一个DLL了,直接编译libcurl库以DLL方式调用即可。

下载libcurl源码,在其中的工程中找不到以静态库方式使用openssl编译动态库libcurl的选项,这就比较尴尬了,都用DLL方式,会导致多了几个DLL,怎么感觉都不太理想。

回到最开始由于都用了zlib库导致编译冲突,那么libcurl中使用zlib库的目的是什么?能不能不使用zlib库呢?搜索了相关资料后发现libcurl中使用zlib库可以把请求返回的数据直接解压,这些根本使用不到,直接把zlib去掉编译一个不带zlib库的libcurl静态库应该就能够解决冲突。

3 解决问题

编译以静态库方式使用openssl的libcurl静态库,替换掉之前带有zlib库支持的库文件,删除头文件中zlib相关内容,重新编译成功,问题解决。

4 事件回顾

回顾整个事件过程,发现问题出在编译libcurl库时,在不清楚zlib在libcurl中的作用的情况下,为了后续可能会使用到zlib,抱着有总比没有的好的想法,编译了支持zlib的libcurl库。

不过在解决zlib冲突的过程中,了解了一些之前不了解的内容,比如:

  1. 依赖静态库A编译得到静态库B,在修改A之后必须重新编译生成B;
  2. 依赖静态库A编译静态库B时,把A添加到工程中,且B中不使用#pragma comment(lib, “A.lib”)方式导入A库,生成的B库内包含A库代码,使用时无需携带A库;
  3. DLL和主程序只有使用相同的堆的情况下,才能跨域释放内存;
  4. DLL和主程序只有都是DLL CRT方式才共用一个堆;