这个库函数应该包含在java的库的路径中(这里有一个专门的java系统变量是处理这个的
java.library.path),对于我们这种自定义的library,在编译java程序时可以传递过去。
-Djava.library.path=/path/to/lib,如果java找不到的话,则会throw一个
UnsatisfiedLinkError的错误。
public class myJni { // save as JNI.java
static {
System.loadLibrary("hello"); // load library libhello.so on UBIX
// at runtime, the library contains
} // a native method called sayHello()
private native void sayHello();
public static void main(String[] args){
new Jni().sayHello(); // Create an instance and invoke the native method
}
}
javac -h . myJni.java:我们来看一下javac -h.
-h <directory> 指定放置生成的本机标头文件的位置
ls:
vimer@host:~/src/test/java$ ls
myJni.class myJni.h myJni.java
hit: 我这是OpenJdk9.其他版本的请自己寻找一下相关的用法。
我们来看一下会生成的相关的头文件。
vimer@host:~/src/test/java$ cat myJni.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class myJni */
#ifndef _Included_myJni
#define _Included_myJni
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: myJni
* Method: sayHello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_myJni_sayHello
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
其中最主要的就是下面这个:
JNIEXPORT void JNICALL Java_myJni_sayHello
(JNIEnv *, jobject);
这个命名规则是Java_{package-and-classname}_{function-name}(JNI-argument)
这里就有一个大名鼎鼎的dot代替下划线的替换。
这两个参数是:
: JNI环境,请注意这个JNI是广泛的特指,而不是 本例的JNI.java中的JNI。
: “this” object in java.
“extern c”是针对c++编译器而言的。它通知c++的编译器这些函数使用c的命名规则而不是c++的 命名规则。
我们首先对应的c程序,不然就算写出来了h文件也没有什么用途。
#include <stdlib.h>
#include <unistd.h>
#include <jni.h> // JNI header provided by JDK
#include "myJni.h" // Generated file from javac
JNIEXPORT void JNICALL Java_myJni_sayHello(JNIEnv *env, jobject thisObj){
printf("Hello, Jni world\n");
return;
}
使用命令去编译这个c程序生成动态加载库:
vimer@host:~/src/test/java$ gcc -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -shared -o libhello.so myJni.c
vimer@host:~/src/test/java$ ls
libhello.so myJni.c myJni.class myJni.h myJni.java
vimer@host:~/src/test/java$ nm libhello.so | grep say
000000000000061a T Java_myJni_sayHello
vimer@host:~/src/test/java$ java -Djava.library.path=. myJni
Hello, Jni world
可以使用tool nm查看一下动态库有没有定义这个符号,我们可以清楚地看到,为T(定义)。 然后使用java命令就可以将动态库的方法加载进去了。
将上面的java代码保持不变,还是使用这个。唯一的不同是使用:
#include <iostream>
#include <jni.h>
#include "myJni.h"
using namespace std;
// Implementions of native method sayHello()
JNIEXPORT void JNICALL Java_myJni_sayHello(JNIEnv *env, jobject thisObj){
cout << "Hello jni world from c++" << endl;
return;
}
接下来不变:
g++ -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -shared -o libhello.so myJni.cpp
vimer@host:~/src/test/java$ java -Djava.library.path=. myJni
Hello jni world from c++
这一个例子是揭示如何利用一个java代码同时使用c或者c++的程序。这次的java代码就有几处 不同。
public class myJniCpp { // save as JNI.java
static {
System.loadLibrary("hello"); // load library libhello.so on UBIX
// at runtime, the library contains
} // a native method called sayHello()
private native void sayHello();
public static void main(String[] args){
new myJniCpp().sayHello(); // Create an instance and invoke the native method
}
}
接着产生myJniCpp.h文件。
vimer@host:~/src/test/java$ javac -h . myJniCpp.java
详细看一下这个文件的内容。
cat myJniCpp.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class myJniCpp */
#ifndef _Included_myJniCpp
#define _Included_myJniCpp
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: myJniCpp
* Method: sayHello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_myJniCpp_sayHello
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
当然,我们最关心的应该还是sayHello()方法的实现。
然后我们利用这个头文件分别实现对应的c和c++的头文件。什么?需要吗?按照我的理解是这样的 一个java服务程序,当然支持c或者c++的程序最好了,那既然上面的方法我们分别实现了对应的 native代码,说明肯定有冲突的地方。
这三个文件各自有自己的用途,我们先看一下myJniCppImpl.h, 这是一个C++的头文件。
#ifndef _MY_JNI_CPP_IMPL_H
#define _MY_JNI_CPP_IMPL_H
#ifdef __cplusplus
extern "C" {
#endif
void sayHello();
#ifdef __cplusplus
}
#endif
#endif
cat myJniCppImpl.cpp:
#include <iostream>
#include "myJniCppImpl.h"
using namespace std;
void sayHello(){
cout << "hello wrold from c++!" << endl;
return;
}
很明显这里就是在c++的程序中实现了c库的函数,其实这里很复杂的,涉及到了编译器 对c++ c函数的编译声明方式。 cat myJniCpp.c:
#include <jni.h>
#include "myJniCpp.h"
#include "myJniCppImpl.h"
JNIEXPORT void JNICALL Java_myJniCpp_sayHello(JNIEnv *env, jobject thisObj){
sayHello();// invoke c++ function
return;
}
这个c程序负责与java程序的交互。下面是编译参数:
g++ -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -shared -o libhello.so myJniCpp.c myJniCppImpl.cpp
如果不注意的话则会出错,比如下面这个。
In file included from myJniCpp.c:3:0:
myJniCppImpl.h:5:10: error: language string ‘"c"’ not recognized
extern "c" {
^~~
In file included from myJniCppImpl.cpp:2:0:
myJniCppImpl.h:5:10: error: language string ‘"c"’ not recognized
extern "c" {
^~~
记住,调用c库的函数时一定注意这个错误,这个错误很牛逼,就是: 你把该大写的”C”写成了小写”c”.
java -Djava.library.path=. myJniCpp
hello wrold from c++!
我们总结一下这个过程,首先在java程序中使用native关键词说明本java方法使用底层库 的一个函数。然后我们使用c++实现了具体的方法,后面使用c程序invoke c++的实现。
frameworks存在大量的java代码,但是vscode原来是不支持java原生跳转的。 但是在这个URL中找到了这个方法,目前看是解决了这个问题。
frameworks/base 里面大多是 Java 代码,VS Code 打开后,你需要告诉它对应的 classpath。这个可以通过创建一个 build.gradle 解决。
# frameworks/base/build.gradle
apply plugin: 'java'
sourceSets {
main {
java {
srcDirs 'core/java'
srcDirs 'graphics/java'
}
}
}
目前的情况是添加了这个文件后,frameworks/base/media/下的java代码是可以跳转啊。
后面如果再不满足的话,自己手动添加吧。
首先使用
./emulator -show-kernel -verbose -nocache -wipe-data
启动模拟器:
adb logcat --regex="ZygoteInit |Entered the Android|StartServic es | phase |set rtc to|Setting time"
thread应该是c++的高级特性了,初学者基本涉及不到对该特性的掌控。 但是目前在工作中已经涉及到了,早点记录下来有助于自己的进步。
提前说明一点,在linux下c++的多进程多依赖pthread库,所以如果以 命令行的形式编译thread相关的程序,需要手动指定pthread的库。例如
g++ -g bar.cpp -o bar -lpthread -std=c++11
这里借花献佛here 更强悍的资料 从学习的路线图来说,使用多线程编程,需要对并行编程(parallel program)有 一个基本的了解
首先借助一个例子:
#include <unistd.h>
#include <pthread.h>
void printids(const char *s){
pid_t pid;
pthread_t tid; // thread id
pid = getpid();
tid = pthread_self();
printf("%s pid %u tid %u (0x%x)\n", s, (unsigned int)pid,
(unsigned int)tid, (unsigned int)tid);
}
void *rhr_fn(void *arg){
printids("new thread:");
return NULL;
}
int main(){
int err;
pthread_t ntid;
err = pthread_create(&ntid, NULL, rhr_fn, NULL);
if (err != 0)
printf("can not create new thread");
printids("main thread:");
pthread_join(ntid, NULL);
return 0;
}
在涉及到thread相关的事务时,我们最好使用另一个计算机词汇去进行描述:routines.
pthread_create();
pthread_exit(status);
pthread_cancel(thread);
pthread_attr_init(attr);
pthread_attr_destroy(attr);
所有的routines(与thread相关)都是从main()函数开始的。pthread_create
就是用来创建新的thread并执行它。其中有四个参数:
void (*)的形式传递,可以使用NULL
作为默认参数。limit可以查看一个进程最多可以创建多少线程。 ulimit -Hu
pthread_attr_init和pthread_attr_destroy分别作为与pthread_attr相关的生死函数。
大体上来说,thread相关的属性包括:
BTW, 这里与thread相关的调度算法包括:FIFO,RR(round-robin),OTHER(OS)
pthread_exit()主要负责这件事。那么什么情况下可以终止thread呢?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#define NUM_THREADS 5
void *print_hello(void *threadid){
long tid;
tid = (long) threadid;
printf("hello, world, It is me, thread #%ld!\n", tid);
pthread_exit(NULL);
}
int main(){
pthread_t threads[NUM_THREADS];
int rc;
long t;
for (t = 0; t < NUM_THREADS; t++){
printf("In main, Createing the thread %ld\n", t);
rc = pthread_create(&threads[t], NULL, print_hello, (void *)t);
if (rc){
printf("error: %d\n", rc);
exit(-1);
}
}
}
我们来看一下结果:
vimer@host:~/src/test/c++/thread$ gcc -g thread_create.c -o test -pthread
vimer@host:~/src/test/c++/thread$ ./test
In main, Createing the thread 0
In main, Createing the thread 1
hello, world, It is me, thread #0!
In main, Createing the thread 2
hello, world, It is me, thread #1!
In main, Createing the thread 3
hello, world, It is me, thread #2!
In main, Createing the thread 4
hello, world, It is me, thread #3!
vimer@host:~/src/test/c++/thread$ ./test
In main, Createing the thread 0
In main, Createing the thread 1
In main, Createing the thread 2
hello, world, It is me, thread #0!
hello, world, It is me, thread #1!
hello, world, It is me, thread #2!
In main, Createing the thread 3
In main, Createing the thread 4
hello, world, It is me, thread #3!
hello, world, It is me, thread #4!
看看,thread的id是不一样的。
首先,我们考虑下之前的有关这类的定义。有一个”左值”(lvalue)是一个指针能够指向的内存位置,那么,” 右值”(rvalue)是不需要指针指向的一类东西。从生命周期的角度考虑,一个rvalue的使用范围很短, 且一个rvalue不指向任何地方;而lvalue自变量进行声明后一直到消亡。
int x = 666;
以这个赋值为例,我们的”666”可以看成一个字符常量,这个字符没有指向任何的地址,所以我们可以 把“666”看成rvalue.而这个数据赋值给了变量x,这个变量x就是一个内存地址的别名,我们可以通过指针 去访问这个x(地址)的内容,所以它是一个lvalue。
int *y = &x; //
我们利用取址符”&”,将变量x的地址取出来,然后赋值给int *的变量y,这里也许有人会问,既然是存储 变量的地址,那么我们使用int y不行吗?至少在我看来,最明显的问题就是类型不一致,因为我们得保障 地址类型(*)的接受变量是一致的。结合我们今天要介绍的知识就是,&的操作数是lvalue,但是产生的 结果是rvalue.
其实,这里就总结出来了左值和右值的一般定义就是: 在”=”赋值符左边的变量就是左值,相反在右边的就是 右值。
int y;
666 = y;
这个程序就算新手也不会这么写,那错在哪里呢?是把y保存到地址666位置上引起系统奔溃吗?从技术角度看 就是,”666”就是一个文字表达式常量(literal constant),实际就是,y没有保存到任何地方(因为编译器 根本识别不了)。
先看一个例子:
int setValue(){
return 6;
}
//...
setValue() = 3;
很明显,这是error,使用上面的例子我们知道了setValue函数的返回值就是一个右值。但是:
int global = 100;
int& setGlobal(){
return global;
}
// ...
setGlobal() = 400;
这个代码是ok的,注意,如果我不指明的话,上面的代码是不是看不出来是c++? 那么,假设这是c的话一点不突兀吧,再想一想自己过去在c++中参数的引用,不就是 这个样子呢?
这个代码返回了一个引用。引用就是一个指向已经存在的变量地址,上述代码就是(global), 因此这也是一个lvalue。注意这里的”&” 不是取地址符而是引用符号。
当然这个用法还是很少用的。
接着看一个例子。
int y = 10;
int& yref = y;
yref++; // Now y is 11
这里,yref的类型是int&,而且是变量y的引用,这个时候就可以正常的改变y的值 通过改变yref.现在可以明确一点的就是,引用必须指向一个具有明确位置的对象,y在这里是满足的,ok。
void func(int& x){
}
int main(){
func(10);// error!
// x = 10;
// func(x);
// is ok
}
output:
rvalue.cpp:14:9: error: cannot bind non-const lvalue reference of type ‘int&’ to an rvalue of type int’
func(10);
^
其实这里涉及到了一个rvalue to lvalue conversion.而这种转化是错误的。但是,哈哈,又有意外:
const int& ref = 10; // It is ok
下面的代码也是ok的:
void func(const int& x){
}
int main(){
func(10);
}
为什么这样一改就可以呢?从来没有技术方面的考虑,就是从一个常理考虑的: 使用const修饰表明你不想修改这个参数,对不对。那么,你传递过去一个字符常量是没问题的。
在英文中,”&&”是 Double address operator或者 double ampersand, 假设:
int&& a; // "a" 是一个 r-value reference
而且在常规的用法中,”&&”是经常在函数中被声明一个参数, 再次澄清一遍,右值就是没有内存地址的一个符号。上面已 经说过,右值引用的不能够指向左值。[1]