android在Java层对 utf 编码是支持得很好了,非常全面;但当你从事一些c/c++工程的开发时可就没有这么幸运了。
笔者最近在使用v8 javascript 引擎时便碰到了一个问题:

有些用户在昵称中使用了 emoji 表情,v8 引擎内部默认会使用utf-16编码,通过 v8 API 取到这个值之后转为utf-8的字符串,进而通过 JNI 的 API JNIEnv->NewStringUTF 往 Java 传递时会被系统的 checkJNI 给拦截住而报错。原因在为了让字符串中不包含任何 null 字节,JNI 以及 Java VM 内部都是使用的Modified UTF-8格式来编码字符串。

后来找到一个办法可以通过将字符串转为 utf-16 编码后传递给 JNI API JNIEnv->NewString 解决之:

  1. 使用 v8 API 将 utf-8 的字符串转为 utf-16 编码

    size_t utf8_to_utf16(const char *src, const uint16_t **dest) {
     if (src == NULL || dest == NULL) {
         return 0;
     }
     Isolate::Scope scope(Isolate::GetCurrent());
     HandleScope handle_scope;
     Local<String> str = String::New(src);
     String::Value val(str); // String::Value的内部编码是 utf-16
     const size_t len = (val.length()) * sizeof(uint16_t);
     uint16_t* target = (uint16_t*)calloc(val.length() + 1, sizeof(uint16_t));
     if (target == NULL) {
         return 0;
     }
     memcpy(target, *val, len);
     *dest = target;
     return val.length();
    }
  2. 将生成的 utf-16 字符串通过 JNI 传递到 Java 层

    uint16_t *utf16_action = NULL;
    size_t len = utf8_to_utf16(action, &utf16_action);
    jstring jText = (*env)->NewString(env, utf16_action, len);
    if (utf16_action != NULL) {
     free(utf16_action);
    }
    if (len == 0) {
     jText = (*env)->NewStringUTF(env, ""); // 降级到使用NewStringUTF来创建一个""字符串
    }

另外一种解决方法是通过 byteArray 来将 utf-8 编码的字符串传送到java层,详情请参考这篇文章

这是 faywang 新的博客,搬家的想法在心里浮起了很多年。今年的几个项目接近尾声,从周末的时间里挤出点来,终于落实了。 之所以叫搬家,还有另外一层意思,这个网站通过备案,最终 host 在我的 homelab 里。确实是到了家了。 接下来就是把 github 上的历史文章全部迁移回“家”。依然是在 ArchLinux 上敲下这几行字,时光如梭,内心热爱未变。 曾几何时,在升升学生公寓,学弟张伟拉我,一起创办了无窗论坛 nowindow.net,意气风发;还有位当时河海大学的网友,过来我杂乱的寝室,唾沫横飞地讲着一类叫做 Wi-MAX 的扩频通信技术要和我做“校园无线网”。 屋外蝉鸣不息,近四十岁的当口,偶尔感怀:当年的少年,走过人生长河,你们在哪,你们还好吗?

最近遇到了4.x版本 android 上由**__isnanf**引发的血案,过程就不表了。典型出错日志(4.2.2):
```bash
07-18 11:58:20.445 W/ExceptionHandler(15303): Caused by: java.lang.UnsatisfiedLinkError: Cannot load library: soinfo_relocate(linker.cpp:975): cannot locate symbol "__isnanf" referenced by "libmylibrary.so"...
```

在4.2.2版本的 [bionic c](http://androidxref.com/4.2.2_r1/xref/bionic/libm/include/math.h) 中:
```c
#define isnan(x) \
((sizeof (x) == sizeof (float)) ? isnanf(x) \
: (sizeof (x) == sizeof (double)) ? isnan(x) \
: __isnanl(x))
```
在后来的版本中,以[5.1.1](http://androidxref.com/5.1.1_r6/xref/bionic/libm/include/math.h)为例,它变成了:
```c
#define isnan(x) \
((sizeof (x) == sizeof (float)) ? __isnanf(x) \
: (sizeof (x) == sizeof (double)) ? isnan(x) \
: __isnanl(x))
```

我们看下 ndk 中的头文件(以r10e)为例: platform-21 中使用的是 __isnanf,platform-21 以下版本的是 isnanf。

有此可见在低版本的 android 上 __isnanf 不是一定存在的,使用它是不安全的,同时由于 ndk 的不同版本所包含的 libm.so 并没有与具体的 platform上的 libm.so 保持一致,在链接时并不能发现这一问题。所以针对这个问题,有两个方面要注意:

1) 编译完动态库之后用如下命令查看下动态符号表,确认是否引用了 __isnanf 符号:
```bash
arm-linux-androideabi-objdump -T {your_library}
```

2)如果你觉得1) 这种方式麻烦,你可以采用如下方式将 isnan 转为安全的 __builtin_isnan
```c
#include
+#undef isnan
+#define isnan(x) __builtin_isnan(x)
```

在调查以上问题时,还 get 到了一个很偏门的 c 语言小知识:
在一些 libc 头文件会以如下方式(函数名外边套一对圆括号)声明函数:
```c
int (isnan)(double) __NDK_FPABI_MATH__ __pure2;
```

其目的在于抑制宏替换,示例如下:

```c
/* the macro */
#define isdigit(c) ...

/* the function */
int (isdigit)(int c) /* avoid the macro through the use of parentheses */
{
return isdigit(c); /* use the macro */
}
```

机理请见下文:
>7.1.4 Use of library functions

>Any function declared in a header may be additionally implemented as a function-like macro defined in the header, so if a library function is declared explicitly when its header is included, one of the techniques shown below can be used to ensure the declaration is not affected by such a macro. Any macro definition of a function can be suppressed locally by enclosing the name of the function in parentheses, because the name is then not followed by the left parenthesis that indicates expansion of a macro function name. For the same syntactic reason, it is permitted to take the address of a library function even if it is also defined as a macro.

为 git 添加自动补全可以为平时的开发工作提升很多效率。而其方法也非常容易:

1. 下载 git-completion 脚本
```sh
wget https://raw.githubusercontent.com/git/git/master/contrib/completion/git-completion.bash
```
2. 配置自动加载以上脚本
若期望整个系统生效,则在 /etc/profile 中(若为单个用户,则在 ~/.bashrc 中)添加:
```sh
source {git-completion.bash}
```