extern C 透彻理解

2023-05-16

一、背景

一直以来对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(使用前将#替换为@)

extern C 透彻理解 的相关文章

随机推荐