前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[020]extern "C"的作用

[020]extern "C"的作用

作者头像
王小二
发布2020-06-08 10:49:42
6600
发布2020-06-08 10:49:42
举报

前言

我们用Android Studio新建native的demo应用中,一般C++的代码如下,这是一个典型的静态注册JNI的例子,调用stringFromJNI的java方法会调用到Java_com_kobe_MainActivity_stringFromJNI这个方法,细心的朋友会发现有一行extern "C",那这个有什么作用呢,能不能删除?

C++代码

代码语言:javascript
复制
#include <jni.h>
#include <string>

extern "C"
JNIEXPORT jstring JNICALL Java_com_kobe_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

删除extern "C"

实践是检验真理的最好手段,我们删除一下extern "C"试试,虽然编译通过了,但是程序运行出现了闪退,出现了如下的异常,好像是没有找到Java_com_kobe_MainActivity_stringFromJNI这个方法,为什么呢,我明明也写了这个方法。

代码语言:javascript
复制
Process: com.kobe, PID: 18796
java.lang.UnsatisfiedLinkError: No implementation found for java.lang.String com.kobe.MainActivity.stringFromJNI() 
    (tried Java_com_kobe_MainActivity_stringFromJNI and Java_com_kobe_MainActivity_stringFromJNI__)
    at com.kobe.MainActivity.stringFromJNI(Native Method)
    at com.kobe.MainActivity.onCreate(MainActivity.java:22)
    at android.app.Activity.performCreate(Activity.java:7802)
    at android.app.Activity.performCreate(Activity.java:7791)
    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3344)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3508)
    at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
    at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2102)
    at android.os.Handler.dispatchMessage(Handler.java:107)
    at android.os.Looper.loop(Looper.java:214)
    at android.app.ActivityThread.main(ActivityThread.java:7499)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:935)

对比编译出来的so

我们通过nm的指令查看两次编译出来的so,发现Java_com_kobe_MainActivity_stringFromJNI变成_Z40Java_com_kobe_MainActivity_stringFromJNIP7_JNIEnvP8_jobject。

代码语言:javascript
复制
//保留extern "C"
19:15 Kobe-Wang:~/Desktop$ nm libnative-lib.so | grep stringFromJNI
000000000000ea98 T Java_com_kobe_MainActivity_stringFromJNI

//去掉extern "C"
19:16 Kobe-Wang:~/Desktop$ nm libnative-lib.so | grep stringFromJNI
000000000000eab8 T _Z40Java_com_kobe_MainActivity_stringFromJNIP7_JNIEnvP8_jobject

静态注册的JNI搜索native层的方法

代码语言:javascript
复制
void* FindNativeMethod(Thread* self, ArtMethod* m, std::string& detail)
      REQUIRES(!Locks::jni_libraries_lock_)
      REQUIRES_SHARED(Locks::mutator_lock_) {
      ...
      //去找本地方法中有没有jni_short_name和jni_long_name的方法
      void* native_code = FindNativeMethodInternal(self,
                                                   declaring_class_loader_allocator,
                                                   shorty,
                                                   jni_short_name,
                                                   jni_long_name);
      //如果找到了就返回函数指针
      if (native_code != nullptr) {
        return native_code;
      }
    //没有找到了就返回异常,也就是前面出现的异常
    detail += "No implementation found for ";
    detail += m->PrettyMethod();
    //jni_short_name就是Java_com_kobe_MainActivity_stringFromJNI
    //jni_long_name就是Java_com_kobe_MainActivity_stringFromJNI__
    detail += " (tried " + jni_short_name + " and " + jni_long_name + ")";
    return nullptr;
  }

extern "C"移除之后,so中的函数最后编译出来的方法名从Java_com_kobe_MainActivity_stringFromJNI变成了Z40Java_com_kobe_MainActivity_stringFromJNIP7_JNIEnvP8_jobject,而静态注册的JNI只会找Java_com_kobe_MainActivity_stringFromJNI和Java_com_kobe_MainActivity_stringFromJNI_,当然找不到。

extern "C"的作用到底是什么呢?

extern "C"的作用就是让被作用的代码块采用c语言的编译规则编译

为什么相同的方法名编译出不同的方法名

java的工程师应该都听说过函数的重载,java语言中函数的重载就是可以存在两个同名不同参数的函数。但是C语言是不支持函数重载的,C++为了实现函数的重载,会对我们自己写的函数在编译之后重新修饰,修饰的规则好像就是把原函数的名和参数组合成了新的一个函数_Z40Java_com_kobe_MainActivity_stringFromJNIP7_JNIEnvP8_jobject。

总结

1.C不支持函数的重载,编译之后函数名不变 2.C++支持函数的重载,编译之后函数名会变 3.静态注册的JNI接口,需要考虑C++编译之后函数名变化的问题,所以需要加上extern "C"的关键字。 4.动态注册的JNI接口,就不用担心这个问题,所以不用加extern "C"

进一步思考

很多时候我们会碰到一些头文件中声明了C语言的函数,但是这个头文件会被C语言或者C++语言使用。比如我们常见的C语言函数库中string.h的函数

代码语言:javascript
复制
void *memset(void *s, int c, size_t n);

如果不加任何处理,当C语言程序包含string.h的时候,C语言编译器会将memset正确引用处理。但是在C++语言中就会将memset函数修饰成_Z6memsetPvii, 这样子链接器就无法与C库中的memset的链接了,所以必须使用extern "C",但是C语言又不支持extern "C",如果为了兼容C和C++,定义两个头文件就过于麻烦了。幸好我们有一种很好的方法可以解决这个问题,那就是使用C++的宏"__cplusplus",我们可以通过这个宏来判断当前的编译器是不是c++编译器。

代码语言:javascript
复制
#if defined(__cplusplus)
extern "C" {
#endif

void *memset(void *s, int c, size_t n);

#if defined(__cplusplus)
}
#endif

这种使用技巧,可以说在android源码中随处可见,下次看到了应该就知道为什么了吧。

参考文献

《程序员的自我修养》第三章 目标文件里有什么

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 删除extern "C"
  • 对比编译出来的so
  • 静态注册的JNI搜索native层的方法
  • extern "C"的作用到底是什么呢?
  • 为什么相同的方法名编译出不同的方法名
  • 总结
  • 进一步思考
  • 参考文献
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档