VC调用giflib(1):VC编译giflib
作者:马健
邮箱:stronghorse_mj@hotmail.com
主页:http://www.comicer.com/stronghorse
发布:2020.03.14
以前我只与静态GIF文件打交道,用CxImage就够了。最近需要编、解码动画GIF,才发现CxImage不够用。刚好处理webp动画的libwebp库提供了用giflib解码动画GIF的源代码,所以就试着在我的VC代码中调用giflib,结果发现实在麻烦——这个库的官方文档基本没有,官方例子代码也绕来绕去,网上的说明和例子不仅不多,还到处是坑,说起来都是泪。好在经过一番折腾,我想要的功能最终还是实现了,虽然过程曲折了一点。为了不在同样的坑里踩两遍,于是写下了这一系列笔记。
本系列笔记所谈的giflib均针对5.2.1版本,不再逐一说明。
==========================================================================
要在VC里调用giflib,第一步当然是用VC编译giflib。考虑到复用性,以编译成静态库为宜。网上有文章介绍这个编译过程,但其中可能藏坑。这里先列出我已经填过坑的编译过程:
1、新建一个空项目,名称为giflib,类型为静态库(Static library),注意务必把预编译头(Precompiled header)选项去掉。
2、把创建出来的项目文件剪切到giflib-5.2.1文件夹。
3、将h和c文件添加到项目中,包括:
dgif_lib.c
egif_lib.c
gif_err.c
gif_hash.c
gifalloc.c
openbsd-reallocarray.c
qprintf.c
gif_hash.h
gif_lib.h
gif_lib_private.h
其中:
1)加入qprintf.c是因为我要调用giftext.c中的代码。如果不调用,可以不加入。
2)如果要使用giflib的调色板量化功能(用法参见例子gif2rgb.c),还需要加入quantize.c。我嫌它这个中位切分(median cut)算法太low就没加,改用FreeImage提供的其他调色板量化算法。
3)、如果要编译giflib的命令行,还需要从github开源项目
https://github.com/bjornblissing/osg-3rdparty-cmake.git
的giflib文件夹下载这两个命令行解析代码,加入工程中:
getopt.c
getopt.h
我不打算编译生成命令行工具,所以就没加。
4、设置Code Generation、Output,创建x64编译配置。
5、开始编译。编译报错时先把#include <stdint.h>、#include<unistd.h>代码全部注释掉。VC没有这两个头文件,也不需要。
6、将gif_lib.h中
#include <stdbool.h>
这一行,改成
//#include #ifndef __cplusplus typedef char bool; #define false 0 #define true 1 #endif typedef unsigned __int32 uint32_t;
即注释掉对stdbool.h的引用,在C下定义bool量为单字节类型,同时增加uint32_t类型定义。注意网上的一些giflib编译说明,包括上面那个github开源项目提供的stdbool.h中一般是
typedef int bool;
即把bool说明为4字节的int类型,而不是我上面说的单字节char类型。这就是我要填的第一个坑,本文后面再详谈。
7、对giflib源代码进行清理,以消除一些警告错误:
1)egif_lib.c中的EGifPutPixel、EGifCompressLine、EGifCompressOutput函数原型的参数类型与实现不一致,按照实现改原型。
2)EGifOpenFileName、EGifOpenFileHandle、DGifOpenFileName、DGifOpenFileHandle等函数会报一大堆4996警告,在gif_lib.h的
#ifdef __cplusplus
前面加一行
#pragma warning( disable : 4996 )
世界就清净了。
3)编译x64版本还有几个类型转换警告,强制转换即可,不转也无所谓。
4)如果需要处理Unicode文件名,把EGifOpenFileName、DGifOpenFileName函数复制一份并更名为EGifOpenFileNameW、DGifOpenFileNameW,把函数原型中FileName参数的类型从char*改成wchar_t*,函数体中的open改成_wopen即可。当然也可以不这么麻烦,而是用EGifOpen、DGifOpen解决Unicode文件名的问题,下一篇笔记会专门讲这两个函数的使用。
经过以上修改,不出意外就可以成功编译出giflib静态库。至于giflib源代码中的内存泄漏(memory leak)等坑,等后面讲到的时候再详谈。
这里先说详细谈一下前面提到的第一个坑,这个坑可以细化为以下三个问题:
- 为什么giflib要单独定义bool类型?
- bool类型究竟是1字节还是4字节?
- 字节数不同对于giflib的调用究竟有什么影响?
先回答第1个问题:bool是C++中规定的内建(built-in)数据类型,在C中则不是。所以纯C代码或跨平台库,通常要么就不使用bool类型,要么就自己定义一个,最著名的就是Windows SDK里单独定义的BOOL类型(typedef int BOOL),freetype-2.8里则定义了FT_Bool数据类型(typedef unsigned char FT_Bool),jpeg_v6b中定义了boolean(typedef unsigned char boolean),djvulibre-3.5.28的定义和我的差不多:
/* - BOOL */ #if !defined(HAVE_BOOL) && !defined(bool) #define bool char #define true 1 #define false 0 #endif
giflib是纯C代码,所以就用了C99标准库stdbool.h,对bool类型进行定义。VC对C99标准只是部分实现,就没有这个库,所以需要自己搞。
再回答第2个问题:在VC 2010帮助文档的bool keyword[C++]词条里,前面引用的都是C99标准说明文字,后面来了一段神转折:
Microsoft Specific
In Visual C++4.2, the Standard C++ header files contained a typedef that equated bool with int. In Visual C++ 5.0 and later, bool is implemented as a built-in type with a size of 1 byte. That means that for Visual C++ 4.2, a call of sizeof(bool) yields 4, while in Visual C++ 5.0 and later, the same call yields 1. This can cause memory corruption problems if you have defined structure members of type bool in Visual C++ 4.2 and are mixing object files (OBJ) and/or DLLs built with the 4.2 and 5.0 or later compilers.
也就是说,从VC 5.0开始,微软就认定用一个字节表示bool类型足够了。在MSDN在线文档
Built-in types (C++) | Microsoft Docs
中,也说明bool就是一个字节,而不是像网上一些代码所定义的是int类型,即4个字节。
结合以上两个问题的答案就可以回答第3个问题:
- giflib是纯C代码,用它编译出来的静态库如果只被纯C代码调用,大家使用相同的stdbool.h定义,对bool类型的理解一致,那么P事没有,一切正常。
- 如果是从VC写的C++代码(CPP)中调用giflib,就会出现数据类型不匹配。
具体而言,由于VC在C++中认定bool只有一个字节,则在调用giflib函数时,对于bool类型就只会传递1个字节的值,但giflib如果认定bool有4个字节,那么接收bool类型参数时就按4字节接收,最终所收到的bool类型参数只有最低1个字节是有效的,其余高位3字节具体是这么值,完全看临近的变量是什么值。
要验证这个问题,其实很简单:
- 就按网上一般说的,采用上面那个github开源项目提供的stdbool.h,把bool定义为int类型,编译giflib静态库。
- 用VC写一段CPP代码,在调用EGifOpenFileName创建一个GIF文件后,就调用函数
void EGifSetGifVersion(GifFileType *GifFile, const bool gif89);
对于gif89参数传递一个false,然后跟踪进去,实际看一下giflib收到的是不是0。
除了这个接口外,最最关键的是GifImageDesc结构体的Interlace成员也有这个问题。我也是在发现无论我怎么设置,创建出来的都是交错GIF文件时,才发现双方在bool类型上是鸡同鸭讲。