侧边栏壁纸
博主头像
翻斗

开始一件事最好是昨天,其次是现在

  • 累计撰写 44 篇文章
  • 累计创建 42 个标签
  • 累计收到 2 条评论

JNI(C/C++)使用问题小结

翻斗
2014-04-20 / 0 评论 / 0 点赞 / 1,785 阅读 / 6,336 字

在Android下用JNI做系统层面开发,遇到了一些问题,现在小结一下 。

内存问题

  • 建议使用calloc而不是malloc,真要使用malloc也需要配合memset初始化
  • const char* xx = env->GetStringUTFChars(js, NULL);这里得到的xx虽然是const的,在C/C++语言层面没法delete/free
    ,但是需要用JNI方法 env->ReleaseStringUTFChars(js, xx);来删除其占用的内存
  • new对应deletemalloc/calloc对应free,不要穿插用
  • free别多次调用(出现过一个,在被调用方法中就free掉了后,出来还free了一次,导致几次进出app后报错
    循环内,切记要delete中间生成的LocalReference对象,env->DeleteLocalReference(env, obj); 否则很容易local reference table 溢出报错。

Invalid indirect reference 0xxxx in decodeIndirectRef

这种情况出现,一般就是在前面某些错误没有处理好,一直Pending到后面,导致后面在某刻爆发,真正出现问题的地方往往不是直接报错的地方

下面是一个遇到的例子:

每次执行都在一个绝对正确的地方报错,后来发现是

jfieldID applicationInfoFLAGSYSTEMField = env->GetFieldID(applicationInfoClass, "FLAG_SYSTEM", "I");

这里弄错了,少了一个static,应该为GetStaticFieldID。这里不会马上报错,但是错误延迟叠加,导致后面某个时候会在其他地方报错。(最难调试的就是这种,报错地方其实没错,出错在其他地方的)。

还是那句话,不管是Method还是Field,一定要精准匹配,要不然会出现各种莫名其妙的问题,很难调试。

另一个例子:

一个很麻烦的问题,同样代码获取的TelephoneyManager就没问题,获取的ClipboardManager对象一使用就会报这个错,最后追踪到问题是这段代码因为在子线程中执行的话必须有一个Looper.prepare();

处理Pending Exception

上面提到的,如果在c/c++代码执行的时候,发现前面有未解决的Exception,可能它这会儿就崩溃了,虽然崩溃,但是问题并不在它,而在于它之前的这个Exception

其实,理论上,在每一次可能引起Exception的地方,比如FindClass, getMethodID, callVoidMethod等等,执行后都需要加一句:

if(!env->ExceptionCheck()){
//这里才能执行真正的代码,表示没有出现异常
}

如果不加上这句,那么很容易就报错了,而且也还不知道在哪儿报错,很难调试。

另一个方法ExceptionDescribe()可以配合使用,方便调试的时候使用

if(env->ExceptionCheck()){
     env->ExceptionDescribe();
}

一般,出现问题,会打印出一个类似Java Stack Trace的东东,方便我们查看具体出了什么问题,当然,上线的时候,最好还是关闭此类语句。

Android4.1版本以下的popen系统调用问题

以前遇到过一个诡异问题,具体的忘记了,但是出错原因是popen4.1以下采用的是vfork作为底层,这个API本身就很buggy,建议采用system命令

参考http://stackoverflow.com/questions/8910934/popen-on-android-ndk/10026308#10026308

Also looking in the gingerbread source tree for bionic I did find an implementation of popen. The implementation might not be 100% posix of libc correct, but it is at least functional to some degree.

Using NDK v6, the following example compiles without any problems, and it runs on my android device.

#include <stdio.h>
#include <stdlib.h>
int main()
{
 FILE *fpipe;
 char *command="/system/bin/ps";
 char line[256];
 if ( !(fpipe = (FILE*)popen(command,"r")) ) exit(1)
 while ( fgets( line, sizeof line, fpipe))
 {
   puts(line);
 }
 pclose(fpipe);
}

UPDATE: Seems than popen on pre ICS versions used vfork() instead of fork(), and those vfork() are known to cause stack corruption.
So from ICS onward popen should be save to use, but on earlier android versions it is available but verry buggy.

优雅的全局崩溃处理

重点就是:

  • 崩溃检测
  • longjmp进行跳转

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <err.h>

#include <setjmp.h>

jmp_buf jumper;

void posix_signal_handler(int sig, siginfo_t *siginfo, void *context) {
    puts("Send crash report here.");
    longjmp(jumper, -1);//将-1传给jumper
}

static unsigned char alternate_stack[SIGSTKSZ];

void set_signal_handler(void)
{

    {//这里这么写,貌似是因为如果其他地方出错,是因为stackoverflow
        //那么这里处理信号的逻辑没有堆栈可以存放,会出问题
        //所以加上这个转移堆栈,保证起码处理信号的逻辑能执行
        stack_t ss = {};

        ss.ss_sp = (void*)alternate_stack;
        ss.ss_size = SIGSTKSZ;
        ss.ss_flags = 0;

        if (sigaltstack(&ss, NULL) != 0) { err(1, "sigaltstack"); }
    }

    {
        struct sigaction sig_action = {};
        sig_action.sa_sigaction = posix_signal_handler;
        sigemptyset(&sig_action.sa_mask);

        sig_action.sa_flags = SA_SIGINFO | SA_ONSTACK;

        if (sigaction(SIGSEGV, &sig_action, NULL) != 0) { err(1, "sigaction"); }
        if (sigaction(SIGFPE,  &sig_action, NULL) != 0) { err(1, "sigaction"); }
        if (sigaction(SIGINT,  &sig_action, NULL) != 0) { err(1, "sigaction"); }
        if (sigaction(SIGILL,  &sig_action, NULL) != 0) { err(1, "sigaction"); }
        if (sigaction(SIGTERM, &sig_action, NULL) != 0) { err(1, "sigaction"); }
        if (sigaction(SIGABRT, &sig_action, NULL) != 0) { err(1, "sigaction"); }
    }
}


int main(void) {

 int a=1,*p=NULL;
 set_signal_handler();
 puts("start");
 int jstatus = setjmp(jumper);//longjmp传过来的是-1,不为0
 if(jstatus==0) {//第一次执行是jstatus=0, longjump 后jstatus 不为0  
    puts("processing");
    a=a/(a-1);
    //*p=100;
 } else { // longjump 到这里
    puts("sth bad happen");// 发现已经出现了不可预测的错误
 }
 puts("end");
}

只在2.2系统提示找不到so

遇到过一个问题,明明其他版本的系统都可以的,偏偏2.2这个版本不行,一直UnsatisfiedLinkeError,这个比较难以发现原因。知道最后才发现:链接的时候用到了 LOCAL_LDLIBS := -llog -landroid

这个-landroidandroid-8没有的,所以到了2.2手机上就报错。

其实在编译的时候已经发现有个WARNING了:

Android NDK: WARNING: APP_PLATFORM android-21 is larger than android:minSdkVersion 8 in ./AndroidManifest.xml

一般编译的时候只是提示了这个警告
如果在Application.mk上添加 APP_PLATFORM := android-8
再编译就发现报错了,报错如下:

➜  testjni  ndk-build
[armeabi] Compile++ thumb: encrypt <= test2.cpp
[armeabi] SharedLibrary  : libencrypt.so
/Users/wangshuang/Library/Android/android-ndk-r10e/toolchains/arm-linux-androideabi-4.8/prebuilt/darwin-x86_64/bin/../lib/gcc/arm-linux-androideabi/4.8/../../../../arm-linux-androideabi/bin/ld: error: cannot find -landroid
collect2: error: ld returned 1 exit status
make: *** [obj/local/armeabi/libencrypt.so] Error 1

这才知道原来2.2上没有libandroid.so可以连接进来,而且貌似这个-landroid也用不上,就删掉即可。

ndk目录下执行命令看看

find . -name libandroid.so

果然发现只有android-8目录下没有,其他目录下都有!
如下:

➜  lib  pwd
/Users/wangshuang/Library/Android/android-ndk-r10e/platforms/android-8/arch-arm/usr/lib
➜  lib  ls
crtbegin_dynamic.o libGLESv1_CM.so    libjnigraphics.so  libstdc++.a
crtbegin_so.o      libGLESv2.so       liblog.so          libstdc++.so
crtbegin_static.o  libc.a             libm.a             libthread_db.so
crtend_android.o   libc.so            libm.so            libz.so
crtend_so.o        libdl.so           libm_hard.a
➜  lib  ls ~/Library/Android/android-ndk-r10e/platforms/android-9/arch-arm/usr/lib
crtbegin_dynamic.o libGLESv1_CM.so    libdl.so           libstdc++.a
crtbegin_so.o      libGLESv2.so       libjnigraphics.so  libstdc++.so
crtbegin_static.o  libOpenSLES.so     liblog.so          libthread_db.so
crtend_android.o   libandroid.so      libm.a             libz.so
crtend_so.o        libc.a             libm.so
libEGL.so          libc.so            libm_hard.a

当然,这只是编译时候连接的NDK位置的so,在真机上,进入/system/lib,也能发现2.2上没有lib

# cd /system/lib
# ls -l libandroid.so libskia.so
libandroid.so: No such file or directory
-rw-r--r-- root     root      1134348 2010-07-01 05:04 libskia.so

这是2.3的:

-rw-r--r-- system   system      60856 2008-08-01 20:00 libandroid.so
# ls libskia.so
libskia.so
# ls -l libskia.so
-rw-r--r-- system   system    1118456 2008-08-01 20:00 libskia.so
#

libskia.so这种的在NDK中不存在,但是运行的时候在/system/lib下,所以也是能够使用的。如果使用这种so文件,需要将其拷贝出来放到电脑上,然后-L指定其路径,加上-lskia.so即可)

让VIM支持C++11语法

C++11的时候,VIM总是提示代码在C98中不兼容,看的不舒服,需要改下,让它自动识别C++11的语法。

添加对C++11的支持:

  • 1、首先是得有YouCompleteMe插件,相关基础配置完成

  • 2、 在~/.vimrc中添加:

let g:syntastic_cpp_compiler = ‘g++‘ "change the compiler to g++ to support c11.
  let g:syntastic_cpp_compiler_options = ‘-std=c
11 -stdlib=libc++‘ "set the options of g++ to suport c++11.

  • 3、 在~/.vim/bundle/YouCompleteMe/third_party/ycmd/cpp/ycm/.ycm_extra_conf.py中,修改相关参数:
      将flags数组中的‘-Wc98-compat‘修改为‘-Wnoc98-compat‘

  • 4、 如果要取消对C++11的支持:
    ~.vimrc中去掉添加的两行syntastic配置。
    .ycm_extra_conf.py中改回来,并且在flags数组中将-std=c++11修改为-std=c99

0

评论区