1. 程式人生 > >c與c++相互呼叫機制分析與實現

c與c++相互呼叫機制分析與實現

c++通常被稱為Better c,多數是因為c++程式可以很簡單的呼叫c函式,語法上基本實現相容。最常用的呼叫方式就是c++模組呼叫c實現的dll匯出函式,很簡單的用法,使用extern "C"將c標頭檔案或者函式修飾下。

本文主要涉及到在c模組中如何呼叫c++函式,或者換個名字,extern "C"在c語言中的功能介紹

c++中的extern "C"

通常,我們在需要呼叫c函式或者c實現的模組時,需要使用extern "C"修飾下對應的部分程式碼,告訴c++編譯器按照c的呼叫規約呼叫相關模組程式碼。常見的形式如下:

extern "C"
{
    // ffmpeg public header
    #include "avutil.h"
    #incluee "avcodec.h"

    // 函式宣告,以c語言呼叫
    int Func(int param);
}

c語言中的extern "C"

近期在看JNI的呼叫實現機制,不自覺的在想c能呼叫c++模組嗎?
基本的思路是來在於c++語言提供的extern "C"機制,既然可以在c++中寫c模組,ok,那隻需要一箇中間層就可以讓c呼叫c++的模組。

普通函式呼叫

在c++實現如下函式:

// in header file(.h)
extern "C" int FunCppDecorate(int param);

// in implenmentation file(.cpp)
int FunCppDecorate(int param)
{
    printf("decorating by extern c, you have rights to invoke cpp function in c\nwith input %d\n"
            , param);
    return (param + 1);
}

在c中按照下面方式呼叫

// declaration
int FunCppDecorate(int param);

// invoke
FunCppDecorate(1);

過載函式呼叫

由於c不支援過載函式,如果需要c呼叫c++過載函式需要顯式的給出呼叫的方式,並在c宣告時給出對應對應機制。
在c++實現如下函式:

// in header file(.h)
void OverloadFunc(int param, bool is_c=false);
void OverloadFunc(double param, bool is_c=false);

extern "C"
{
    void OverloadDecorate_i(int param);
    void OverloadDecorate_d(double param);
}
    

// in implenmentation file(.cpp)
// ...
void OverloadDecorate_i(int param)
{
    OverloadFunc(param, true);
}

void OverloadDecorate_d(double param)
{
    OverloadFunc(param, true);
}

在c中按照下面方式呼叫

// declaration
void OverloadDecorate_i(int param);
void OverloadDecorate_d(double param);

// invoke
OverloadDecorate_i(1);
OverloadDecorate_d(2.0);

類成員函式的呼叫

由於c++中類具有特殊的編譯器附加的構造和解構函式,為了在c中可以訪問c++的類,需要做一些c++編譯器實現的功能,比如物件的構造和析構。c不能直接使用class名稱,需要使用struct作為中轉。實現呼叫如下:

// in header file(.h)
class AType
{
public:
    AType();
    ~AType();
    
    void MemFunc(int value);
};

extern "C"
{
    struct TagAType * CreateInstance();
    void DestoryInstance(struct TagAType ** atype);
    void ClassMemFunc(struct TagAType * pthis, int param);
}
    

// in implenmentation file(.cpp)
// ...
extern "C" struct TagAType
{
    AType a;
};

struct TagAType * CreateInstance()
{
    return (TagAType*)malloc(sizeof(TagAType));
}
void DestoryInstance(struct TagAType ** atype) 
{
    if (NULL != atype && NULL != *atype)
    {
        free(*atype);
        atype = NULL;
    }
}
void ClassMemFunc(struct TagAType * pthis, int param)
{
    if(NULL != pthis)pthis->a.MemFunc(param);
}

在c中按照下面方式呼叫

// declaration
struct TagAType;
struct TagAType * CreateInstance();
void DestoryInstance(struct TagAType ** atype);
void ClassMemFunc(struct TagAType * pthis, int param);

// invoke
struct TagAType * obj = CreateInstance();
ClassMemFunc(obj, 12);
DestoryInstance(&obj);

小結

相關程式碼可以從我的git下載:https://git.oschina.net/Tocy/SampleCode.git ,位於c_c++目錄下,名字字首為1-c-invoke-cpp*。
其中四個檔案,1-c-invoke-cpp.cpp(h)是c++中的實現檔案(標頭檔案),1-c-invoke-cpp-main.c(h)是c中的實現檔案(標頭檔案),其中包含主函式的測試程式碼。
編譯和執行命令可以參考如下:

g++ -c 1-c-invoke-cpp.cpp
gcc -c 1-c-invoke-cpp-main.c
gcc 1-c-invoke-cpp.o 1-c-invoke-cpp-main.o -o invoke.exe
invoke
pause

針對c++實現中的extern "C"修飾符的作用,可以使用nm命令檢視.o檔案的輸出格式,這是我使用gcc編譯後的輸出

nm 1-c-invoke-cpp.o
...
00000000000000ac T _Z12OverloadFuncdb
0000000000000041 T _Z12OverloadFuncib
0000000000000000 T _Z6printfPKcz
0000000000000000 T _Z8DenyFuncv
0000000000000168 T _ZN5AType7MemFuncEi
0000000000000150 T _ZN5ATypeC1Ev
0000000000000150 T _ZN5ATypeC2Ev
000000000000015c T _ZN5ATypeD1Ev
000000000000015c T _ZN5ATypeD2Ev
00000000000001e4 T ClassMemFunc
000000000000018f T CreateInstance
00000000000001a7 T DestoryInstance
U free
000000000000001b T FunCppDecorate
U malloc
000000000000012c T OverloadDecorate_d
000000000000008d T OverloadDecorate_i

從上面輸出可以明顯看出c++和c的函式編譯之後的修飾規則是不同的。