存档

作者存档

GBK 字符中的转义符陷阱

2016/07/19 7,469

在工作中很多时候我们会使用 GBK 编码来存储数据。但在有些操作中,例如在解析 JSON/XML 或操作数据库时,会因为 GBK 引发一些问题,导致操作失败或引发异常。 这是因为,GBK 中,有些字符会带有 0x5C 的数据。它本是字符串的一部分,但很可能被当作转义符来处理(‘\’),这就使得操作的数据不正确或引发异常。 这些字符是:

乗俓僜刓匼哱圽塡奬媆峔嶾廫慭怽揬昞朶梊榎橽歕沑漒瀄焅燶猏玕琝甛璡痋盶癨瞈砛碶穃竆筡篭糪絓綷縗繺羂耚肻腬臶臷芢蒤薥蚛蝄蟎衆蟎裓覾譢豛赲踈躙輁郳醆鈂鉢鎈鏫閈闬隲頫颸餦馶骪鯸鮘鳿鵟鸤黒齖

除此之外,还有一些特殊符号,如一些制表符,扩充汉字等,详情请戳《gbk-汉字内码扩展规范编码表》下载,表里凡是在 0x5C 位置的字符都在此列。

qq%e6%88%aa%e5%9b%be20160919114316

 

我们在使用GBK编码时要特别注意这些字符

 

多语言编程之 C and Java: Android NDK

2016/07/11 8,864

NDK(Native Debelopment Kit) 是 Android 提供的一个工具集,它允许在 Android app 使用 native 代码(C/CPP). 它集成了交叉编译器, 使用 mk 文件对平台、CPU 等进行隔离。在不同的平台下,只需要配置相应的 mk 文件即可以编译相就的模块。

1 NDK project 基本结构

ndkprojectFoloer

 

 

 

 

 

2 Android.mk

Android.mk 文件存在于工程路径中的 jni/ 路径下。它可以看作是 GNUMakefile 的一部分,用来向编译器描述如何从 native 源码构建共享库(动态库)。
Android.mk 可以将源码分成不同的模块。这些 模块 可以是静态库、共享库(甚至是可执行文件)。静态库用来生成共享库,共享库则会和 app 打包。

继续阅读

多语言编程之 C and Java:JNI进阶–字段访问与方法回调

2016/07/09 8,407

在上一章(《多语言编程之 C and Java:JNI 30 分钟入门》)中讲解了 JNI 的基本使用。这一章将讨论一些更加实用的用法:如何在 JNI 中访问 Java 对象的成员变量或静态变量,以及回调 Java 中的成员方法或静态方法。

1 JNI 访问 Java 成员变量

1.1 实例

Java 实现

package transvar;
public class TransVar{
    static{
	System.loadLibrary("hello");
    }

    //成员变量(字段)
    private int age = 88;
    private String name = "Jims";

    //静态成员变量
    private static int years = 20;

    private native void modifyInstanceVariable();  
}

这个 Java 类中包含 3 个私有成员变量,名为 age 的 int 型变量, 名为 name 的 String 型变量和一个名为 years 的 int 型静态变量。我们将在使用 C 来访问或修改这些成员变量。

C 实现

JNIEXPORT void JNICALL Java_transvar_TransVar_modifyInstanceVariable
  (JNIEnv *env, jobject obj)
{
    //获取 this object 的类
    jclass thisClass = (*env)->GetObjectClass(env,obj);
    if(thisClass == NULL)
    {
      printf("thisClass null\n");
    }

    //获取字段ID
    jfieldID fidAge = (*env)->GetFieldID(env, thisClass, "age",  "I");
    jfieldID fidName = (*env)->GetFieldID(env, thisClass, "name", "Ljava/lang/String;");
    jfieldID fidYear = (*env)->GetStaticFieldID(env, thisClass, "years", "I");

    //获取字段
    jint age = (*env)->GetIntField(env, obj, fidAge);
    jint years = (*env)->GetStaticIntField(env, obj, fidYear);
    jstring name = (*env)->GetObjectField(env, obj, fidName);
    const char* pszName = (*env)->GetStringUTFChars(env, name, NULL);

    printf("[%d Years Later for  %s] \n", years, pszName);

    (*env)->ReleaseStringUTFChars(env, name, pszName);

    age += years;

    //修改成员变量
    (*env)->SetIntField(env, obj, fidAge, age);
}

