C语言代码中调用C++代码的方法示例

由于历史原因,以及不同开发人员的技术偏好,C语言和C++语言都有一些独有的非常有价值的项目,因而两种语言的互操作,充分利用前人造的轮子是一件非常有价值的事情。

C++代码调用C代码很简单,只要分别在包含的C头文件的开头和结尾加上如下的两个块:

#ifdef __cplusplus
extern "C" {
#endif

#ifdef __cplusplus
}
#endif

即可。

然而为了支持类、重载等更加高级的特性,在编译C++代码时,C++符号会被修饰。我们dump Linux平台加密库 libcrypto++ 的符号表,可以看到如下的内容:

$ readelf -s /usr/lib/libcrypto++.so
Symbol table '.dynsym' contains 9607 entries:
 Num: Value   Size Type Bind Vis  Ndx Name
  0: 0000000000000000  0 NOTYPE LOCAL DEFAULT UND
  1: 00000000001daa58  0 SECTION LOCAL DEFAULT 9
  2: 0000000000000000  0 OBJECT GLOBAL DEFAULT UND _ZTIi@CXXABI_1.3 (2)
  3: 0000000000000000  0 FUNC GLOBAL DEFAULT UND __errno_location@GLIBC_2.2.5 (3)
  4: 0000000000000000  0 FUNC GLOBAL DEFAULT UND _ZSt18uncaught_exceptionv@GLIBCXX_3.4 (4)
  5: 0000000000000000  0 FUNC GLOBAL DEFAULT UND _ZNSt8__detail15_List_node_base7_M_hookEPS0_@GLIBCXX_3.4.15 (5)
  6: 0000000000000000  0 FUNC GLOBAL DEFAULT UND getservbyname@GLIBC_2.2.5 (6)
  7: 0000000000000000  0 FUNC GLOBAL DEFAULT UND bind@GLIBC_2.2.5 (6)
  8: 0000000000000000  0 FUNC GLOBAL DEFAULT UND _ZSt29_Rb_tree_insert_and_rebalancebPSt18_Rb_tree_node_baseS0_RS_@GLIBCXX_3.4 (4)
  9: 0000000000000000  0 FUNC GLOBAL DEFAULT UND __longjmp_chk@GLIBC_2.11 (7)
 10: 0000000000000000  0 OBJECT GLOBAL DEFAULT UND _ZTIh@CXXABI_1.3 (2)
 11: 0000000000000000  0 OBJECT GLOBAL DEFAULT UND _ZTVSt9basic_iosIcSt11char_traitsIcEE@GLIBCXX_3.4 (4)
 12: 0000000000000000  0 FUNC GLOBAL DEFAULT UND socket@GLIBC_2.2.5 (6)
 13: 0000000000000000  0 FUNC GLOBAL DEFAULT UND _ZNSt14basic_ifstreamIcSt11char_traitsIcEED1Ev@GLIBCXX_3.4 (4)
 . . . . . .
 86: 0000000000000000  0 FUNC GLOBAL DEFAULT UND _ZNSo5writeEPKcl@GLIBCXX_3.4 (4)
 87: 0000000000000000  0 FUNC GLOBAL DEFAULT UND malloc@GLIBC_2.2.5 (6)
 88: 0000000000000000  0 FUNC GLOBAL DEFAULT UND _ZNSt9basic_iosIcSt11char_traitsIcEE4initEPSt15basic_streambufIcS1_E@GLIBCXX_3.4 (4)
 89: 0000000000000000  0 FUNC GLOBAL DEFAULT UND _ZNSi5seekgElSt12_Ios_Seekdir@GLIBCXX_3.4 (4)
 90: 0000000000000000  0 FUNC GLOBAL DEFAULT UND pthread_key_delete@GLIBC_2.2.5 (3)
 91: 0000000000000000  0 FUNC GLOBAL DEFAULT UND shutdown@GLIBC_2.2.5 (6)
 92: 0000000000000000  0 FUNC GLOBAL DEFAULT UND _ZSt15set_new_handlerPFvvE@GLIBCXX_3.4 (4)
 93: 0000000000000000  0 FUNC GLOBAL DEFAULT UND pthread_getspecific@GLIBC_2.2.5 (3)
 94: 0000000000000000  0 FUNC GLOBAL DEFAULT UND strcmp@GLIBC_2.2.5 (6)
 95: 0000000000000000  0 FUNC GLOBAL DEFAULT UND strtol@GLIBC_2.2.5 (6)
 96: 0000000000000000  0 FUNC GLOBAL DEFAULT UND ioctl@GLIBC_2.2.5 (6)
 . . . . . .
 186: 00000000002c5a80 142 FUNC GLOBAL DEFAULT 12 _ZN8CryptoPP6xorbufEPhPKhS2_m
 187: 00000000002fd6d0  9 FUNC WEAK DEFAULT 12 _ZN8CryptoPP21InvertibleRSAFunction9BERDecodeERNS_22BufferedTransformationE
 188: 00000000001ea840 73 FUNC GLOBAL DEFAULT 12 _ZN8CryptoPP13Base64Decoder22GetDecodingLookupArrayEv
 189: 0000000000249760  6 FUNC WEAK DEFAULT 12 _ZThn8_N8CryptoPP13DL_SignerImplINS_25DL_SignatureSchemeOptionsINS_5DL_SSINS_13DL_Keys_ECDSAINS_4EC2NEEENS_18DL_Algorithm_ECDSAIS4_EENS_37DL_SignatureMessageEncodingMethod_DSAENS_6SHA256EiEES5_S7_S8_S9_EEED0Ev
 190: 0000000000278b60 86 FUNC WEAK DEFAULT 12 _ZN8CryptoPP8Rijndael3DecD1Ev
 191: 00000000001fd1f0  2 FUNC WEAK DEFAULT 12 _ZN8CryptoPP23DefaultEncryptorWithMAC8FirstPutEPKh
 192: 000000000026a490 51 FUNC GLOBAL DEFAULT 12 _ZN8CryptoPP23FilterWithBufferedInputC2EPNS_22BufferedTransformationE
 193: 0000000000285180  6 FUNC WEAK DEFAULT 12 _ZNK8CryptoPP8GCM_Base6IVSizeEv
 194: 000000000032e830 510 FUNC WEAK DEFAULT 12 _ZN8CryptoPP18StandardReallocateItNS_20AllocatorWithCleanupItLb0EEEEENT0_7pointerERS3_PT_NS3_9size_typeES8_b
 195: 00000000002a1790 185 FUNC WEAK DEFAULT 12 _ZSt18uninitialized_copyISt15_Deque_iteratorIyRKyPS1_ES0_IyRyPyEET0_T_S9_S8_
 196: 0000000000355610 25 OBJECT WEAK DEFAULT 14 _ZTSN8CryptoPP11RSAFunctionE
 . . . . . .

