打印

linux开发丛林历险记

[复制链接]
117|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
博斯|  楼主 | 2018-9-27 12:02 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
这个项目甲方指名要用linux,只好把我的PowerPCB框架从Window移植linux。自从几年前搞过一次linux项目,后面就再也没弄过了,该忘的都忘光了,所有的一切基本从零开始,靠着google和百度,跌跌撞撞各种加班终于在一周内把程序大体移植成功了,还算比较顺利,只有最后一个kcbp的库移植不成功,一运行就coredump,以为是自已程序问题,各种参数穷举不果,实在不行找领导把那个库源码拿过来,折腾了一天,刚刚发现竟然类的构造函数都不能被调用,NND,简直是毁三观啊,感觉在linux平台,很多认为是天经地义的事情,竟然还有变化,还以为g++编译器有问题,下载未果,无意中看到我的程序里有一个类与开发包里的一个类同名,突然感觉被雷劈中,这在window平台屁事没有,跑到linux平台就害人了,把我的类名字一改,全部OK。



花了一天时间在解决这个问题,这要是没有开发包源码,以我在linux平台的开发经验,估计就要抓瞎了。以后知道了,不管啥.so,拿过来至少要nm看看导出,千万别跟自已的程序函数一样,坑爹啊。







linux开发就像一场丛林历险,你永远不知道什么时候,在什么地方,会跳出个什么样的毒虫猛兽。



当你需要记上几十个命令,几百个参数才能用时,你就明白linux就是反人类!



珍惜生命,远离linux!











下面这个**是我很早就找到的一个最有用的文档,我遇到的所有郁闷的问题基本都在这里,把每一条都看清楚,遇到解决不了的问题

把下面的每一条都过一下,可能就解决了。







跨平台的 C++ 代码移植要点

1. 分层设计



   隔离平台相关的代码, 就像可测试性一样, 可移植性也要从设计抓起。一般来说, 最上

   层和最下层都不具有良好的可移植性:

      1). 最上层是 GUI, 大多数 GUI 都不是跨平台的, 如: Win32 SDK 和 MFC

      2). 最下层是操作系统 API, 大部分操作系统 API 都是专用的



   如果这两层的代码散布在整个软件中, 那么这个软件的可植性将非常的差, 这是不言自

   明的。那么如何避免这种情况呢? 当然是分层设计了:

      1). 最底层采用 Adapter 模式, 把不同操作系统的 API 封装成一套统一的接口(如:

          KYLib 库), 至于封装成类还是封装成函数, 要看实际情况而定。如果在开发第

          一个平台时就采用 KYLib, 可以大大减少移植的工作量。



      2). 最上层采用分离界面表现与内部逻辑代码的模式, 把大部分代码放到内部逻辑里

          面, 界面仅仅是显示和接收输入, 即使要换一套 GUI, 工作量也不大。这同时也

          是提高可测试性的手段之一, 当然还有其它一些附加好处。所以即使你采用 QT

          或者 GTK+ 等跨平台的 GUI 设计软件界面, 分离界面表现与内部逻辑也是非常

          有用的。





2. 注意平台的特性

   a. 目录分隔符: 在Windows下用 '\\', 在Linux下用 '/'。



   b. 文本文件换行符: 在Windows下用 "\r\n", 在Linux下用 '\n'。



   c. 在 Windows 中文件名不区分大小写字母, 而在 Linux 中则区分大小写字母。



   d. 在 Windows 中线程可以 suspend 和 resume, 而在 Linux 中则不允许此操作。



   e. 在 Windows 的动态库中, 除非明确指明为 export 的函数外, 其它函数对外都是不

      可见的。



   f. 在 Linux 的共享库中, 所有非 static 的全局变量和函数, 对外全部是可见的。这

      要特别小心, 同名函数引起的问题, 让你查上两天也不为过。



   g. 在 Linux 的共享库中, 如果想绑定共享库里的全局符号(变量, 函数和类等等), 则

      在链接共享库的时候, 添加 gcc 选项 -Wl,-Bsymbolic 即可。



   h. 在 Linux 的共享库中, 如果共享库存取主程序里定义的全局符号, 链接主程序的时

      候, 使用参数 -Wl,--export-dynamic 即可。





3. 最好不要使用编译器特有的特性

   a. 像在 VC 里, 你要实现线程局部存储, 在变量前加一个 __declspec( thread ) 就行

      了, 然而尽管在 pthread 里有类似的功能, 却不能按这种方式实现, 所以无法移植

      到 Linux 下。



   b. 同样 gcc 也有很多扩展, 是在 VC 或者其它编译器里所没有的。如编译成多线程安

      全的选项 -pthread, 此选项在编译源程序和链接时使用。





