c语言中extern的作用6,[020]extern "C"的作用

2023-05-16

前言

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

C++代码

#include

#include

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这个方法,为什么呢,我明明也写了这个方法。

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。

//保留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层的方法

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的函数

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++编译器。

#if defined(__cplusplus)

extern "C" {

#endif

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

#if defined(__cplusplus)

}

#endif

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

参考文献

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

c语言中extern的作用6,[020]extern "C"的作用 的相关文章

  • 房卡麻将分析之"防作弊处理"

    房卡麻将分析之 34 防作弊处理 34 棋牌游戏最重要的一个特点就是人与人对局 xff0c 因为玩家各自的不可见 xff0c 就存在着一些作弊的可能性和漏洞 对于手机房卡麻将游戏 xff0c 大家最讨厌的问题就是作弊 如何防止玩家作弊 xf
  • "extern C", 你真的懂了吗?

    在c 43 43 prime书中看到过 xff0c 在DLL和lib中看到过 xff0c 但是每次看过就不求甚解地一扫而过 心里知道有extern c这个语句 xff0c 却不知道该用在哪里 xff0c 又能起到什么作用 唉 xff0c 想
  • 无法解析的外部符号 "public: virtual struct CRuntimeClass

    无法解析的外部符号 34 public virtual struct CRuntimeClass thiscall CMessageBox GetRuntimeClass void const 34 以下原因是会引起上述错误的 xff1a
  • 解决"SSL handshake failed"问题

    前阵子不知是对Ubuntu动了些啥 xff0c 结果Ubuntu One死活也不上 App Center上点击 34 Buy 34 按钮会提示 34 SSL handshake failed 34 猜想大概是由于同一问题导致的 网上给出的解
  • gcc error - "iostream: No such file or directory"

    include lt iostream gt using namespace std int main void cout lt lt 34 Hello World n 34 return 0 使用命令 gcc o test test c
  • 一行代码引发的"血案"

    昨天在使用pykafka的时候又遇到了之前我遇到过的PartitionOwnedError ConsumerStoppedException异常 xff0c 关于这个异常我之前写过一篇分析的文章 链接在这里 xff0c 我自认为之前应该是把
  • c++中的extern c以使用

    extern C 是c 43 43 可以正确使用c中代码而产生的 xff0c 虽然c 43 43 兼容c xff0c 但是在c 43 43 程序调用c的库时 xff0c 也会产生链接错误 因为c的库中函数的修饰规则与c 43 43 的函数修
  • 对extern,static,const的再认识

    const const修饰的值为常量 是不可改变的 在c 语言中是不可改变的 而在C语言中 我们可以通过指针去修改那一片地址的值 const修饰的指针 表面指针指向或者指针的值是不可被修改的 我们可以通过通配符 的位置来判断 在左说明修饰的
  • 如何声明 constexpr extern?

    是否可以声明变量extern constexpr并在另一个文件中定义它 我尝试了一下 但是编译器给出了错误 声明constexpr多变的 i 不是一个定义 in h extern constexpr int i in cpp constex
  • C 中的外部指针和静态指针

    您好 静态和外部指针的用法是什么 如果它们存在的话 为了回答您关于何时可以使用它们的问题 举几个简单的例子 静态指针可用于实现始终向程序返回相同缓冲区的函数 并在第一次调用时分配它 char GetBuffer static char bu
  • 静态内联、外部内联和普通内联函数有什么区别?

    和有什么区别static inline extern inline和一个正常的inline功能 我看到过一些对此的模糊解释 据我了解 static inline不只是一个inline函数仅在某个文件中被称为static关键字通常意味着 同样
  • 如何在多个.cpp文件中使用全局变量?

    我有一个简单的程序 它尝试在单独的文件中打印我的全局变量 我正在使用Visual Studio 2013 专业版 IDE print h ifndef PRINT H define PRINT H void Print endif 打印 c
  • 在 Objective-C 代码中使用 extern "C" 时发生链接器错误

    我正在尝试创建一些可以从 iPhone 应用程序中的 Objective C 和 C 代码调用的实用函数 我有无法编译为 ObjectiveC mm 的第三方 C 类 我有一个头文件声明我的函数 然后有一个定义它们的 c 文件 我已经三次检
  • C、硬件抽象层中“extern”类型的变量

    我正在研究硬件抽象层 该 HAL 的目的是在 Linux 驱动程序和 MCU 驱动程序之间轻松切换 我正在研究SPI接口 下面是 打开 SPI接口的HAL函数的签名 哈尔 spi h spi handle t spi open spi po
  • 更改外部变量的值

    我们在 File1 c 中有 int arr 10 在 File2 c 中 extern int arr int main arr 0 10 return 0 这样做可能会出现哪些问题以及为什么 数组不是指针 内存访问将会出错 In Fil
  • 访问另一个 .cpp 文件中的 .cpp 文件中定义的全局变量[重复]

    这个问题在这里已经有答案了 考虑以下场景 我的文件 cpp const int myVar 0 全局变量 另一个文件 cpp void myFun std cout lt lt myVar compiler error Undefined
  • 将 {具有 C 链接的函数指针} 分配给 {具有 C++ 链接的函数指针},反之亦然

    这段代码合法吗 extern C typedef void ft blah c extern C typedef void ft blah cpp extern C void fn blah c extern C void fn blah
  • 如果我将 extern "C++" 与 C 工具链一起使用会发生什么?

    我的问题主要是关于 C 工具链 理解 C 和 C 的事实 所以如果我用extern C 对于 C 工具链 我认为它可以理解如何处理它 但是如果我用以下代码提供代码怎么办extern C 到 C 工具链 预期的行为是什么 如果编译器也理解 C
  • 在定义中使用 static 关键字与在 C 中使用声明

    以下编译良好 使用static仅在函数声明期间 include
  • 块范围内没有链接?

    块中声明的所有变量是否都 无链接 例如 1 如果我声明一个静态变量 void foo static int i 它有内部联系还是没有联系 如果没有链接 那为什么要使其静态呢 2 如果我使用 extern 会发生什么 global scope

随机推荐