这与我们在源文件和头文件里看到的那些函数、类的声明定义都不一样。通过binutils的工具c++filt demangle这些符号可以让我们看到它们在代码里的样子:

$ c++filt _ZTSN8CryptoPP11RSAFunctionE
typeinfo name for CryptoPP::RSAFunction
$ c++filt _ZN8CryptoPP18StandardReallocateItNS_20AllocatorWithCleanupItLb0EEEEENT0_7pointerERS3_PT_NS3_9size_typeES8_b
CryptoPP::AllocatorWithCleanup<unsigned short, false>::pointer CryptoPP::StandardReallocate<unsigned short, CryptoPP::AllocatorWithCleanup<unsigned short, false> >(CryptoPP::AllocatorWithCleanup<unsigned short, false>&, unsigned short*, CryptoPP::AllocatorWithCleanup<unsigned short, false>::size_type, CryptoPP::AllocatorWithCleanup<unsigned short, false>::size_type, bool)

那到底有没有办法在C代码中调用C++代码呢?方法当然是有的,而且还不止一种。

通过extern “C”调用

在 .cpp 文件中定义一个函数,声明为extern "C",则该函数可以方便地在C代码中调用。由于该函数在 .cpp 文件中定义,因而在该函数的实现中,可以调用任意的C++代码,包括C++函数,创建C++类等等。

C++头文件:

#ifndef CPPFUNCTIONS_H_
#define CPPFUNCTIONS_H_
#ifdef __cplusplus
int cpp_func(int input);
extern "C" {
#endif
int c_func(int input);
#ifdef __cplusplus
}
#endif
#endif /* CPPFUNCTIONS_H_ */

C++实现文件如下:

#include "CppFunctions.h"
int cpp_func(int input) {
 return 5;
}
int c_func(int input) {
 return cpp_func(input);
}

在C代码里调用C++函数:

#include <stdio.h>
#include "CppFunctions.h"
int main(int argc, char **argv) {
 printf("%d\n", c_func(10));
 return 0;
}

在C++文件里定义的c_func函数就像一座桥一样,连接了C代码的世界和C++代码的世界。但 C 函数c_func的参数及返回值的类型自然是受到一定的限制的,但在函数实现中可以适配要调用的C++接口,做一些适配。

通过dlopen/dlsym调用