4. 数据类型差别

   a. 在 VC 中64位整型是 __int64, 而在 Linux 中是 int64_t。



   b. 在 VC 中函数指针默认情况下可以直接赋值给 void* 类型变量, 而在 Linux 中则不

      允许直接赋值, 必须使用 (void*) 强制转换。



   c. 在 Windows 中的原子锁相关函数 InterlockXXX 中的参数类型是 long*,

      而在 Linux 中的原子锁相关函数 InterlockXXX 需要用AT&T内嵌汇编实现。





5. 调用外部库(静态库和动态库)差异

   a. 在 VC 中调用外部库有 .lib 支持, 若是动态库则直接通过 .lib 关联。



   b. 在 Linux 中调用静态库为 .a 文件, 库之间的先后顺序非常重要, 如 libKYLib.a

      和 libkylin.a, 且 kylin 依赖 KYLib, 则在工程中加载库的顺序必须为: 先加载

      libkylin.a, 再加载 libKYLib.a。



   c. 在 Linux 中调用动态库为 .so 文件, 如果有好几个库, 它们之间有一些依赖关系的

      话, 例如 X 依赖 Y, 那么你就要先加载那些被依赖的 Y, 然后加载 X。



   d. 在 Linux 中混合调用静态库和动态库, 如使用 libKYLib.a 和 librc32c.so, 且

      librc32c.so 中使用了 libKYLib.a, 则在加载库时必须先加载 libKYLib.a, 然

      后再加载 librc32c.so。





6. 加载动态库时查找路径顺序的差异

   a. Windows 库搜索路径和顺序

      1). 应用程序目录

      2). 当前工作目录

      3). 系统目录 (%systemroot%, %systemroot%\system 和 %systemroot%\system32),

          如: C:\WINNT\, C:\WINNT\system, C:\WINNT\system32

      4). 路径变量 (系统的环境变量 Path)



   b. Linux 库搜索路径和顺序

      1). 链接时指定的路径, 如: -Wl,-rpath=./ 选项表示编译时 ld 路径

      2). 环境变量 LD_LIBRARY_PATH 指明的路径

      3). /etc/ld.so.cache中的函数库列表

      4). /lib目录, 然后/usr/lib

      5). 当前工作目录





7. 动态库入口函数的差异

   a. Windows 中有 DllMain 入口函数, 而 Linux 中则没有。



   b. Linux 中有特殊函数 _init 和 _fini, 主要是分别用来初始化函数库和关闭的时候

      做一些必要的处理, 我们可以把自己认为需要的代码放到这两个函数里面, 它们分别

      在函数库被加载和释放的时候被执行。具体说, 如果一个函数库里面有一个名字为

      "_init" 的函数输出, 那么在第一次通过 dlopen() 函数打开这个函数库, 或者只是

      简单的作为共享函数库被打开的时候, _init 函数被自动调用执行。与之相对应的就

      是 _fini 函数, 当一个程序调用 dlclose() 去释放对这个函数库的引用的时候, 如

      果该函数库的被引用计数器为 0 了, 或者这个函数库是作为一般的共享函数库被使

      用而使用它的程序正常退出的时候, _fini就会被调用执行。



      C语言定义它们的原型如下:

      void _init(void);

      void _fini(void);



      当使用你自己的 _init 和 _fini 函数时, 会出现命名冲突, 就会得到一个

      "multiple-definition" 的错误, 编译器提示已经存在这个名字, 可以通过几种方式

      来解决:

         1). 自定义 init 函数名字, 比如 myinit 用 -Wl, 选项给 ld 传递此名字:

         gcc ... -Wl,-init=myinit



         2). 当 GCC 编译源程序时, 可以使用选项 -nostartfiles 来使共享库不与系统

             启动文件一起编译

         gcc ... -nostartfiles



         3). 使用上面的函数或 GCC 的 -nostartfiles 选项并不是很好的习惯, 因为这

             可能会产生一些意外的结果。相反, 库应该使用

             __attribute__((constructor)) 和 __attribute__((destructor)) 函数属

             性来输出它的构造函数和析构函数。如下所示:



             void __attribute__((constructor)) x_init(void);

             void __attribute__((destructor))  x_fini(void);



             构造函数会在dlopen()返回前或库被装载时调用;

             析构函数会在这样几种情况下被调用: dlclose() 返回前, 或 main() 返回

             后, 或装载库过程中 exit() 被调用时。



   c. Linux 中的初始化和释放函数不建议使用。



作者:kyee

使用特权

评论回复

相关帖子

发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

412

主题

412

帖子

0

粉丝