Linux下C调用C++接口详解

    C++做久了,经常用C++的方式去思考问题,有时候就突然发现自己不太会写C程序了。写程序的时候,难免会用到第三方插件或者是库,而这些插件或者库很多时候都不能完全满足我们的需求,遇到这种情况,如果全是C++,那好办,写个适配器就OK了,关于适配器模式参考我的博客《C++ Adaptor 设计模式》    如果要提供给C程序使用,那就需要自己封装C程序可以使用的库。前几天在CSDN Linux/Unix版闲逛的时候,遇到一位网友,他问这样的问题:(原话我记不住了,大致是这个意思)我要要封装一个C++接口提供给C程序使用,C++程序完全没有问题,可以运行,但是封装的时候,我使用g++编译,就没问题,但是预期的结果是使用gcc编译程序,可是使用gcc编译的时候确保错,很多 undefined...我想熟悉Linux编程的人一看这个问题都知道是怎么回事。这里我就不标新立异了,我只想总结下如果让C调用C++接口

         再将接口封装之前先将建C/C++的一些特性有助于后面的理解。

        C++创始人在编写C++的时候,C语言正盛行,他不得不让C++兼容C。C++最大的特性就是封装,继承,多态,重载。而这些特性恰恰是C语言所不具备的。至于多态,核心技术是通过虚函数表实现的,其实也就是指针。而对于重载,与C语言相比,其实就是编译方式不同而已: C++编译方式和C编译方式。对于函数调用,编译器只要知道函数的参数类型和返回值以及函数名就可以进行编译连接。那么为了让C调用C++接口或者是说C++调用C接口,就必须是调用者和被调用者有着同样的编译方式。这既是extern "C"的作用,extern “C”是的程序按照C的方式编译。我们先来看看C++和C两种编译方式对于究竟有何不同,由于C只考虑函数调用,这里只讨论函数的差别。下来看一段源代码:

我们用一个很简短的代码说明问题:

//cplus.cpp
//按照C++方式编译程序
int Operation(int)

键入命令编译cplus: g++ -c cplus.cpp -o cplus.o 产生了目标文件cplus.o。我们来看看该目标文件中的符号

使用nm命令查看内部符号:nm cplus.o

内容很简单:00000000 T _Z9Operationi

再来看看加 exern "C"按照C方式编译程序:

extern "C"
int Operation(int)

同样使用上面的命令产生cplus.o。然后查看符号如下:

00000000 T Operation

对比下可以发现,使用C++方式编译函数多了个_Z9前缀和i后缀,其中i指的是参数类型。这下明白了,因为C不存在重载,只需要知道函数名称就可以确定函数,而C++有重载,需要根据参数类型和返回类型才可以唯一确定一个函数。


    说道这里,大家估计已经理解的差不多了。提供给C的接口必须加 extern "C"。这里还只是确定了编译方式,extern "C"只能让编译器安C的方式编译。但是C并不认识

extern "C",这里还要加一道工序:在C文件中 extern下接口。这样C程序就认识接口函数了。下面以一个简单的例子来说明具体如何让封装C++接口给C使用。

//myclass.h
#include <iostream>
using namespace std;

class Myclass
{
    public:
        Myclass(){}
        ~Myclass(){}
        void Operation();

};


//myclass.cpp
extern "C"
void Myclass::Operation
{
    cout << "Hi, Harlen" << endl;
}

编译命令:g++ -c myclass.cpp -o myclass.o

//interface.cpp

#include "myclass.h"

void interface()
{
	Myclass obj;
	obj.Operation();
}


编译命令:g++ -c interface.cpp -o interface.o

这样,其实接口就已经准备好了。一种方式是使用命令:ar rs libinterface.a interface.o myclass.o产生静态库提供接口。

另一种方式是使用gcc,将调用程序的.o目标文件和myclass.o, interface.o一起编译成可执行程序。

//main.c
extern interface();

int main(int argc, char**argv)
{
    interface()
}

编译:gcc -c main.c -o main.o

:gcc -o main interface.o myclass.o -lstdc++到此为止,接口已经提供完成。C程序中就可以使用interface接口了。


  • 5
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
文中是linuxC++动态库 实现接口提供类导出的一个例子 注意其中使用函数返回基类指针的用法,因为Linux的动态链接库不能像MFC中那样直接导出类 一、介绍 如何使用dlopen API动态地加载C++函数和类,是Unix C++程序员经常碰到的问题。 事实上,情况偶尔有些复杂,需要一些解释。这正是这篇mini HOWTO的缘由。 理解这篇文档的前提是对C/C++语言中dlopen API有基本的了解。 这篇HOWTO的维护链接是: http://www.isotton.com/howtos/C++-dlopen-mini-HOWTO/ 二、问题所在 有时你想在运行时加载一个库(并使用其中的函数),这在你为你的程序一些插件或模块架构的时候经常发生。 在C语言中,加载一个库轻而易举(调用dlopen、dlsym和dlclose就够了),但对C++来说,情况稍微复杂。 动态加载一个C++库的困难一部分是因为C++的name mangling (译者注:也有人把它翻译为“名字毁坏”,我觉得还是不翻译好), 另一部分是因为dlopen API是用C语言实现的,因而没有提供一个合适的方式来装载类。 在解释如何装载C++库之前,最好再详细了解一下name mangling。 我推荐您了解一下它,即使您对它不感兴趣。因为这有助于您理解问题是如何产生的,如何才能解决它们。 1. Name Mangling 在每个C++程序(或库、目标文件)中, 所有非静态(non-static)函数在二进制文件中都是以“符号(symbol)”形式出现的。 这些符号都是唯一的字符串,从而把各个函数在程序、库、目标文件中区分开来。 在C中,符号名正是函数名:strcpy函数的符号名就是“strcpy”,等等。 这可能是因为两个非静态函数的名字一定各不相同的缘故。 而C++允许重载(不同的函数有相同的名字但不同的参数), 并且有很多C所没有的特性──比如类、成员函数、异常说明──几乎不可能直接用函数名作符号名。 为了解决这个问题,C++采用了所谓的name mangling。它把函数名和一些信息(如参数数量和大小)杂糅在一起, 改造成奇形怪状,只有编译器才懂的符号名。 例如,被mangle后的foo可能看起来像foo@4%6^,或者,符号名里头甚至不包括“foo”。 其中一个问题是,C++标准(目前是[ISO14882])并没有定义名字必须如何被mangle, 所以每个编译器都按自己的方式来进行name mangling。 有些编译器甚至在不同版本间更换mangling算法(尤其是g++ 2.x和3.x)。 即使您搞清楚了您的编译器到底怎么进行mangling的,从而可以用dlsym调用函数了, 但可能仅仅限于您手头的这个编译器而已,而无法在下一版编译器下工作。 三、类 使用dlopen API的另一个问题是,它只支持加载函数。 但在C++中,您可能要用到库中的一个类,而这需要创建该类的一个实例,这不容易做到。 四、解决方案 1. extern "C" C++有个特定的关键字用来声明采用C binding的函数: extern "C" 。 用 extern "C"声明的函数将使用函数名作符号名,就像C函数一样。 因此,只有非成员函数才能被声明为extern "C",并且不能被重载。 尽管限制多多,extern "C"函数还是非常有用,因为它们可以象C函数一样被dlopen动态加载。 冠以extern "C"限定符后,并不意味着函数中无法使用C++代码了, 相反,它仍然是一个完全的C++函数,可以使用任何C++特性和各种类型的参数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值