借助于在 .cpp 文件中定义的C函数,间接地调用C++接口,固然是能实现在 C 代码中调用C++代码的目标,然而还是有些麻烦。通过libdl提供的接口,可以使我们的目标通过更简便的方式实现。

为dlsym传入经过修饰的符号,可以找到对应的函数的地址。

通过如下命令将上面的CPPFunctions.cpp文件编译为一个动态链接库:

$ gcc -shared -fPIC CPPFunctions.cpp -o libCppLibTest.so

通过dlopen和dlsym找到对应的C++函数,并将其强制类型转换为适当类型的函数指针,然后通过函数指针调用目标函数,如:

#include <dlfcn.h>
#include <stdio.h>
int main(int argc, char **argv) {
 void *libCPPTest = dlopen("/home/hanpfei0306/workspace_java/CppLibTest/Debug/libCppLibTest.so", RTLD_NOW);
 int (*cpp_func)(int) = (int (*)(int))dlsym(libCPPTest, "_Z8cpp_funci");
 printf("cpp_func = %p\n", cpp_func);
 printf("cpp_func output = %d\n", cpp_func(10));
 return 0;
}

编译并执行上面的代码,在我的机器上可以看到如下的输出:

cpp_func = 0x7f35727a8650
cpp_func output = 5

总结

以上就是这篇文章的全部内容了,希望本文的的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。

(0)

