放牧代码和思想
专注自然语言处理、机器学习算法
    愛しさ 優しさ すべて投げ出してもいい

Make迁移CMake与跨平台的一点总结

目录

这几天将一个C++绘图库从Make迁移到CMake,并且利用NDK+SWIG支持Android,利用MinGW支持Windows,最后还把作者留下的一个VisualStudio的GUI Demo也转成CLion项目,期间踩了不少坑,解决了许多疑难杂症,过程艰辛,特此记录下来。

CMake-logo.png

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

编译链接……总算没有错误了,运行……出了个白窗口,然后:

Make迁移CMake与跨平台的一点总结3.png

出师未捷身先死啊。

看到gdb给出的信息是:

Signal: SIGSEGV (Segmentation fault)

但是没有任何断点与堆栈信息!不禁无比怀念Java,至少任何错误都是exception,而C++的段错误却不是异常,无从追踪。

既然没有断点,那我就从窗口创建开始追踪起吧。一次次单步,这次发现是一个GDI+对象创建失败,然后整个函数不再往下执行,直接由窗口响应下一个消息。再看了看代码,发现如下逻辑:

Make迁移CMake与跨平台的一点总结2.png

在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)

编译运行,一切正常。

总结

  1. 对Make和CMake都不熟悉,踩坑是必经之路

  2. GUI程序debug很麻烦,但不是不可能,多下日志就行了。

  3. 段错误不可怕,基本上就是野指针。

Reference

http://stackoverflow.com/questions/3571250/wwinmain-unicode-and-mingw

http://stackoverflow.com/questions/6008470/catch-segfault-or-any-other-errors-exceptions-signals-in-c-like-catching-excep

CMake Practice.pdf

知识共享许可协议 知识共享署名-非商业性使用-相同方式共享码农场 » Make迁移CMake与跨平台的一点总结

评论 2

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
  1. #2

    不喜欢CMake,要找一个自定义的东西非常难看。。

    a'b'cabc4年前 (2020-03-16)回复
  2. #1

    不太喜欢cmake 每次编译 总会碰到各种各样的错误 …有时候配个环境要花一天半…

    牙_ChenmxS8年前 (2015-12-21)回复

我的作品

HanLP自然语言处理包《自然语言处理入门》