写点什么

C 语言与 C++ 语言混合编程的方法

作者:王玉川
  • 2023-11-15
    上海
  • 本文字数:3281 字

    阅读完需:约 11 分钟

在实际的开发工作中,有时会受到现有代码库的约束,必须对 C 和 C++语言进行混合编程。


而要实现混合编程的主要办法就是声明:extern "C"。被它修饰的变量和函数,将会按照 C 语言方式进行编译和连接。

C++调用 C

从 C++调用 C 语言的函数会比较简单,只需要对函数进行如下的修改:


// C function to be called by C and C++#ifdef __cplusplusextern "C"{#endif
void foo_with_C();
#ifdef __cplusplus}#endif
复制代码


通过 #ifdef __cplusplus 这个宏定义,使得 extern "C"声明在 C++时启用、C 时不启用。这样此函数既可用于 C 语言,也可用于 C++语言。


我们在调用标准库的函数时,其实标准库已经把这一步做好了,所以感觉 C++可以透明的调用 C 的函数。

C 调用 C++

而从 C 语言调用 C++会比较麻烦一些,需要针对 C 语言声明各种 API 函数,然后在 API 函数里再用 C++的语法去调用原来的 C++代码。基础的原理也是依赖于 extern "C"这个声明。


以下用一个简单的例子来讲解这个过程。


假设现有的代码库里面,已经有了一个 Employee 的 C++类。以下分别为该类的头文件和 cpp 文件。


// Employee.h// Pure C++ code// Only for C++
#ifndef EMPLOYEE_H#define EMPLOYEE_H
#include <string>
class Employee{public: Employee(int id, const std::string& first_name, const std::string& last_name, float salary); ~Employee();
int GetID() const; void SetID(int val);
std::string GetFirstName() const; void SetFirstName(const std::string& val);
std::string GetLastName() const; void SetLastName(const std::string& val);
float GetSalary() const; void SetSalary(float val);
void AdjustSalary(float delta);
std::string Summary() const;
private: int id; std::string firstName; std::string lastName; float salary;};
#endif
复制代码


// Employee.cpp// Pure C++ code// Only for C++
#include <iostream>#include <sstream>#include "Employee.h"
using namespace std;
Employee::Employee(int id, const string& first_name, const string& last_name, float salary){ this->id = id; this->firstName = first_name; this->lastName = last_name; this->salary = salary;}
Employee::~Employee(){}
int Employee::GetID() const{ return id;}
void Employee::SetID(int val){ id = val;}
string Employee::GetFirstName() const{ return firstName;}
void Employee::SetFirstName(const string& val){ firstName = val;}
string Employee::GetLastName() const{ return lastName;}
void Employee::SetLastName(const string& val){ lastName = val;}
float Employee::GetSalary() const{ return salary;}
void Employee::SetSalary(float val){ salary = val;}
void Employee::AdjustSalary(float delta){ salary += delta;}
string Employee::Summary() const{ stringstream ss; ss << "ID: " << id << ", Name: " << firstName << " " << lastName << ", Salary: " << salary; return ss.str();}
复制代码


现在,我们有个 C 语言的项目,需要复用这个类的功能。


为了在 C 语言复用 Employee 类,我们需要创建一个 Wrapper,包括头文件和 cpp 文件。


其中,头文件里面,根据需要声明各个 API 函数。该文件会被 C 语言使用,需要用 extern "C"来声明 API 函数,而且不能使用 C++独有的语法与功能(比如 class)。


cpp 文件,包括了各个 API 函数的实现。针对 C++编译器,可以使用 C++的语法与功能。


参考代码如下所示:


// Employee_API.h// Need to support both C and C++// Wrap C++ classes into C interfaces// C calls C++ interfaces by including this file
#ifndef EMPLOYEE_API_H#define EMPLOYEE_API_H
#ifdef __cplusplus// Link with C wayextern "C" {#endif
void* Employee_Create(int id, const char* first_name, const char* last_name, float salary);void Employee_Adjust_Salary(void* employee, float delta);void Employee_Summary(void* employee, char* ret);void Employee_Destroy(void* employee);
#ifdef __cplusplus}#endif
#endif
复制代码


