概述
全称:Native Development Kit,可让您使用C和C++等语言以原生代码实现应用的各个部分.
https://developer.android.google.cn/ndk
文件类型:
1
2
3
|
.o 等同于.Obj
.a 等同于.lib
.so等同于.dll
|
编译器
目前主要是clang编译器,Gcc编译器已被移除.
下载NDK,里面有Clang编译器.
编译示例:
将Clang编译器所在目录加入到环境变量path中,这个目录中有很多做好的cmd文件(如:i686-linux-android16-clang.cmd).
假如说编译Hello.c
- 编译
1
|
i686-linux-android16-clang -c -o Hello.o Hello.c
|
- 链接
1
|
i686-linux-android16-clang -o Hello Hello.o
|
生成的Hello文件格式为Elf.
- 将生成的文件传到手机上运行
1
2
3
4
5
6
7
|
//传文件到手机上
adb push Hello /data/local/tmp
//进入shell,修改/data/local/tmp/Hello路径权限为可执行
adb shell
chmod a+x /data/local/tmp/Hello
//运行程序
/data/local/tmp/Hello
|
注意
若要在手机上运行Hello这个程序,需要使用shell命令.若手机有Root权限,该Hello文件执行时也会有Root权限,但是apk文件不会具有Root权限,它只能得到一个带root权限的shell.
通用编译脚本
Makefile
假如说10个人开发一个软件,用VS是不行的.
Android NDK工具包中有makefile解释器.
效率:
当生成的某个obj文件没有发生变化时,不重新生成,提高编译效率,可通过判断obj生成时间和.c文件的生成时间,若前者大于后者,则说明不需要重新生成,否则就需要重新生成obj文件.
语法参考:
https://github.com/seisman/how-to-write-makefile
Makefile语法参考.pdf
ndk-build脚本:
手工编译示例:
- 将ndk-build.cmd所在的路径添加到系统环境变量中.
- 将要编译的文件放到一个名为jni或cpp的文件夹中.
- 新建一个Android.mk文件.
- 新建一个Application.mk文件.
- 编译.
- 编译清理.
ndk-build手工编译.7z
利用AndroidStudio来编译:
- AS新建一个空工程.
- 将上面的jni或cpp文件夹拷贝到新建的工程中.
- 在AS中选择Link C++ Project.
这个地方的右键,其实是在编译脚本中加了一段话.
- 指明NDK路径,下载了NDK,默认即可.
- 指定编译的平台.
- Ctrl + F9 编译工程即可.
ndk-build_AS编译.7z
注意
用ndk-build编译C++程序需注意,Cmake编译无需注意.
需要设置STL C++异常 Rtti的开关.
Cmake编译(Android主推)
- 新建一个工程,这个工程默认就是CMake编译.
- 将要编译的文件,放到工程的cpp目录中.
- 添加编译链接内容.
- 指定编译的平台.
- Ctrl + F9编译工程即可.
Cmake编译.7z
静态库
又称归档文件,当一个工程链接静态库时,只会链接需要的Obj.
编译
手工编译
编译静态库的程序为llvm-ar.exe.
1
2
3
|
//mou1.h
#include <stdio.h>
void mou1();
|
1
2
3
4
5
6
|
//mou1.c
#include "mou1.h"
void mou1(){
printf("Hello mou1\n");
}
|
1
2
3
4
5
6
7
|
//main.c
#include "mou1.h"
int main(){
mou1();
printf("Hello Main\n");
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
//Makefile
NDK_PATH := D:/AndroidSDK/ndk/21.4.7075529/toolchains/llvm/prebuilt/windows-x86_64/bin
BUILD_ABI := i686
BUILD_PLT_VERSION := android21
BUILD_CLANG := $(BUILD_ABI)-linux-$(BUILD_PLT_VERSION)-clang
BUILD_AR := $(BUILD_ABI)-linux-android-ar
BUILD_MODULE_NAME := main
BUILD_LIB_NAME := libmou.a
BUILD_FLAGS := -c
BUILD_LINKER_FLAGS := -o $(BUILD_MODULE_NAME) main.o mou1.o
all:
$(BUILD_CLANG) $(BUILD_FLAGS) main.c mou1.c
$(BUILD_CLANG) $(BUILD_LINKER_FLAGS)
lib:
$(BUILD_AR) r $(BUILD_LIB_NAME) mou1.o
install:
adb push $(BUILD_MODULE_NAME) /data/local/tmp
adb shell chmod a+x /data/local/tmp/$(BUILD_MODULE_NAME)
adb shell /data/local/tmp/$(BUILD_MODULE_NAME)
clean:
del *.o $(BUILD_MODULE_NAME) $(BUILD_LIB_NAME)
|
静态库手工编译.rar
ndk-build
可以放到AS中进行编译.
- 将要编译的源文件放到jni文件夹中.
- 新建一个Android.mk文件.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
#编译静态库
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
#存储要构建的模块的名称
LOCAL_MODULE := mou
#列举源文件
LOCAL_SRC_FILES := mou1.c mou2.c
#包含编译脚本
include $(BUILD_STATIC_LIBRARY)
#使用静态库
include $(CLEAR_VARS)
LOCAL_MODULE := main
LOCAL_SRC_FILES := main.c
LOCAL_STATIC_LIBRARIES := mou
include $(BUILD_EXECUTABLE)
|
- 新建一个Application.mk文件.
1
2
|
APP_PLATFORM := android-16
APP_ABI := x86_64
|
- cmd运行ndk-build进行编译.
静态库ndk-build.7z
Cmake编译
假设编译的为Cpp文件,需要注意头文件加 extern “C”.
- 新建一个工程.
- 在cpp文件夹新建源文件.
- 指明要静态编译的文件.
- 修改编译脚本.
- 直接编译即可.
Cmake使用第三方静态库
优点
软件静态链接静态库的时候,只链接需要的代码.
缺点
某一个.o文件有Bug,其他已经链接了该静态库的软件需要重新编译链接,维护性差.
动态库
编译
手工编译
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
//Makefile
NDK_PATH := D:/AndroidSDK/ndk/21.4.7075529/toolchains/llvm/prebuilt/windows-x86_64/bin
BUILD_ABI := i686
BUILD_PLT_VERSION := android21
BUILD_CLANG := $(BUILD_ABI)-linux-$(BUILD_PLT_VERSION)-clang
BUILD_AR := $(BUILD_ABI)-linux-android-ar
BUILD_MODULE_NAME := main
BUILD_LIB_NAME := libmou.so
BUILD_FLAGS := -c
BUILD_LINKER_FLAGS := -o $(BUILD_MODULE_NAME) main.o $(BUILD_LIB_NAME)
all:
$(BUILD_CLANG) $(BUILD_FLAGS) main.c
$(BUILD_CLANG) $(BUILD_LINKER_FLAGS)
lib:
$(BUILD_CLANG) -c mou1.c mou2.c
$(BUILD_CLANG) -o $(BUILD_LIB_NAME) -fpic -shared mou1.o mou2.o
install:
adb push $(BUILD_LIB_NAME) /data/local/tmp
adb push $(BUILD_MODULE_NAME) /data/local/tmp
adb shell chmod a+x /data/local/tmp/$(BUILD_MODULE_NAME)
adb shell /data/local/tmp/$(BUILD_MODULE_NAME)
clean:
del *.o $(BUILD_MODULE_NAME) $(BUILD_LIB_NAME)
|
-fpic 位置无关代码.
1
2
3
4
5
6
|
#编译动态库
make lib
#编译可执行文件
make
#安装可执行文件到Android中并运行
make install
|
动态库手工编译.7z
报错处理:
需了解Linux共享库搜索规则.
共享库搜索路径:
- system/lib
- user/lib
- 环境变量,如果以apk运行,会将/data/data/packageName/lib设为环境变量
示例:
1
2
3
4
|
//查看环境变量
echo $LD_LIBRARY_PATH
//设置临时环境变量, 以:分割环境变量路径
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/data/local/tmp
|
ndk-build
cmd 输入ndk-build即可编译.
动态库NDK-BUILD.7z
Cmake编译
优点
某一个.so文件有Bug,只需重新编译该so文件即可.
缺点
- 启动速度慢,但可以延迟加载.
- 软件加载动态库的时候,Dll中的函数代码全部进内存.
查看导出函数
1
|
命令行 llvm-readelf -s so文件路径
|
函数不导出
1
2
|
//在Linux中 函数默认是导出的,若不导出需加前缀
__attribute__ ((visibility("hidden")))
|
初始化函数
不常用:
之所以说不常用,是因为不方便,每加一个功能都有可能会修改_init中的代码.
常用:
方便,名字随意只要声明为构造或析构函数即可,数量不限.
动态使用
相关函数:
1
2
3
4
|
void* dlopen(const char* __filename, int __flag);
void* dlsym(void* __handle, const char* __symbol);
int dladdr(const void* __addr, Dl_info* __info);
int dlclose(void* __handle);
|
示例:
动态库使用.7z
Android API
基于Linux内核,遵循Posix标准,也有自己独有的API.
NDK
这是独有的API.
常用来打日志:
1
2
3
4
|
#include <android/log.h>
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "LuoHun", __VA_ARGS__);
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, "LuoHun", __VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "LuoHun", __VA_ARGS__);
|
添加库:
Posix
可移植操作系统接口(Portable Operating System Interface),无窗口API.
Cygwin,该库在Win32系统下实现了POSIX系统调用的API.
errno
扩展了 C标准的errno,下面是C标准的errno.
通过查文档可知该函数出错时,errno是否被设置.
文件
相关函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
//打开
int open(const char *pathname, int flags, mode_t mode);
//关闭
int close(int __fd);
//读
ssize_t read(int fd, void *buf, size_t count);
//写
ssize_t write(int fd, const void *buf, size_t count);
//移动文件指针
off_t lseek(int fildes, off_t offset, int whence);
//文件控制
int fcntl(int __fd, int __cmd, ...);
//指定位置读写
ssize_t pread(int fildes, void *buf, size_t nbyte, off_t offset);
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);
//文件属性
int stat(const char* __path, struct stat* __buf);
//检查文件权限
int access(const char* __path, int __mode);
//更改文件权限
int chmod(const char* __path, mode_t __mode);
int fchmod(int __fd, mode_t __mode);
//创建目录
int mkdir(const char* __path, mode_t __mode);
//遍历文件
DIR* opendir(const char* __path);
|
文件控制:
fcntl的用途之一是针对一个打开的文件,获取或修改访问模式和状态标识.
要获取这些设置,应将fcntl的cmd参数设置为F_GETFL.
1
2
3
4
5
6
|
//下述到吗可以测试文件是否以同步写方式打开
int nFlags;
nFlags = fcntl(fd, F_GETFL);
if(nFlags & O_SYNC){
//...
}
|
文件属性:
利用系统调用stat(),lstat(),fstat(),可以获取文件相关的信息.
1
2
3
4
5
6
7
8
9
|
//返回文件的相关信息
int stat(const char* __path, struct stat* __buf);
//与stat类似,区别,如果文件属于符号链接,那么返回的信息针对是的符号链接自身
int lstat(const char* __path, struct stat* __buf);
//根据文件描述符获取文件的相关信息
int fstat(int __fd, struct stat* __buf);
/*
系统调用stat()和lstat()无需对其所操作的文件本身拥有任何的权限,但是针对指定的pathname的父目录要有执行(搜素)权限.而fstat()系统调用只要文件描述符有效,总是成功.
*/
|
st_mode:
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
struct stat statBuf;
stat("/data/local/tmp/2.txt", &statBuf);
printf("st_uid = %d st_gid = %d st_size = %lld, st_mode = %o\n",
statBuf.st_uid,
statBuf.st_gid,
statBuf.st_size,
statBuf.st_mode);
if (S_ISREG(statBuf.st_mode)) {
printf("常规文件\n");
}
if (statBuf.st_mode & S_IRUSR) {
printf("拥有读权限\n");
}
|
目录权限:
目录与文件拥有相同的权限方案,只是对3种权限的含义另有所指.
- 读权限:可列出目录之下的内容.
- 写权限:可在目录创建,删除文件.
- 可执行权限:可访问目录中的文件(搜索权限).
权限检查算法:
- 对于特权级进程,授予所有访问权限.
- 若进程的有效用户ID与文件的用户ID相同,内核会根据文件的属主权限,授予进程相应的访问权限.
- 若进程的有效组ID与文件的组ID相匹配,内核会根据文件的属组权限,授予进程相应的访问权限.
- 若以上3点都不满足,内核会根据文件的其他权限,授予进程相应的访问权限.
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
|
#include <jni.h>
#include <string>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <android/log.h>
#include <errno.h>
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "Luo", __VA_ARGS__);
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, "Luo", __VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "Luo", __VA_ARGS__);
int main() {
/* FILE* fp = fopen("/data/local/tmp/1.txt", "r");
if (fp == NULL){
//当C库函数出错了,会将错误传给全局变量errno
printf("fopen error %d\n", errno);
//将错误转换为字符串
perror("fopen");
//传errno,将错误字符串返回,方便界面显示
char* pErr = strerror(errno);
printf("%s\n", pErr);
}*/
int fd = open("/data/local/tmp/2.txt", O_RDWR | O_CREAT, S_IRWXU);
if (fd < 0) {
perror("open");
}
int bytes = write(fd, "Hello", 5);
if (bytes < 0) {
perror("write");
} else {
printf("write bytes: %d\n", bytes);
}
if (lseek(fd, 0, SEEK_SET) < 0) {
close(fd);
perror("lseek");
return 0;
}
char szBuf[0x100] = {};
ssize_t readBytes = read(fd, szBuf, sizeof szBuf);
if (readBytes < 0) {
close(fd);
perror("read");
return 0;
}
printf("szBuf = %s readBytes = %d\n", szBuf, readBytes);
//获取文件属性
struct stat statBuf;
stat("/data/local/tmp/2.txt", &statBuf);
printf("st_uid = %d st_gid = %d st_size = %lld, st_mode = %o\n",
statBuf.st_uid,
statBuf.st_gid,
statBuf.st_size,
statBuf.st_mode);
if (S_ISREG(statBuf.st_mode)) {
printf("常规文件\n");
}
if (statBuf.st_mode & S_IRUSR) {
printf("拥有读权限\n");
}
//修改文件权限 相同Uid才可以修改权限 Root用户除外
fchmod(fd, S_IRWXU);
//检查文件权限
if (!access("/data/local/tmp/2.txt", W_OK)){
printf("拥有写权限\n");
}
close(fd);
return 0;
}
|
线程
两种状态:
- 连接状态,pthread_join,会阻塞,等待线程结束拿线程返回值.
- 分离状态,pthread_detach,不会阻塞,节省内存开销.
终止线程:
1
2
3
4
5
6
7
8
|
void pthread_exit(void *value_ptr);
return
/*
当一个线程设置了一个取消点,可使用下述API结束那个线程,
那个线程运行到取消点时,会结束线程
但遗憾的是,Android不允许使用此API
*/
int pthread_cancel(pthread_t thread);
|
线程同步:
互斥体:
1
2
3
4
5
6
7
8
9
|
//初始化
//可以使用全局变量初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//也可以使用API初始化
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
//加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
//取消锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
|
信号量:
1
2
3
4
5
6
7
8
9
10
11
|
//初始化
//跨进程使用sem_open
//不跨进程使用sem_init
int sem_init(sem_t* __sem, int __shared, unsigned int __value);
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag,
mode_t mode, unsigned int value);
//释放信号量
int sem_post(sem_t *sem);
//等待信号量
int sem_wait(sem_t *sem);
|
示例:
进程
Android将进程相关操作从原来的process.h移动到了unistd.h
相关函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
//获取自身进程PID
pid_t getpid(void);
//获取父进程PID
pid_t getppid(void);
//创建进程
pid_t fork(void);
//退进程
__noreturn void _exit(int __status);
int kill(pid_t __pid, int __signal);
//信号量
int sem_init(sem_t* __sem, int __shared, unsigned int __value);
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag,
mode_t mode, unsigned int value);
int sem_close(sem_t *sem);
int sem_destroy(sem_t *sem);
int sem_getvalue(sem_t *sem, int *sval);
//内存映射
void* mmap(void* __addr, size_t __size, int __prot, int __flags, int __fd, off_t __offset);
int munmap(void* __addr, size_t __size);
int msync(void* __addr, size_t __size, int __flags);
//修改内存保护属性,需要给页首地址,否则会失败
int mprotect(void* __addr, size_t __size, int __prot);
|
创建进程:
系统调用fork(),一个进程(父进程)创建一个新进程(子进程),新的子进程获取父进程的栈,数据段,堆和执行文本段的拷贝.
Linux系统中,父子进程关系很密切,同生共死.
虚拟文件系统:
Linux搞了一些假文件,通过操作这些假文件,可以访问内核的一些信息,Android中的虚拟文件在根目录的/proc中.
拿Cpu信息:
访问/proc/cpuinfo这个文件,就可以拿到Cpu信息.
遍历进程:
/proc目录下的每个以数字命名的文件夹都是一个进程,这些数字代表的是进程PID
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
void EnumProcess() {
DIR *dirp;
struct dirent *direntp;
dirp = opendir("/proc");
if (dirp == NULL) {
perror("can't open /proc");
} else {
for (;;) {
direntp = readdir(dirp);
if (direntp == NULL) {
break;
}
// /proc目录中以数字开头的是进程
if (direntp->d_name[0] >= '0' && direntp->d_name[0] <= '9') {
//取进程路径,在命令行的第一个参数中,有的系统进程可能无路径
char szProcessCmdPath[0x1000] = {};
char szProcessPath[0x1000] = {};
sprintf(szProcessCmdPath, "/proc/%s/cmdline", direntp->d_name);
FILE *fp = fopen(szProcessCmdPath, "r");
fgets(szProcessPath, sizeof(szProcessPath), fp);
fclose(fp);
printf("PID:%s ProcessPath:%s\n", direntp->d_name, szProcessPath);
}
}
closedir(dirp);
}
}
|
遍历模块:
1
2
3
4
5
6
7
8
9
10
11
12
|
void EnumModule() {
char buf[0x1000];
//访问/proc每个进程下的maps文件,即可遍历模块
//只要root权限的用户才可以遍历别人进程的模块
//遍历自身模块,可以写/proc/self/maps
FILE *fp = fopen("/proc/self/maps", "r");
while (!feof(fp)) {
fgets(buf, sizeof(buf), fp);
printf("%s", buf);
}
fclose(fp);
}
|
读写内存:
访问/proc/pid/mem文件即可,文件偏移就是内存地址.
Andorid内存搜索工具:GG
进程状态:
访问/proc/pid/status文件即可.
网络编程
常规Socket
耗费网络资源
服务端:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
#include <jni.h>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <linux/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main() {
//初始化Socket
int s = socket(AF_INET, SOCK_STREAM, 0);
if (s < 0) {
perror("socket");
return 0;
}
printf("socket s = %d\n", s);
fflush(stdout);
//绑定
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(5566);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(s, (sockaddr *) &addr, sizeof addr) < 0) {
perror("bind");
return 0;
}
printf("bind s = %d ok\n", s);
fflush(stdout);
//监听
if (listen(s, SOMAXCONN) < 0) {
perror("listen");
return 0;
}
printf("listen s = %d ok\n", s);
fflush(stdout);
//接受连接
socklen_t len = sizeof addr;
int cs = accept(s, (sockaddr *) &addr, &len);
if (cs < 0) {
perror("accept");
return 0;
}
printf("accept cs = %d ip:%s port:%d\n", cs, inet_ntoa(addr.sin_addr), htons(addr.sin_port));
fflush(stdout);
char buf[260];
int bytes;
while (true) {
bytes = recv(cs, buf, sizeof buf, 0);
if (bytes < 0) {
break;
}
printf("bytes:%d buf:%s\n", bytes, buf);
fflush(stdout);
}
close(s);
printf("close Socket\n");
fflush(stdout);
return 0;
}
|
客户端:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
#include <jni.h>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <linux/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main() {
//初始化Socket
int s = socket(AF_INET, SOCK_STREAM, 0);
if (s < 0) {
perror("socket");
return 0;
}
printf("socket s = %d\n", s);
fflush(stdout);
//绑定
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(5566);
addr.sin_addr.s_addr = INADDR_ANY;
if (connect(s, (sockaddr *) &addr, sizeof addr) < 0) {
perror("connect");
return 0;
}
printf("connect s = %d ok\n", s);
fflush(stdout);
//发送数据
char buf[260];
int bytes;
while (true) {
scanf("%s", buf);
bytes = send(s, buf, sizeof buf, 0);
printf("send bytes:%d buf:%s\n", bytes, buf);
fflush(stdout);
}
close(s);
printf("close Socket\n");
fflush(stdout);
return 0;
}
|
DoMain Socket
- 不耗费网络资源.
- 无IP和端口概念,用虚拟文件来代替IP地址.
服务端:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
#include <jni.h>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <linux/in.h>
#include <linux/un.h>
#include <arpa/inet.h>
#include <unistd.h>
int main() {
//初始化Socket
int s = socket(AF_UNIX, SOCK_STREAM, 0);
if (s < 0) {
perror("socket");
return 0;
}
printf("socket s = %d\n", s);
fflush(stdout);
//绑定
//删除文件
unlink("/data/local/tmp/myServer");
sockaddr_un addr;
//协议要用AF_UNIX
addr.sun_family = AF_UNIX;
//用文件来代替IP地址
strcpy(addr.sun_path, "/data/local/tmp/myServer");
if (bind(s, (sockaddr *) &addr, sizeof addr) < 0) {
perror("bind");
return 0;
}
printf("bind s = %d ok\n", s);
fflush(stdout);
//监听
if (listen(s, SOMAXCONN) < 0) {
perror("listen");
return 0;
}
printf("listen s = %d ok\n", s);
fflush(stdout);
//接受连接
socklen_t len = sizeof addr;
int cs = accept(s, (sockaddr *) &addr, &len);
if (cs < 0) {
perror("accept");
return 0;
}
printf("accept cs = %d ip:%s\n", cs, addr.sun_path);
fflush(stdout);
char buf[260];
int bytes;
while (true) {
bytes = recv(cs, buf, sizeof buf, 0);
if (bytes < 0) {
break;
}
printf("bytes:%d buf:%s\n", bytes, buf);
fflush(stdout);
}
close(s);
printf("close Socket\n");
fflush(stdout);
return 0;
}
|
客户端:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
#include <jni.h>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <linux/in.h>
#include <linux/un.h>
#include <arpa/inet.h>
#include <unistd.h>
int main() {
//初始化Socket
int s = socket(AF_UNIX, SOCK_STREAM, 0);
if (s < 0) {
perror("socket");
return 0;
}
printf("socket s = %d\n", s);
fflush(stdout);
//绑定 可以不绑定 绑定是为了让服务器知道是谁连接的
sockaddr_un clientAddr;
//协议要用AF_UNIX
clientAddr.sun_family = AF_UNIX;
//用文件来代替IP地址 +Pid 防止文件冲突
sprintf(clientAddr.sun_path, "/data/local/tmp/myClient_%d", getpid());
if (bind(s, (sockaddr*)&clientAddr, sizeof clientAddr)){
perror("bind");
return 0;
}
sockaddr_un serverAddr;
//协议要用AF_UNIX
serverAddr.sun_family = AF_UNIX;
//用文件来代替IP地址
strcpy(serverAddr.sun_path, "/data/local/tmp/myServer");
if (connect(s, (sockaddr *) &serverAddr, sizeof serverAddr) < 0) {
perror("connect");
return 0;
}
printf("connect s = %d ok\n", s);
fflush(stdout);
//发送数据
char buf[260];
int bytes;
while (true) {
scanf("%s", buf);
bytes = send(s, buf, sizeof buf, 0);
printf("send bytes:%d buf:%s\n", bytes, buf);
fflush(stdout);
}
close(s);
printf("close Socket\n");
fflush(stdout);
return 0;
}
|
注意
测试的时候,填写的虚拟Socket文件可能没有权限,需要进root测试,实际apk应用的时候,可以填写apk所在的目录,这样就没有权限问题了.
混合编程JNI
概述
Java Native Interface
Java => JNI =>xxx(动态库 so)
xxx(动态库 so) => JNI =>Java
两边互调应满足以下条件:
- so文件应该导出函数,在函数前加JNIEXPORT.
- 类型匹配,Java要调C++,首先类型应该匹配,在Jni.h中已定义好.
- 调用约定,在函数前加JNICALL.
如果是C++,防止名称粉碎,在函数前加extern “C”
名称规范问题:
1
2
3
4
|
为了防止误调so中的导出函数,应以包名_类名_函数名来命名
可以使用javah.exe将编译好的class文件给这个exe,
它会扫描类声明,看到native会自动创建头文件
不过这样还是很麻烦的,AndroidStodio已集成该工具
|
Java调C++
- 在Java类中添加声明, 声明前加native关键字.
- Alt + Enter,创建Jni方法.
Java反射
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
//拿目标对象
//①
// Class cls = MainActivity.class;
//② 本身有对象
//Class cls = this.getClass();
//③ 常用, 通过类名拿
try {
Class cls = Class.forName("org.example.luojni.MainActivity");
//new 对象
//不带构造new对象
Object obj = cls.newInstance();
//带构造new对象
Constructor constructor = cls.getConstructor();
//传构造参数即可
obj = constructor.newInstance(10);
//调方法 传参数是为了防止有歧义,因为函数可以重载
//①拿方法
Method method = cls.getMethod("MyAdd", int.class, int.class);
//②拿方法 修改方法的访问权限 当方法为private时 第①种方式会拿不到
method = cls.getDeclaredMethod("MyAdd", int.class, int.class);
//修改方法的访问权限
method.setAccessible(true);
//为了统一 返回值为Object
//如果为静态方法 第一个参数给null就可以了
Object ret = method.invoke(obj, 1, 2);
//若返回值为整型 就转为整型的包装类
int nResult = ((Integer) ret).intValue();
//反射字段
//①拿字段
Field field = cls.getField("mData");
//②拿字段 修改字段的访问权限 当字段为private时 第①种方式会拿不到
field = cls.getDeclaredField("mData");
//修改字段的访问权限
field.setAccessible(true);
//修改字段值
field.setInt(obj, 1);
//获取字段值
nResult = field.getInt(obj);
} catch (ClassNotFoundException | NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
|
Java反射的强大之处在于可以修改方法和字段的访问权限.
C++调Java
C++操作Java中的字符串以及数组:
- Java中声明函数,以及使用.
- C++中操作.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
extern "C"
JNIEXPORT jstring JNICALL
Java_org_example_luojni_MainActivity_fun1(JNIEnv *env,
jobject thiz,
jstring str,
jbyteArray ary) {
//操作Java中的字符串
int nLen = env->GetStringLength(str);
//传false 说明不拷贝缓冲区,跟Java中用的是同一个缓冲区
jboolean flag = false;
const char* psz = env->GetStringUTFChars(str, &flag);
LOGD("len = %d str = %s\n", nLen, psz);
//操作Java中的数组
int nCount = env->GetArrayLength(ary);
jbyte * p = env->GetByteArrayElements(ary, &flag);
for (int i = 0; i < nCount; ++i) {
LOGD("%d ", p[i]);
}
//返回String
return env->NewStringUTF("Hello Ret");
}
|
C++反射调Java:
- 写一个Java类.
- 在Java中声明native方法并调用.
- 在C++中反射调Java.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
//参数信息描述
//I(int) F(float) D(double) J(long) Z(bool) C(char) S(short) Ljava/lang/String(String)
//一维数组 [i(int[])
//二维数组 [[i(int[][])
extern "C"
JNIEXPORT void JNICALL
Java_org_example_luojni_MainActivity_fun2(JNIEnv *env, jobject thiz) {
//反射
//拿Java类
jclass cls = env->FindClass("org/example/luojni/MyClass");
//new对象
//传"<init>" 就是拿构造
jmethodID construct = env->GetMethodID(cls, "<init>", "()V");
//构造若是有参数就传参, 这个函数是不定参的
jobject obj = env->NewObject(cls, construct);
//调方法
//非静态方法
//()V 括号内写参数 后面写返回值
jmethodID fun1 = env->GetMethodID(cls, "fun1", "()V");
env->CallVoidMethod(obj, fun1);
//静态方法
jmethodID fun2 = env->GetStaticMethodID(cls, "fun2", "()V");
env->CallStaticVoidMethod(cls, fun2);
//示例
jmethodID Add = env->GetMethodID(cls, "Add", "(II)I");
//检查Java中的异常
if (env->ExceptionCheck()){
//打印异常栈信息
env->ExceptionDescribe();
//清除异常信息 为了不让程序崩
env->ExceptionClear();
return;
}
jint nRet = env->CallIntMethod(obj, Add, 1, 6);
//修改字段
jfieldID mData = env->GetFieldID(cls, "mData", "I");
env->SetIntField(obj, mData, 200);
//引用计数 我们在C++中New了一个对象 Java虚拟机如何知道何时释放对象
//释放字符串或数组对象 可以调env->Release...
env->DeleteLocalRef(obj);
}
|
加密:
Java的逆向相对于C来说比较容易,将Java中的所有代码都用C反射来调用,然后对C++层进行加密.
注意
在C++中反射调Java无视权限(public private).