zoukankan      html  css  js  c++  java
  • 一个令人蛋疼的NDK链接错误

    背景

    我们APP的引擎包engine.so。包括了A、B、C三个project。但每次都是源代码形式编译,导致svn上存在多份同样代码拷贝。

    很不科学。

    。。核心的Bproject由我维护。整个SO编译project由多个人维护。

    于是乎偶进行了一次升级:将B源代码从soproject中解耦:将B打成一个静态库,然后编译So的时候链接静态库。


    開始行动

    基本思路:将B的源代码包到一个guide_b外壳project中。ndk-build生成guide_b.so 的同一时候诱导生成libB.a静态库,然后这个libB.a能够公布。
    android的编译文件夹实在蛋疼。eclipse下设置路径难用的非常。还是习惯命令行下的ndk-build。可是ndk-build的前提是:当前路径下必须有一个jni文件夹,且它里面有一个Android.mk文件,以及srcxx子文件夹。里面放了源码

    为了遵循android ndk编译这个蛋疼的规定,并且又不破坏Bproject的项目结构(旧的支持xcode和vs编译),在build文件夹下添加一个android子文件夹。创建Android.mk文件,然后通过python脚本将所需源代码文件复制到android/jni下,全部这些操作通过一个run.bat批处理脚本串联,build完毕以后删掉拷贝的源代码和编译中间结果。

    将python引入编译很灵活。

    踩雷了

    拿到编译出的libB.a,放入engine.so编译project中,改动mk文件,头部增加静态库预编译段,
    include $(CLEAR_VARS)
    
    LOCAL_MODULE := BModule
    
    LOCAL_SRC_FILES := libB.a
      
    include $(PREBUILT_STATIC_LIBRARY)
    在so编译部分载入BModule模块:LOCAL_STATIC_LIBRARIES := BModule
    编译so很顺利。可是拿到APPproject中。傻眼了build以后的包仅仅有地图文字,没有底图了。

    泪奔了,libB.a和engine.so的编译过程都自我感觉很之良好啊。。。尼玛。仅仅能不断自我打击。暗示一定什么环节出问题了。。

    没有crash,文字标注还有,可是底图一直渲染不成功。

    。。


    排雷的过程

    1)将B的源代码放入SO编译project,终于so包没问题。仅仅能怀疑自己的libB.a编译有问题或者链接有问题咯,于是进行第一个尝试:

    不直接进行源代码编译。而是通过ndk自带的arm-linux-androideabi-ar.exe工具,将源代码编译时产生的一系列.o文件。手工编译成.a。然后链接这个.a,发现build的so包还是有问题。

    source =>	*.o	=>	engine.so
    
    *.o	=>	libB.a	=>	engine.so
    	ar
    上述两种路径:第一条表示源代码编译,ok;第二条是源代码编译的中间结果.o文件。手工通过ar打包成 libB.a。然后链接libB.a,就有问题。

    真是见鬼了。!。

    2)躲只是了,仅仅能source中添加log,第一次build成libB.a,然后第二次build成engine.so。最后复制到androidproject中。build APK。

    source =>	libB.a	=>	engine.so	=>	apk.

    整个蛋疼的定位过程得益于windows的批处理脚本。能够实现半自己主动化。

    不断反复这个过程,不断调整log精度。终于定位究竟图瓦片绘制失败的问题:坐标转化函数GetGeoRect的结果错误。导致绘制时候取不到数据

    定位问题的解决办法

    尼玛,疑问重重:源代码编译没问题。build成静态库,然后再链接就有问题。代码没有修改,为何单单这个核心函数出问题呢?
    进一步验证:
    GetGeoRect是一个类静态函数。写一个main.cpp,測试libB.a中的该函数是否正确,显式传入制定的參数,build成可运行程序,然后推到手机上运行,详细參见http://blog.csdn.net/ryfdizuo/article/details/28891649 结果函数运行结果正确。

    。说明libB.a内这些函数本身没有问题。

    问题出在so包的链接阶段

    跟组内一经验丰富的哥们讨论,那天恰好周五。下班前还是没结果。。

    。晚上回去后回一直在回忆编译的整个过程。想起他无心的一句话:“是不是可能有反复的定义啥的“。最终想到了一个问题,Aproject里面Bproject的两个头文件。当时为了解耦其它人将两个头文件反复拷贝了一份,(明显触犯了DRY原则)例如以下, yy.h中包括了静态函数的GetGeoRect定义,vv.h中包括了render_config_t结构体定义,而GetGeoRect中使用了render_config_t结构体。


    我近期一次B模块升级。更新了vv.h中的render_config_t结构体,内部添加了一个256的char数组。

    附图新旧vv.h头文件里的render_config_t结构体:

    旧的:新的:

    第二天周六,按耐不住奔到公司,更新A模块中的vv.h头文件,build出的so包最终正确了。

    总算是找到问题所在了:

    A和Bproject中的vv.h和yy.h文件反复。B中vv.h文件近期被更新过。

    1)当A、Bproject均採用源代码编译时,终于SO中的GetGeoRect函数内部使用了最新的render_config_t结构体布局(编译器可能依据文件的改动时间等等作为比对条件吧),因此底图绘制正确。

    2)可是当Bprojectbuild成静态库libB.a时。此时build成SO时,GetGeoRect函数定义採用了Aproject中源代码(编译器可能更加信赖源代码吧)。因此render_config_t也採用了旧的内存布局。因此当调用SO执行时。传入GetGeoRect函数的render_config_t的对象採用最新的内存布局,可是内部实际上是按旧的结构体解析和执行,当然结果就全然错了。


    一句话总结问题

    文件反复拷贝 =》 导致两个地方文件更新不同步 =》导致同一个结构体有两份定义。两种内存布局 =》 导致SO中的全局函数中,libB.a中没有被反复定义的函数採用了新的结构体布局。被反复的函数则採用了旧的内存布局 =》终于结果:传入GetGeoRect函数之前的结构体是新布局,函数内部按旧布局解析,全部參数错乱。

    附录


    静态库,仅仅是.o文件的集合打包。
    动态库文件:没有作用域区分,全部函数都以唯一的全局函数形式存在,C++的函数会被name-mangling处理,假设我们希望直接通过函数名获取so中的函数地址,则使用extern C包裹防止函数名被改动。通过objdump工具能够验证。



  • 相关阅读:
    【java】对象赋值给另一个对象
    spring boot系列(五)spring boot 配置spring data jpa (查询方法)
    Spring Data JPA 查询
    Spring Data JPA 介绍
    OpenID简介
    OAUTH协议介绍
    URL encoding(URL编码)
    RESTful 介绍
    spring boot系列(四)spring boot 配置spring data jpa (保存修改删除方法)
    spring boot 启动报 java.lang.NoClassDefFoundError: ch/qos/logback/core/spi/LifeCycle 错误
  • 原文地址:https://www.cnblogs.com/liguangsunls/p/6978745.html
Copyright © 2011-2022 走看看