目录
这几天将一个C++绘图库从Make迁移到CMake,并且利用NDK+SWIG支持Android,利用MinGW支持Windows,最后还把作者留下的一个VisualStudio的GUI Demo也转成CLion项目,期间踩了不少坑,解决了许多疑难杂症,过程艰辛,特此记录下来。
Make迁移到CMake
Make是平台相关的,CMake是跨平台的,所以不可能有完全的自动化工具,得靠手写。旧项目是一个多目录结构,只好参考旧的Makefile一个个改写成CMakeLists.txt。写的过程中发现原作者似乎将每个目录都link成一个静态库:
TARGET =libxyz.a
所以我在CMake里也这么干了。
后来迁移demo,在最终link的时候,即使加了
target_link_libraries(bin lib1 lib2 ...)
链接了所有target,还是出现一大堆的undefined reference to function,后来一查,原来不光要有静态库,静态库的顺序也要对,依赖别人的库要在别人前面。
不过我一看这么多个静态库立马傻眼了,又不是我写的,我怎么知道哪个依赖哪个?何况仔细看了看,还出现过相互依赖的情况。
于是跑过去看原作者是怎么用Makefile解决的,一看又傻眼了,这家伙根本就没链接静态库,而是把所有的obj输出到同一个目录,最终统一link为动态库(应该也可以link为静态库,但绝不是link每个目录的静态库)。
感情libxyz.a一点用都没有,白写了那么多CMakeLists.txt,这些目录仅仅是为了组织上好看,不输出lib。于是我上了个file命令:
file(GLOB_RECURSE SRC "../src/*.cpp")
一句话解决了所有问题。
然后项目是Unicode字符集,使用:
add_definitions(-DUNICODE -D_UNICODE)
so与jar
SWIG能生成JNI类,NDK能编译so库,但打包成jar还得自己编译JNI类然后和so打包在一起,方便起见用InteljIDEA开了个Android的Library项目,构建了jar的Artifact。
MinGW
项目有个Windows的Demo,是VisualStudio的项目。不过我更喜欢CLion,所以又开始“MinGW开发Windows GUI程序”的折腾。
GDIPlus
链接还是很简单的:
target_link_libraries(Win32App gdiplus )
Undefined reference to wWinMain
由于是Unicode字符集,所以尝试使用wWinMain,不过MinGW不支持wWinMain,所以依然改用WinMain,反正这个GUI demo也不需要解析命令行。
Segmentation faults
编译链接……总算没有错误了,运行……出了个白窗口,然后:
出师未捷身先死啊。
看到gdb给出的信息是:
Signal: SIGSEGV (Segmentation fault)
但是没有任何断点与堆栈信息!不禁无比怀念Java,至少任何错误都是exception,而C++的段错误却不是异常,无从追踪。
既然没有断点,那我就从窗口创建开始追踪起吧。一次次单步,这次发现是一个GDI+对象创建失败,然后整个函数不再往下执行,直接由窗口响应下一个消息。再看了看代码,发现如下逻辑:
在VisualStudio里,没有问题,但在MinGW下无法编译运行,同时CLion智能地指出,灰色部分根本不会执行。猜测可能是GDI+没有初始化,因为一旦发生段错误,函数就终止执行了,根本不会检查p是否为null。
于是把这段挪到new之前,窗口终于不是一闪就死了。
尝试画个图试试,发现只能画第一笔,之后窗口就死了,不响应重绘事件。这个问题很难追踪,毕竟不能从开头追起,一旦断点下得过早,就根本无法用鼠标作图。于是蹉跎了一天的时间,终于发现一个奇怪的现象:
if (!canvas || m_impl->canvas || isStopping()) { return false; }
这个if根本不会执行,直接段错误终止,窗口重绘失败,但依然能响应鼠标。
为此我将if拆开,加了三句调试语句:
LOGD("!canvas = %d", !canvas); LOGD("m_impl = %d", m_impl); LOGD("isStopping() = %d", isStopping()); if (!canvas || m_impl->canvas || isStopping()) { return false; }
发现控制台只输出第一句,第二句根本不执行就死掉了。
这意味着你无法用!m_impl来解决,因为只要你一访问m_impl,马上就死了。
问题陷入绝境,又蹉跎了几小时。
后来我发现,作者写了个奇怪的东西:
static View* fromHandle(long h) { View* p; *(long*)&p = h; return p; } //!< 转为对象 long toHandle() const { long h; *(const View**)&h = this; return h; } //!< 得到句柄,用于跨库转换
我一下子感觉到,肯定在我不知道的地方,指针经过了一次转换,导致了野指针。进而想到,32位系统的指针是4字节的,而64位的是8字节的。把一个8字节的指针转为4字节的long,然后转为8字节的指针,不就出问题了吗,而我恰好在用TDM-GCC64啊。
于是在CMake里加了个参数:
add_definitions(-DUNICODE -D_UNICODE -DWIN32)
编译运行,一切正常。
总结
-
对Make和CMake都不熟悉,踩坑是必经之路
-
GUI程序debug很麻烦,但不是不可能,多下日志就行了。
-
段错误不可怕,基本上就是野指针。
Reference
http://stackoverflow.com/questions/3571250/wwinmain-unicode-and-mingw
不喜欢CMake,要找一个自定义的东西非常难看。。
不太喜欢cmake 每次编译 总会碰到各种各样的错误 …有时候配个环境要花一天半…