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

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进阶--字段访问与方法回调

在上一章(《多语言编程之 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 分钟入门

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 检查成果

阅读更多

Apache Thrift

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线程基础

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* 类型的指针做为参数。如果函数需要多个参数,需要将参数做为结构体,然后将结构体地址做为参数传入。 创建线程时并不能保证新增线程优先于其它线程先运行,这依赖于操作系统的线程实现与调度算法。

阅读更多