1.2 访问 Java 变量

在 JNI 中我们获取了这 3 个变量,并对 age 变量进行了修改。
由这段代码可以看出,访问 Java 成员变量的一般步骤为:

  1. 通过 GetObjectClass() 获取 Java 类的引用。
  2. 通过 GetFieldID()/GetStaticFieldID() 从 Java 类中获取 FieldID 。
  3. 基于 FieldID, 通过 Get<type>Field()/GetStatic<type>Field() 取回变量。
  4. 基于 FieldID, 通过 Set<type>Field()/GetStatic<type>Field() 修改变量。

GetFieldID()/GetStaticFieldID() 原型如下:

jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);

继续阅读

多语言编程之 C and Java:JNI 30 分钟入门

2016/06/29 8,520

1 引言

1.1 简介

在实际项目中,很多时候是以多种语言混合开发的。常用的模式是使用 C/C++ 做底层支撑,然后上层辅之以高级语言。 JNI可以使 native 代码与 Java 对象交互,而不会像 Java 代码中的功能那样受到诸多限制, 往往充作 Java 与 C/C++/Assembly 语言的粘合剂。

JNI 明确分开了 Java 代码与 native 代码的执行,为两者的通信定义了一套清晰的API.避免 native 代码对 JVM 的直接内存引用。

2 JNI 入门

2.1 JNI 调用 C

2.1.1  java 中的 native 方法声明

HelloJNI.java

public class HelloJNI{
    static{
        System.loadLibrary("hello");  //运行时加载本地库 hello.dll(Windows)或libhello.so(Unixes) 或 libhello.dylib(MacOX)
    }

    private native void sayHello();  //声明 native 方法

    public static void main(String[] args ){
        new HelloJNI().sayHello();  //调用本地方法
    }
}

当HelloJNI类加载的时候,静态初始化调用 System.loadLibrary() 来加载本地库 hello.dll(或libhello.so)。如果不能加载 hello 库,会抛出 UnsatisfiedLinkError 异常。
然后我们使用 native 关键字声明了 native 方法 sayHello() ,这表明该方法是使用其它语言实现的。
最后 main() 方法实例化了 HelloJNI 并调用了 sayHello().
然后我们编译这个类:

> javac HelloJNI.java

2.1.2  生成 C/C++ 头文件

使用 javah 创建头文件:

> javah HelloJNI

会得到如下 .h 文件 HelloJNI.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloJNI */

#ifndef _Included_HelloJNI
#define _Included_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloJNI
 * Method:    sayHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

这份自动生成的头文件里声明一个 C 函数 Java_HelloJNI_sayHello:

JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);

这个方法名是由javah转换来的,转换规则为:

Java_{package_and_classname}_{function_name}(JNI arguments)

包名中的 点 会被转换为 下划线
函数中带有两个参数(虽然我们在本例中并没有使用这两个参数)

  • JNIEnv*: 指向 JNI environment ,提供对 JNI 方法的访问
  • jobject: 指向 this Java 对象

2.1.3 C 代码的实现

HelloJNI.c

#include <jni.h>
#include <stdio.h>
#include "HelloJNI.h"

JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv * env, jobject obj)
{
    printf("Hello World!");
}

这个实现将在控制台打印万能的 “Hello World!”
文件中包含了 jni.h 头文件。这个头文件由 java 提供,位于 %JAVA_HOME%/include 路径下(Windows下还需要引用 %JAVA_HOME%/include/win32).

2.1.4 编译 C 库

接下来,编译这个 c 文件:

#Windows gcc
> gcc -Wl,--add-stdcall-alias -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -shared -o hello.dll HelloJNI.c

#Windows VS cl.exe link.exe
> cl /c /I%JAVA_HOME%/include /I%JAVA_HOME%/include/win32 HelloJNI.c
> link HelloJNI.obj /DLL /OUT:hello.dll

2.1.5 检查成果

继续阅读

NDK 无法 Debug的一个问题

2016/06/15 6,672