// Employee_API.cpp// Only for C++
#include "Employee_API.h"#include "Employee.h"
#ifdef __cplusplusextern "C" {#endif
void* Employee_Create(int id, const char* first_name, const char* last_name, float salary){ return new Employee(id, first_name, last_name, salary);}
void Employee_Adjust_Salary(void* employee, float delta){ Employee *emp = (Employee *)employee; if (!emp) { return; }
emp->AdjustSalary(delta);}
void Employee_Summary(void* employee, char* ret){ Employee *emp = (Employee *)employee; if (!emp) { return; }
std::string info = emp->Summary(); info.copy(ret, info.length());}
void Employee_Destroy(void* employee){ Employee *emp = (Employee *)employee; if (emp) { delete emp; }}
#ifdef __cplusplus}#endif
复制代码


相当于我们把类的成员函数拆出来,变成一个个普通的函数。原来的类实例,则变成一个函数的指针参数。


接下来,我们就可以在 C 语言里面通过这个 Wrapper 来使用 C++的功能了。


下面是个简单的 C 语言例子:


#include <stdio.h>#include <string.h>#include "Employee_API.h"
int main(int argc, char const *argv[]){ printf("Demo to show how to call C++ class and methods from C.\n");
void* tom = Employee_Create(100, "Tom", "Jerry", 8888.88); if (!tom) { return 1; }
const int max_len = 256; char info[max_len]; memset(info, '\0', sizeof(info)); Employee_Summary(tom, info); printf("%s\r\n", info);
Employee_Adjust_Salary(tom, 1111.11);
memset(info, '\0', sizeof(info)); Employee_Summary(tom, info); printf("%s\r\n", info);
Employee_Destroy(tom);
return 0;}
复制代码


至此,代码方面的工作已经完成。接下来有两种方法完成混编的工作。

1. 通过动态链接库.so 文件

第一种做法,是把原来的 C++代码,还有新加的 Wrapper 代码,编译成一个动态链接库;然后 C 语言的代码 link 这个库,得到可执行文件。


具体做法如下:


$ g++ -Wall -fpic -shared Employee.cpp Employee_API.cpp -o libemployee_api.so$ gcc -Wall -c main.c -o main.o$ gcc -Wall main.o -o mixed -L . -lemployee_api
复制代码


这样,得到一个.so 动态链接库,和一个 mixed 程序。


通过指定动态链接库搜索目录,可以启动 mixed 程序:


$ LD_LIBRARY_PATH="$(pwd)"$ export LD_LIBRARY_PATH$ ./mixedDemo to show how to call C++ class and methods from C.ID: 100, Name: Tom Jerry, Salary: 8888.88ID: 100, Name: Tom Jerry, Salary: 9999.99
复制代码


可以看到,程序正常运行、输出期望的结果了。

2. 通过连接 stdc++和.o 文件

第二种做法,则是把各个 cpp 文件都变成.o 文件,最后与 C 语言的.o 文件 link 到一起,得到可执行文件。具体做法如下:


$ g++ -Wall -c Employee.cpp -o Employee.o$ g++ -Wall -c Employee_API.cpp -o Employee_API.o$ gcc -Wall -c main.c -o main.o$ gcc -Wall Employee.o Employee_API.o main.o -o mixed -lstdc++
复制代码


这样,得到一个 mixed 程序。


直接运行该程序:


$ ./mixDemo to show how to call C++ class and methods from C.ID: 100, Name: Tom Jerry, Salary: 8888.88ID: 100, Name: Tom Jerry, Salary: 9999.99
复制代码


可以看到,这种方法也是可以正常运行、输出期望结果的。


实际项目中,具体选用哪种方法,可以根据项目的情况决定。但不管用哪种方法,繁琐的 Wrapper 封装成 API 的步骤,是少不了的。

用户头像

王玉川

关注

https://yuchuanwang.github.io/ 2018-11-13 加入

https://www.linkedin.com/in/yuchuan-wang/

评论

发布
暂无评论
C语言与C++语言混合编程的方法_c_王玉川_InfoQ写作社区