C语言如何封装CPP代码的接口

为什么要用C语言封装CPP代码?

C++不是兼容C语言的吗?

是的,如果你只是自己开发程序,只用一种语言的话,就没有必要进行封装。如果你开发的是一个C++的.so库,你希望将这个库提供给其他语言使用的话,就涉及到不同语言的相互调用,大家都知道C语言的库是最容易与其他语言交互的。

比如mysql是用C++编写的,但还会提供C语言的调用接口(.h头文件),但是这个头文件与C++所用的头文件是不一样的,因为C语言处理不 了Class这些东西。

这个文章我们只讲如何在C语言中调用C++代码。

基本方法是,写一个 wrapper文件,把 C++类封装起来,对外只提供C语言的接口,和 C++相关的实现在 wrapper的实现文件里实现一遍。

如何封装。

提供一个C语言专用的头文件,里面只提供完成程序功能最基本的C语言类型指针和函数,所有的功能都通过此头文件对应的函数实现。

所有的定义放在extern "C" { }代码块中包裹起来。

看下面的示例:

假定有一个类定义 ,是用C++定义的。

apple.h

#ifndef __APPLE_H__
#define __APPLE_H__


enum class LogLevel {
    Trace  /// Most detailed output
    ,Debug
    ,Info
    ,Warn
    ,Error
    ,Fatal  /// Least detailed output
    ,Current  /// no-op, value indicates current level should be retained
};


class Apple
{
public:
    Apple();
    int GetColor(void);
    void SetColor(int color);
    
private:
    int m_nColor;
};
#endif 

apple.cpp

#include "apple.h"

Apple::Apple() : m_nColor(0)
{
}
 
void Apple::SetColor(int color)
{
    m_nColor = color;
}
 
int Apple::GetColor(void)
{
    return m_nColor;
}

 此时如果直接用C++写main函数是这样的:

#include "apple.h"
#include 
 
// main for cpp
int main(void)
{
    Apple a;
    a.SetColor(1);
    printf("color = %d\n", a.GetColor());
    return 0;
}

编译生成mainpp程序

g++ -g -c apple.cpp   -o apple.o
g++ -g main.cpp -o mainpp  apple.o

但是如果将apple.cpp编译成一个库提供给C语言开发者,C语言肯定是无法调用 的。

所以就要有一个wrapper。

apple_c.h

#ifndef _APPLE_C_H_
#define _APPLE_C_H_


#if defined _WIN32 || defined WIN32  || defined _WINDOWS || defined __CYGWIN__
  #ifdef _DLL
    #ifdef __GNUC__
      #define DLL_PUBLIC __attribute__ ((dllexport))
    #else
      #define DLL_PUBLIC __declspec(dllexport)
    #endif
  #else
    #ifdef __GNUC__
      #define DLL_PUBLIC __attribute__ ((dllimport))
    #else
      #define DLL_PUBLIC __declspec(dllimport)
    #endif
  #endif
  #define DLL_LOCAL
#else
  #if __GNUC__ >= 4
    #define DLL_PUBLIC __attribute__ ((visibility ("default")))
    #define DLL_LOCAL  __attribute__ ((visibility ("hidden")))
  #else
    #define DLL_PUBLIC
    #define DLL_LOCAL
  #endif
#endif



#ifdef __cplusplus
extern "C" {
#endif


typedef enum  {
    LogLevelTrace = 0,  /// Most detailed output
    LogLevelDebug,
    LogLevelInfo,
    LogLevelWarn,
    LogLevelError,
    LogLevelFatal  /// Least detailed output
} AppLogLevel;

typedef void* ApplePtr; //指针类型定义
DLL_PUBLIC ApplePtr GetInstance(void);
DLL_PUBLIC void     ReleaseInstance(ApplePtr ptr);
DLL_PUBLIC void     SetColor(ApplePtr ptr, int color);
DLL_PUBLIC int      GetColor(ApplePtr ptr);

#ifdef __cplusplus
};
#endif

#endif

apple_c.cpp

#include "apple_c.h"
#include "apple.h"
#include 
#include 



#ifdef __cplusplus
extern "C" {
#endif



DLL_PUBLIC ApplePtr GetInstance(void)
{
    return new Apple;
}

DLL_PUBLIC void ReleaseInstance(ApplePtr ptr)
{
 try {
        auto *client = static_cast(ptr);
        delete client;
        ptr = nullptr;
    } catch (std::exception &e) {
        std::cout<< e.what() << std::endl;
    }
}

DLL_PUBLIC void SetColor(ApplePtr ptr, int color)
{
    if (ptr == nullptr) {
        return ;
    }
    auto *pApple = static_cast(ptr);
    pApple->SetColor(color);
}
 
DLL_PUBLIC int GetColor(ApplePtr ptr)
{
    if (ptr == nullptr) {
        return -1;
    }
    auto *pApple = static_cast(ptr);
    return pApple->GetColor();
}




#ifdef __cplusplus
};
#endif

main.c的写法:

#include "apple_c.h"
#include 
 
int main(void)
{
    ApplePtr pApple;
    pApple = GetInstance();
    SetColor(pApple, 1);

    printf("color = %d\n", GetColor(pApple));
    ReleaseInstance(pApple);
    //printf("color = %d\n", GetColor(pApple));// pApple released
    return 0;
}

编译:

apple.cpp生成c++的静态库,再用apple_c.cpp生成一个wrapper的动态库libapple_c.so

再用main.c去调用这个动态库。

# compile .cpp
g++ -g -c apple.cpp   -o apple.o
g++ -g -c apple_c.cpp -o apple_c.o

# build cpp static library
ar rcs libapple.a  apple.o

# build c wraper libapple_c.so dynamic library
g++ -g -fpic -shared  -o libapple_c.so   apple_c.o  -L. -l apple -fvisibility=hidden


# build main.c 
gcc -g main.c -o mainc    -L. -l apple_c

结构体、类。已经在c++中定义过了,只需要定义对应的void *指针类型即可。

类的成员函数都要转换成普通的函数,一般将类对象指针作为第一个参数传入函数中。

enum类要在c头文件中重新定义

Exception处理。 c语言不支持Exception,所以异常需要在c++对应的wrapper cpp文件中进行处理。否则会在运行时出问题。

C语言封装的代码示例:

https://github.com/opentdf/client-cpp/blob/main/src/lib/include/tdf_client_c.h

https://github.com/opentdf/client-cpp/blob/main/src/lib/include/tdf_client.h

参考知识:

Standard C++ icon-default.png?t=N7T8https://isocpp.org/wiki/faq/mixing-c-and-cpp

你可能感兴趣的:(c语言,开发语言,wrapper)