在使用 Android NDK 进行 native 代码调试时,尽管 Android.mk/Application.mk 与Gradle 中都指定了Debug编译,但 C 代码的断点一直无法命中。 排查后,使用 greadelf(Mac)/readelf(Unix) –header x.so 查看编译出来的 so ,发现其中没有debug信息,那么一定是 ndk-build 的过程中出现了问题。无意中发现ndk-build 编译完成以后,又调用了 strip ,这一步将 so 中的一些无用的符号移除了,当然也包括debug信息。

在 Android.mk 中将 strip 这一步做个处理,使其在debug时不执行:

ifeq ($(APP_OPTIM),debug)
    cmd-strip :=
endif

 

如何在windows下编译cairo

2016/06/14 11,102

cairo  是一个免费的矢量绘图软件库,它可以绘制多种输出格式。cairo 支持许多平台,如  Linux、BSD、OSX等,当然,还有  Windows。
cairo 官网提供了在 windows 下的编译方法,详情可戳这里。尽管它给出了看起来很详细的步骤,但在实际编译中,你有可能会遇到各种各样的问题。

我们先从官网的步骤开始。

  1. 下载并编译 zlib .
  2. 下载并编译 libpng .
  3. 安装 MozillaBuild编译环境。( 注意安装路径下不要有空格) .
  4. 找到 MozillaBuild 的安装路径,找到 VC++ 对应版本的启动脚本,如 start-shell-msvc2015.bat,按官网给出的方式进行修改,定义出 zlib \ libpng 等 lib、include 等的路径 .
  5. 运行修改后的 bat,到 pixman 所在路径下,运行 make 命令进行编译 .
    make -f Makefile.win32 CFG=release

进行到这一步的时候,基本就无法进行下去了。因为在这一步开始就会出现各种各样的错误
下面我们将从这里开始,一步一步完成cairo的编译。

1.  修改MozillaBuild脚本:

  • 打开 VS 版本对应的 bat 文件,注意 MOZ_MSVCBITS 的值 :这里将值改为 32。我们将编译 32 位版本。
  • 打开 start-shell.bat 。在 “REM Reset some env vars and set some others.” 内容下方,按照官网上给出的方式,指定 zlib, libpng 的 lib 和 include 所在路径。
  • 如果你的PC是 64 位,那么 MozillaBuild  会默认使用 64 的 VS 进行编译,可能会引发平台不匹配的问题。此时需要将 “REM Prepend MSVC paths.”  下面的
    SET TOOLCHAIN=64-bit cross-compile

    改为

    SET TOOLCHAIN=32-bit cross-compile

2.  修改 pixman-version.h.in

进入 pixman/pixman 文件夹下,修改 @PIXMAN_VERSION_MAJOR@ ,  @PIXMAN_VERSION_MINOR@  , @PIXMAN_VERSION_MICRO@ 的值为对应的版本号。

这里有一份修改好的文件,版本号为 0.35.1 ,可直接取用

git clone https://git.oschina.net/wandoer/pixmanversionheader.git

3.  编译pixman

运行 start-shell-msvc2015.bat ,转到 pixman 路径下,运行

make -f Makefile.win32 CFG=release

如果成功,则 pixman/pixman 路径下有 release 文件夹,内有 pixman-1.lib

4.  修改Makefile.win32.common

在 cairo/build/ 路径下找到  Makefile.win32.common 文件,指定 zlib 和 libpng 所在的路径( ZLIB_PATHLIBPNG_PATH)。

修改 zdll.lib 为 zlib.lib。

5.  编译 cairo

转到 cairo/ 路径下运行命令:

make -f Makefile.win32 CFG=release

如不出意外,此步应编译成功

6.  提取 include 头文件

在 cairo/ 路径下,添加以下脚本并执行:

mkdir -vp include
cp  ./src/cairo-features.h  include/
cp  ./src/cairo.h 	include/
cp  ./src/cairo-deprecated.h	include/
cp  ./src/cairo-win32.h	include/
cp  ./src/cairo-script.h	include/
cp  ./src/cairo-ps.h	include/
cp  ./src/cairo-pdf.h	include/
cp  ./src/cairo-svg.h	include/
cp	./cairo-version.h	include/

至此,cairo 的编译已经完成。

Apache Thrift