相关推荐

  • C++调用C函数实例详解

    C++调用C函数实例详解 前言:以前见到extern "C"这样的语句,只是简单地知道跟外部链接有关,但是没有深刻理解它的意思. 首先,为什么要使用extern "C"修饰符? C++调用其它语言的函数,由于编译器生成函数的机制不一样,所以需要经过特殊处理,才可以调用.调用C语言的函数,需要在函数声明的地方语句extern "C".如果不使用该语句,在链接的时候,编译器就会报以下这种错误. Test.obj : error LNK2019: 无法

  • 在C语言中调用C++做的动态链接库

    今天在做东西的时候遇到一个问题,就是如何在C语言中调用C++做的动态链接库so文件 如果你有一个c++做的动态链接库.so文件,而你只有一些相关类的声明, 那么你如何用c调用呢,别着急,本文通过一个小小的例子,让你能够很爽的搞定. 链接库头文件: head.h class A { public: A(); virtual ~A(); int gt(); int pt(); private: int s; }; firstso.cpp #include <iostream> #include &

  • C++调用C#的DLL实现方法

    SwfDotNet是C#编写的,这是个特别好的读写Swf文件的库.本文讲述了在C++项目中,怎么让C++调用C#的DLL动态链接库文件. 具体的实现步骤如下: 一.创建C# DLL,需要指定应用类型为"类库",代码: namespace CSLib { public class Class1 { private string name; public string Name { get { return name; } set { name = "Your Name: &qu

  • Java如何调用C++ DLL库

    最近做了一个网页端人脸识别的项目,用c++写了人脸识别的算法,但是在网页端要使用java后台,这其中就涉及到了java调用dll的问题.下面是小编通过查阅相关资料,实现的一个简单例子. 1.第一步,先在Java中新建一个类 如上图,其中注意这句System.loadLibrary("javaCallcpp");,这就是加载dll文件的代码了.然后我们需要dll中实现下面定义的加减乘除方法. 2.编译文件,文件名为Java2cpp.java,首先编译成class文件,如果用的是eclip

  • C++调用C#的DLL程序实现方法

    把C#编译成DLL或者Axtive控件,再由C调用!比如使用C++调用C#的DLL. SwfDotNet是.net下输出flash的类库.SwfDotNet是C#编写的,作者的C#水平,真是令我佩服.这是个特别好的读写Swf文件的库.但是,我要用在C++项目中,怎么让C++调用C#的DLL呢.今天一上午都在琢磨这个问题,耽误了很多时间,原因是编译是出现:warning C4819: 该文件包含不能在当前代码页(936)中表示的字符.请将该文件保存为 Unicode 格式以防止数据丢失. 接着就是

  • C语言代码中调用C++代码的方法示例

    由于历史原因,以及不同开发人员的技术偏好,C语言和C++语言都有一些独有的非常有价值的项目,因而两种语言的互操作,充分利用前人造的轮子是一件非常有价值的事情. C++代码调用C代码很简单,只要分别在包含的C头文件的开头和结尾加上如下的两个块: #ifdef __cplusplus extern "C" { #endif 和 #ifdef __cplusplus } #endif 即可. 然而为了支持类.重载等更加高级的特性,在编译C++代码时,C++符号会被修饰.我们dump Linu

  • 如何在C++中调用python代码你知道吗

    目录 一.环境设置 二.VS项目中设置 (1)首先在acaconda中找到include文件夹和libs文件夹,如图所示 (2)点击链接器,然后输入,附加依赖项,添加python36_d.lib的路径 (3)python代码 三.可能会出现的问题 总结 一.环境设置 windows VS2015 python的话用的是acaconda自带的python环境,不同版本的acaconda自带的python不同,我的是python3.6(这一步很重要,如果使用acaconda创建的虚拟环境的pytho

  • 教你在VS2022 MFC程序中调用CUDA代码的方法

    目录 在VS2022 MFC程序中调用CUDA函数 Pre: 安装好CUDA后VS中该有的效果 将CUDA函数集成到MFC项目中 1. 为项目添加CUDA配置 2. 把cuda代码添加到项目中 3. 导出想调用的cuda函数定义 4. 创建CUDA的调用接口(非必须) 在VS2022 MFC程序中调用CUDA函数 Pre: 安装好CUDA后VS中该有的效果 首先,假设你已经安装好了CUDA,并且成功集成在VS2022中(即新建项目有CUDA项目的选项,如下图所示). 你已经有一个MFC项目和一份

  • golang代码中调用Linux命令

    目录 传统方案--crontab 分布式任务调度 go执行shell命令 实际我们在golang代码中调用Linux命令 1.普通调用 2.结合协程调用,可控制中断调用 传统方案--crontab 缺点 配置任务时,需要SSh登录脚本服务器进行操作 服务器宕机,任务将终止调度,需要人工迁移 排查问题低效,无法方便的查看任务状态与错误输出 分布式任务调度 优点 可视化Web后台,方便进行任务管理 分布式架构.集群化调度,不存在单点故障 追踪任务执行状态,采集任务输出,可视化log查看 go执行sh

  • Go语言中调用外部命令的方法总结

    目录 引子 运行命令 显示输出 显示到标准输出 输出到文件 发送到网络 保存到内存对象中 输出到多个目的地 运行命令,获取输出 分别获取标准输出和标准错误 标准输入 环境变量 检查命令是否存在 封装 总结 引子 在工作中,我时不时地会需要在Go中调用外部命令.前段时间我做了一个工具,在钉钉群中添加了一个机器人,@这个机器人可以让它执行一些写好的脚本程序完成指定的任务.机器人倒是不难,照着钉钉开发者文档添加好机器人,然后@这个机器人就会向一个你指定的服务器发送一个POST请求,请求中会附带文本消息

  • PHP正则删除HTML代码中宽高样式的方法

    本文实例讲述了PHP正则删除HTML代码中宽高样式的方法.分享给大家供大家参考,具体如下: 因工作需要,需要采集html,并把html内容保存到数据库中.为了避免影响使用,宽高样式需要删除.例如图片和div中的width, height等. 不过采集到的html中,样式的写法各有不同,例如大小写,中间有空格等. 因此使用php正则编写了下面这个方法,对这些奇葩的样式进行过滤. 代码如下: <?php /** * 清除宽高样式 * @param String $content 内容 * @retu

  • php正则删除html代码中class样式属性的方法 原创

    本文实例讲述了php正则删除html代码中class样式属性的方法.分享给大家供大家参考,具体如下: 一.问题: 有如下代码: <div class="jb51"><div class="jb51_txt">欢迎访问我们</div></div> 要求:删除HTML代码中的class属性. 二.实现方法: php实现代码如下: $str='<div class="jb51"><div

  • 在java代码中获取JVM参数的方法

    实例如下: MemoryMXBean memorymbean = ManagementFactory.getMemoryMXBean(); MemoryUsage usage = memorymbean.getHeapMemoryUsage(); System.out.println("INIT HEAP: " + usage.getInit()); System.out.println("MAX HEAP: " + usage.getMax()); System.

  • C语言中可变参数的使用方法示例

    前言 在C语言程序编写中我们使用最多的函数一定包括printf以及很多类似的变形体.这个函数包含在C库函数中,定义为 int printf( const char* format, ...); 除了一个格式化字符串之外还可以输入多个可变参量,如: printf("%d",i); printf("%s",s); printf("the number is %d ,string is:%s", i, s); 格式化字符串的判断本章暂且不论,下面分析一

  • C#中调用MySQL存储过程的方法

    本文实例讲述了C#中调用MySQL存储过程的方法.分享给大家供大家参考.具体如下: 这段代码演示在 C# 程序中调用 MySQL 的存储过程,没有返回值,没有参数传递. MySqlConnection myConnection; myConnection = new MySqlConnection(); myConnection.ConnectionString = "database="+database+";server="+ server+";use

随机推荐