一.C++中子线程操作JNIEnv环境指针
先声明一个native方法去启动线程:
public native void executePthread();
在C++中进行实现:
extern "C"
JNIEXPORT void JNICALL
Java_com_carey_myndk_MainActivity_executePthread(JNIEnv *env, jobject thiz) {
// 线程
pthread_t thread;
// 创建线程
// 参数1 指向线程标识符的指针
// 参数2 用来设置线程属性
// 参数3 线程运行函数的起始地址
// 参数4 运行函数的参数
pthread_create(&thread, NULL, get_min, NULL);
}
我们创建get_min运行函数:
void* get_min(void* arg) {
for (int i = 0; i < 5; i ++) {
__android_log_print(ANDROID_LOG_INFO, "carey====get_min", "%d", i);
}
JNIEnv *env = NULL;
// 正确调用 AttachCurrentThread
int result = jvm->AttachCurrentThread(&env, NULL);
if (result!= 0) {
// 处理 AttachCurrentThread 失败的情况
__android_log_print(ANDROID_LOG_ERROR, "carey====", "Failed to attach current thread.");
return NULL;
}
__android_log_print(ANDROID_LOG_INFO, "carey====", "%s", "my name is carey.");
// 分离线程,解除关联JVM虚拟机
jvm->DetachCurrentThread();
return NULL;
}
里面我们有一个循环打印,完了通过jvm的AttachCurrentThread方法获取环境变量JNIEnv指针,这里的jvm是JavaVM,JavaVM可以通过重写JNI_OnLoad方法得到:
JavaVM* jvm;
// 当我们应用程序加载完毕之后,虚拟机立马调用该方法
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
// 通过JavaVM获取环境指针
jvm = vm;
return JNI_VERSION_1_6;
}
通过上面方法获取到JavaVM指针,并返回当前JNI的版本号。
当Java虚拟机卸载包含本地代码(通常是用C或C++编写的动态库)的库时,会调用JNI_OnUnload方法:
JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved);
最后Java中调用executePthread方法,查看日志打印:
二. C++中的常量
新建一个java类,并声明一个native方法:
public class NDKCppInterface {
// C++中常量
public native void executeCppConst();
}
在Terminal中执行命令,生成相应的头部.h文件:
如果javah命令找不到,可以在环境变量中添加jdk/bin目录。这时在当前java类同级目录下生成了.h文件:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_carey_myndk_NDKCppInterface */
#ifndef _Included_com_carey_myndk_NDKCppInterface
#define _Included_com_carey_myndk_NDKCppInterface
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_carey_myndk_NDKCppInterface
* Method: executeCppConst
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_carey_myndk_NDKCppInterface_executeCppConst
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
我们把这个.h文件放到cpp/目录下:
同时,我们在cpp/目录下创建对应的.cpp文件com_carey_myndk_NDKCppInterface.cpp:
#include <jni.h>
#include <string.h>
#include <stdlib.h>
#include "com_carey_myndk_NDKCppInterface.h"
#include <android/log.h>
/*
* Class: com_carey_myndk_NDKCppInterface
* Method: executeCppConst
* Signature: ()V
*/
extern "C"
JNIEXPORT void JNICALL Java_com_carey_myndk_NDKCppInterface_executeCppConst
(JNIEnv *, jobject) {
const int a = 100; // 常量
int *p = (int*)&a; // 指针指向a的地址
*p = 200; // 改为200
__android_log_print(ANDROID_LOG_INFO, "carey====", "C语言: %d", a);
}
同时更改CMakeLists.txt文件中添加新增加的cpp文件:
在Java中调用该方法,查看日志:
NDKCppInterface ndkCppInterface = new NDKCppInterface();
ndkCppInterface.executeCppConst();
这里我们看到输出的a值还是100,就是说明这个常量值是不能修改的。
三.指针的引用
我们新增一个方法:
public class NDKCppInterface {
// C++中常量
public native void executeCppConst();
// 指针的引用
public native void executeCppPointer();
}
在.h文件和.cpp文件中分别添加该方法是声明和实现:
JNIEXPORT void JNICALL
Java_com_carey_myndk_NDKCppInterface_executeCppPointer(JNIEnv *env, jobject thiz);
// 定义一个结构体
struct User {
char *name;
int age;
};
// 更新user
void update_user(User **u) { // 二级指针,`u` 是一个指向 `User*` 类型指针的指针。要修改 `u` 所指向的指针(即 `*u`),需要通过解引用 `u` 来实现。
User *user = (User*) malloc(sizeof(User));
user->name = "Carey";
user->age = 100;
*u = user;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_carey_myndk_NDKCppInterface_executeCppPointer(JNIEnv *env, jobject thiz) {
User *user = nullptr;
update_user(&user);
__android_log_print(ANDROID_LOG_INFO, "carey====", "名字: %s, 年龄:%d", user->name, user->age);
delete user;
user = nullptr;
}
我们先用二级指针的方式去更改结构体对象User的数据,查看打印:
下面通过指针的引用去更改User信息,我们改下update_user方法代码:
// 指针引用
void update_user(User* &u) { // `u` 是一个对 `User*` 类型指针的引用。可以直接修改 `u` 所指向的地址。
// 使用new分配内存并初始化
u = new User(); // 调用默认构造函数
strcpy(u->name, "Carey2"); // 使用strcpy复制字符串
u->age = 28;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_carey_myndk_NDKCppInterface_executeCppPointer(JNIEnv *env, jobject thiz) {
User *user = nullptr;
update_user(user); // 传入指针
__android_log_print(ANDROID_LOG_INFO, "carey====", "名字: %s, 年龄:%d", user->name, user->age);
delete user; // 释放内存
user = nullptr;
}
这样我们查看打印:
四.常引用
const修饰符用于声明一个变量或对象为常量,即一旦它被初始化后,它的值就不能被修改。const关键字的使用可以增强代码的可读性和安全性,因为它防止了意外的修改。我们先看一个例子,在NDKCppInterface类中创建native方法:
public class NDKCppInterface {
// C++中常量
public native void executeCppConst();
// 指针的引用
public native void executeCppPointer();
// 常引用
public native void executeCppConstRef();
}
void const_init() {
int a = 100;
int b = 200;
const int &c = a;
// c = 300; // 报错,只读,不能修改
__android_log_print(ANDROID_LOG_INFO, "carey====", "值:%d", c);
const int &d = 200;
__android_log_print(ANDROID_LOG_INFO, "carey====", "字面量:%d", d);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_carey_myndk_NDKCppInterface_executeCppConstRef(JNIEnv *env, jobject thiz) {
const_init();
}
查看日志打印:
当const修饰方法参数时,此参数变量不能被修改:
// 常引用作为函数的参数使用
struct Company {
char *name;
char *address;
int age;
};
void const_func_param(const Company &cp) {
// 不能修改,只读。
// cp.age = 100;
__android_log_print(ANDROID_LOG_INFO, "carey====", "公司地址:%s", cp.address);
}
void const_printf() {
Company company;
company.address = "辽宁沈阳科技园";
company.name = "carey有限公司";
company.age = 10;
const_func_param(company);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_carey_myndk_NDKCppInterface_executeCppConstRef(JNIEnv *env, jobject thiz) {
const_printf();
}
查看打印:
常量指针(Pointer to Const)
常量指针是指一个指针,它指向一个常量数据。这意味着你不能通过这个指针来修改它所指向的数据的值。但是,你可以改变这个指针本身的值,即让它指向另一个地址。看个例子代码:
const int a = 10;
const int* p = &a; // p 是一个常量指针,指向 int 类型的常量
// *p = 20; // 错误:不能通过 p 修改它所指向的常量的值
int b = 20;
p = &b; // 正确:可以改变 p 指向的地址,但 p 仍然指向一个 const int
// *p = 30; // 错误:即使 p 指向了一个新的地址,该地址仍然是一个 const int
指针常量(Const Pointer)
指针常量是指一个指针本身的值是常量,即你不能改变这个指针指向的地址。但是,你可以通过这个指针来修改它所指向的数据的值(前提是指针不是指向常量的)。看下面代码:
int a = 10;
int* const q = &a; // q 是一个指针常量,指向 int 类型的数据
*q = 20; // 正确:可以通过 q 修改它所指向的数据的值
// q = &b; // 错误:不能改变 q 指向的地址
五.总结
今天学习了C++中创建线程、获取JavaVM指针,通过JavaVM指针的AttachCurrentThread方法得到JNIEnv指针;还学习了C++中指针引用和常量。喜欢的可以点赞和收藏,感谢!