2016/05/26 4,815

1 简介

1.1 概览

Apache Thrift官网
Thrift是一个跨语言的服务开发部署 框架,其最初由Facebook于2007年开发,于2008年加入Apache开源项目。
Thrift 通过编译thrift文件(由Thrift Types 组织的中间语言,用于定义RPC数据结构),生成不同语言的类。这些类负责RPC协议层和传输层的实现。

1.2  优点

相对于XML、Json,Thrift在性能和传输数据压缩上有绝对优势。

2 简单使用

2.1 过程

  1. 下载并编译 thrift编译器 .
  2. 编写thrift文件.需要用到 thrift自定义数据类型 .包括
    1. 基本数据类型
    2. 特殊数据类型(binary)
    3. 结构体
    4. 容器(list,set,map).容器元素可以是服务以外的任何thrift type .
    5. 异常(Exceptions)
    6. 服务
  3. 编译thrift文件,生成可供服务端和客户调用的源码.命令语法如下:
thrift --gen <language> <thrift filename>

2.2 简单实例

2.2.1 编辑thrift文件

代码清单如下:

namespace cpp im
struct Msg
{
    1:i32 idFrom,
    2:i32 idTo,
    3:string content,
}

service transMsg
{
     void trans(1:Msg m);
}

2.2.2 生成cpp代码

使用thrift.exe :

thrift.exe -gen cpp transMsg.thrift

此时会在 “gen-cpp” 文件夹(默认文件夹)下生成相应的cpp代码:

msg_constants.cpp
msg_constants.h
msg_types.cpp
msg_types.h
transMsg.cpp
transMsg.h
transMsg_server.skeleton.cpp

其中 “msg_ types” 和 “msg_ constants” 对数据类型进行了定义,”transMsg” 实现了基本逻辑。”transMsg_ server.skeleton” 则简单实现了一个server端的实例。

2.2.3 实现业务逻辑

新建两个工程,一个做为服务端,一个做为用户端。 将 gen-cpp 文件夹下的文件分别添加到两个工程中,并在工程中添加thrift的静态库(或直接引用源码).
Server端的工程可以直接使用生成的代码。Client端的业务则需要自己实现。
下面简单实现Client端的业务:

int main(int argc, char **argv) {
  int port = 9090;
  std::string strHost("192.168.3.11");

  boost::shared_ptr<TTransport> socket(new TSocket(strHost.c_str(), port));
  boost::shared_ptr<TTransport> transport(new TBufferedTransport(socket));
  boost::shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport));
  transMsgClient client(protocol);

  try
  {
          im::Msg m;
          m.__set_idFrom(10);
          m.__set_idTo(280);
          m.__set_content("Hi, I`m a message. ");

          transport->open();

          client.trans(m);

          transport->close();
  }
  catch (TException  e)
  {
          printf("%s", e.what());
  }

  system("pause");
  return 0;
}

其中strHost为服务器的IP.
继续阅读

Unix线程基础

2016/05/24 8,749

1 线程标识

每个线程都有一个唯一标识:线程ID. 线程ID用 pthread_t 数据类型来表示,该结构体在不同的系统上有不同的实现,某些操作系统将其实现为 unsigned long ,某些操作系统将其实现为结构体。所有可移植代码中不可以对其直接比较,必须使用一个函数来对两个线程ID进行比较。

//返回值:若相等,则为非0;否则,返回0
int pthread_equl(pthread_t t1, pthread_t t2);

不同的实现方式带来的影响就是,不能使用一种可移植的方式来打印该数据类型的值。
获取当前线程:

//Unix
pthread_t pthread_self(void);

//Windows
HANDLE GetCurrentThread(void);

2 线程创建

新增的线程可以通过调用pthread_create 函数来创建

#include <pthread.h>
//thread: 当成功时,指向新增线程的内存单元
//返回值:若成功,则返回0;否则 返回错误编号
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);

新创建的线程从 start_routine 函数的地址开始运行。该函数有一个 void* 类型的指针做为参数。如果函数需要多个参数,需要将参数做为结构体,然后将结构体地址做为参数传入。 创建线程时并不能保证新增线程优先于其它线程先运行,这依赖于操作系统的线程实现与调度算法。

继续阅读