在Windows上面利用GTK+库开发应用程序时, 大多是动态链接的, 但是对于只是想拿它来写一些小的工具的人来说, 动态编译显然有些不太合适, 写完了程序还要附带上一大堆的dll文件, 这样无论是发布还是运行都比较麻烦。因此, 产生了想要静态编译GTK+库的想法。
去GTK+的mailing list中找到了相关的提问, 这是链接:
https://mail.gnome.org/archives/gtk-app-devel-list/2003-September/msg00038.html
其实, 从里面的邮件回复就可以看出来, GTK+库目前还是不支持静态编译的。
但是, 偶然的一次机会, 看到了StackOverflow里面有人回复, 有一个项目可以让GTK+实现静态编译, 这是StackOverflow的链接:
http://stackoverflow.com/questions/1875855/statically-linking-gtk-libaries-in-windows
里面提到了一个叫做mingw-cross-env的项目, 于是去看了一下, 然后顺便找到了GitHub的项目页面:
https://github.com/mxe/mxe
于是, 就照着这个项目开始了GTK+-3.14.4库的静态编译的探索。
查看一下这个项目的源代码, 发现在src目录下有很多的.mk和patch文件, 各个名字代表了各个库, 如atk.mk和atk-1-DllMain.patch代表了这几个文件是用于编译atk的静态库的。.mk文件指明了库的下载地址, 以及具体的configure和make的过程, 而.patch文件则是对应的库的补丁。
首先, 去msys2里面的源(http://mirrors.ustc.edu.cn/msys2/REPOS/MINGW/i686/)去下载并配置gtk+的动态编译的Bundle包, 如下图:
从图上面可以看出, 要想编译GTK+的程序, 一共需要依赖这么多的库。但是, 这中间只有atk、gdk_pixbuf、glib、libepoxy、pango和最终的GTK+库需要自己去重新编译成静态库。同时, 还要根据mingw32.files.tar.gz安装python3和libxml并解决相关依赖。至于编译环境, 推荐最新版本的mingw-w64, 以及msys或者msys2, 避免出现编译错误。
然后配置编译环境。安装msys到D;\, 如D:\msys。解压mingw-w64到D:\, 如D:\mingw32。然后逐个解压从msys源下载下来的库, 如mingw-w64-i686-zlib-1.2.8-9-any.pkg.tar.xz, 然后进入解压后的目录, 找到lib文件夹下面的*.dll.a, 将其删除, 然后将解压后的目录与D:\mingw32目录合并。除了需要额外静态编译的库之外, 其他的库也同样处理, 包括python3和libxml以及相关依赖。但是, gettext库除了与D:\mingw32合并之外, 还需要将include和lib目录与D:\mingw32\i686-w64-mingw32下面的include和lib目录合并, 否则会在configure时报错。
接下来逐个去编译atk、gdk_pixbuf、glib、libepoxy、pango和GTK+库。
编译libepoxy库
根据MXE项目里面src目录下面的.patch进行相应处理。但是libepoxy中gl.h文件需要特别处理一下。
从上面可以看出, 程序在编译阶段就默认链接的是动态库, 因此, 需要在#endif /*_WIN32*/后面添加如下语句进行覆盖:
#define APIENTRY
#define GLAPIENTRY
#define EPOXY_IMPORTEXPORT
#define EPOXY_CALLSPEC
#define GLAPI
#define KHRONOS_APIENTRY
#define KHRONOS_APICALL
将dispatch_wgl.c中的DllMain函数改名, 否则后面链接时会产生符号重定义的错误。
然后根据.mk文件去configure、make、make install。
编译gdk_pixbuf库
除了按照.mk和.patch处理外, 还需要在configure阶段在CFLAGS中定义GDK_PIXBUF_STATIC_COMPILATION宏, 让编译器静态编译。
编译glib库
这个库比较麻烦, 首先, 打补丁的地方较多, 其次, 有两处都默认采用了动态编译。
gmodule.h中的部分代码如下:
与libepoxy一样, 需要在#endif /*!G_PLATFORM_WIN32*/后面添加一句:
# define G_MODULE_EXPORT
然后, 除了按照.mk和.patch处理外, 还需要在configure阶段在CFLAGS中定义GLIB_STATIC_COMPILATION宏, 让编译器静态编译。
编译atk库
除了按照.mk和.patch处理外, 还需要在configure阶段在CFLAGS中定义ATK_STATIC_COMPILATION宏, 让编译器静态编译。
编译pango库
因为这个库是依赖glib库的, 因此除了按照.mk和.patch处理外, 还需要在configure阶段在CFLAGS中定义GLIB_STATIC_COMPILATION宏, 让编译器静态编译。
编译GTK+库
除了按照.mk和.patch处理外, 还需要在configure阶段在CFLAGS定义上述的各种宏, 让编译器静态编译, 还要指定具体LIBS=-lgdk_pixbuf-2.0 -lgobject-2.0 -lglib-2.0 -lpango-1.0 -latk-1.0 -lcairo -lcairo-gobject -lpangocairo-1.0 -lintl -lgmodule-2.0 -lgio-2.0 -lgmodule-2.0 -lglib-2.0 -lgobject-2.0 -lintl -lws2_32 -lwinmm -limm32 -luser32 -lgdi32 -luuid -lole32 -lpng -lgdiplus -lpangowin32-1.0 -lffi -lws2_32 -lwinmm -lglib-2.0 -liphlpapi -lole32 -lintl -lws2_32 -lwinmm -lpixman-1 -lfreetype -lfontconfig -lpangoft2-1.0 -lfreetype -lharfbuzz -lexpat -lbz2 -lpng -lmsimg32 -liconv -lshlwapi -lz -lgdi32 -ldnsapi -lusp10 -lgcc -lkernel32 -lmsvcrt, 否则make时会报"undefined reference"错误。
至此, 整个GTK+的静态库也就全部制作完成了。用自己制作的GTK+的静态库编译一个简单的窗体程序, 最终的可执行文件有17M多, strip之后大概15M左右。
其实, 试着去运行一下编译GTK+库时编译的test程序就会发现, 总是会在命令行串口中出现"assert failed"的输出, 这是glib库的输出, 不知道是不是属于BUG。另外, 个人感觉GTK+静态编译出来的程序还是太大了, 同样是静态编译出来的QT的程序, 相比较而言大小就小一些, 大概12M多。
总之, 这次尝试还是值得的。