一、背景
一直以来对extern C的理解都停留在表面,只知道为了C/C++混合编程。今天来透彻理解下这个概念。
二、整体的项目结构。
jni
---Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := lesson2
LOCAL_SRC_FILES := Lesson2.cpp MyTest.cpp module.c
LOCAL_LDLIBS := -llog -ldl
LOCAL_CPPFLAGS += -O3
LOCAL_CFLAGS += -fvisibility=hidden
include $(BUILD_SHARED_LIBRARY)
---Application.mk
APP_PLATFORM := android-21
APP_ABI := armeabi-v7a
---com_example_ndkreverse2_Lesson2.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_ndkreverse2_Lesson2 */
#ifndef _Included_com_example_ndkreverse2_Lesson2
#define _Included_com_example_ndkreverse2_Lesson2
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_ndkreverse2_Lesson2
* Method: main
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_example_ndkreverse2_Lesson2_main
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
---Lesson2.cpp
#include "com_example_ndkreverse2_Lesson2.h"
#include <android/log.h>
#include <stdlib.h>
#include <string.h>
#include <sys/auxv.h>
#include <sys/prctl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include "MyTest.h"
extern "C" {
#include "module.h"
}
JNIEXPORT void gettime() {
struct tm *info;
time_t rawtime;
char buffer[64];
char final_time[128];
time( &rawtime );
info = localtime( &rawtime );
struct timeval tv;
gettimeofday(&tv,NULL);
strftime(buffer,64,"%Y.%m.%d.%H:%M:%S.%%u", info);
snprintf(final_time, 0x80u, buffer, tv.tv_usec / 1000);
ALOGD("info:%s\n", final_time);
}
JNIEXPORT void JNICALL Java_com_example_ndkreverse2_Lesson2_main
(JNIEnv * env, jobject jobject) {
gettime();
MyTest* pTest = new MyTest();
pTest->Print();
foo(1,2);
foo1(2,1);
}
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
ALOGD("JNI_OnLoad");
return JNI_VERSION_1_4;
}
---module.c
#include "module.h"
int foo( int x, int y ) {
return x + y;
}
---module.h
#ifndef NDKREVERSE2_MODULE_H
#define NDKREVERSE2_MODULE_H
int foo( int x, int y );
#endif //NDKREVERSE2_MODULE_H
---MyTest.cpp
#include "MyTest.h"
#include <android/log.h>
#define LOG_TAG "lesson2"
#define ALOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
int foo1(int x, int y) {
MyTest myTest;
myTest.Print();
return x - y;
}
MyTest::MyTest()
{
ALOGD("MyTest\n");
}
void MyTest::Print()
{
ALOGD("Print\n");
}
---MyTest.h
#ifndef NDKREVERSE2_MYTEST_H
#define NDKREVERSE2_MYTEST_H
int foo1(int x, int y);
class MyTest {
public:
MyTest();
void Print();
};
#endif //NDKREVERSE2_MYTEST_H
三、extern C的第一种用法--告诉C++编译器按照C语言编译器的规则来链接
使用ndk-build,首先会生成几个.o文件:Lesson2.o,module.o,MyTest.o。我们分别使用ida查看下module.o的导出符号。
module.o是C语言编译器编译生成的,所有导出符号是foo。而调用module的地方是在Lesson2.cpp文件中,Lesson2.o是使用C++编译器生成的。由于C++语法支持重载,所以使用C++编译器生成的函数和使用C语言编译器生成的函数是不一样的(后面可以看到哪里不一样)。为了让module.o和Lesson2.o能够链接到一起,我们在Lesson2.cpp加入了
extern "C" {
#include "module.h"
}
这是告诉编译器在链接时,按照C语言的编译风格寻找module.o中的foo函数。
extern C 是C++语法,不能在C语言中使用。如果要在C语言中使用,需要如下定义:
#ifdef __cplusplus
extern "C" {
#endif
int foo(int x, int y)
#ifdef __cplusplus
}
#endif
在C++语言包含时,会加上extern C。在C语言包含时,会去掉extern C。
四、extern C的第二种用法---定义.cpp中说明函数要按照C语言的风格编译
上面工程中MyTest.h定义了foo1这个函数,而这个函数实现在MyTest.cpp中,编译生成的MyTest.o。
我们可以看到是按照C++的风格编译的函数名为_Z4foo1ii。
如果MyTest.h加上extern C。
#ifndef NDKREVERSE2_MYTEST_H
#define NDKREVERSE2_MYTEST_H
extern "C" int foo1(int x, int y);
class MyTest {
public:
MyTest();
void Print();
};
#endif //NDKREVERSE2_MYTEST_H
编译的结果如下:
我们可以看到是按照C语言的风格编译的函数名为foo1。
同时也告诉了Lesson2.o和MyTest.o中链接的时候按照C语言的风格寻找foo1函数。
五、so导出函数
我们在Android.mk中定义了
LOCAL_CFLAGS += -fvisibility=hidden
这个CFLAGS的含义是所有在so中的符号默认不导出,只有加上了
#define JNIEXPORT __attribute__ ((visibility ("default"))) 才是导出函数。
我们回看下Lesson2.cpp,Lesson2.h:
Java_com_example_ndkreverse2_Lesson2_main是有extern C包含的,所以导出函数就是Java_com_example_ndkreverse2_Lesson2_main。
还有个疑问,JNI_Onload为什么按照C语言的风格导出呢?
答案在jni.h中
#ifdef __cplusplus
extern "C" {
#endif
/*
* VM initialization functions.
*
* Note these are the only symbols exported for JNI by the VM.
*/
jint JNI_GetDefaultJavaVMInitArgs(void*);
jint JNI_CreateJavaVM(JavaVM**, JNIEnv**, void*);
jint JNI_GetCreatedJavaVMs(JavaVM**, jsize, jsize*);
#define JNIIMPORT
#define JNIEXPORT __attribute__ ((visibility ("default")))
#define JNICALL
/*
* Prototypes for functions exported by loadable shared libs. These are
* called by JNI, not provided by JNI.
*/
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved);
JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved);
#ifdef __cplusplus
}
#endif
那么这里为什么要按照C原因的风格导出呢?
因为加载so的时候会使用dlsym,dlsym传入的函数名称,就是导出的函数名称,这样调用dlsym(handle,"JNI_Onload")就能调用对应的方法了。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)