目录
一、背景
实际场景中可能出现两种情况:
1.有个东西是C++写的,现在有个C语言的程序,需要调用这个东西。
2.有个东西是C写的,现在有个C++的程序,需要调用这个东西。
这里的东西就是动态库或者静态库。
二、准备工作
这是一份博主自己封装的栈的代码
Stack.h
cpp
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
void StackInit(ST* ps);
void StackDestroy(ST* ps);
void StackPush(ST* ps, STDataType x);
void StackPop(ST* ps);
STDataType StackTop(ST* ps);
int StackSize(ST* ps);
bool StackEmpty(ST* ps);
Stack.c
cpp
#include "Stack.h"
// CĿ
void StackInit(ST* ps)
{
assert(ps);
ps->a = NULL;
ps->top = 0; // ps->top = -1;
ps->capacity = 0;
}
void StackDestroy(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->capacity = ps->top = 0;
}
void StackPush(ST* ps, STDataType x)
{
assert(ps);
if (ps->top == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newCapacity);
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
ps->a = tmp;
ps->capacity = newCapacity;
}
ps->a[ps->top] = x;
ps->top++;
}
void StackPop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
ps->top--;
}
STDataType StackTop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
return ps->a[ps->top - 1];
}
int StackSize(ST* ps)
{
assert(ps);
return ps->top;
}
bool StackEmpty(ST* ps)
{
assert(ps);
/*if (ps->top == 0)
{
return true;
}
else
{
return false;
}*/
return ps->top == 0;
}
生成静态库
首先把他封装成静态库
点击创建后会弹出选项,其他选项什么也不要勾选,点击确定即可。
然后把刚刚的代码分别添加到头文件与源文件里面,点击编译就生成了静态库。
后缀是.c调用的就是C语言的编译器;后缀是.cpp就是C++的编译器 。编译一下生成.lib
三、C++项目代用C语言的库
然后重新建立一个C++新项目
cpp
bool isValid(const char* s) {
ST st;
StackInit(&st);
while (*s)
{
if (*s == '('
|| *s == '{'
|| *s == '[')
{
StackPush(&st, *s); //call ? StackPush@@YAXPAUStack@@H@Z
++s;
}
else
{
// 遇到右括号了,但是栈里面没有数据,说明
// 前面没有左括号,不匹配,返回false
if (StackEmpty(&st))
{
StackDestroy(&st);
return false;
}
STDataType top = StackTop(&st);
StackPop(&st);
if ((*s == '}' && top != '{')
|| (*s == ']' && top != '[')
|| (*s == ')' && top != '('))
{
StackDestroy(&st);
return false;
}
else
{
++s;
}
}
}
// 如果栈不是空,说有栈中还有左括号未出
// 没有匹配,返回是false
bool ret = StackEmpty(&st);
StackDestroy(&st);
return ret;
}
int main()
{
cout << isValid("{[]}") << endl;
cout << isValid("([)]") << endl;
//printf("%d\n", isValid("{[]}"));
//printf("%d\n", isValid("([)]"));
return 0;
}
在这个项目里通过相对路径的方式包上Stack.h的头文件
配置静态库
现阶段,我是一个C++项目,库是用C写的,编译的时候可以编译通过,但是链接的时候出错了,找这些函数的时候找不到,这是因为我们只包含了一个.h只有函数的声明。而没有函数的地址。所以我们要链接上静态库。所以静态库不是只包含了头文件就可以用的,还需要做一些配置。
然后点击应用,点击确定。这样就添加上静态库了。
再次编译,还是找不到。不是已经添加库了吗?
原因就在于库是.c的。
我们把库改成.cpp然后编译。
这个时候执行程序,运行成功。所以C++项目去调用C++的库,经过配置完后是完全没有问题的。
但是只要我们把库改成.c就,源.cpp这个程序就运行出错了。这个库是完全可以用C语言写的。我用C++的项目去调用这个库,显然是调用失败的,原因就是C语言和C++生成的函数名修饰规则不一样。C语言直接用的就是函数名,C++则有自己的规则,g++的函数名修饰规则_Z+函数名长度+函数名+参数首字母。
eg:windows下调用StackPush按照这样的名字(StackPush@@YAXPAUStack@@H@Z)去找该函数地址(去编译出来的D_S.lib里面去找这个函数地址)。但是D_S.lib它的符号表里面没有这个名字+地址的映射。因为D_S.lib是C语言写的,按照C语言的规则,C语言是没有函数名修饰规则的,直接就是这个StackPush这个名字+地址的映射。所以根本就找不到。
这个时候为了让C++调用C。就可以使用extern C。
用上这个语法后,就告诉了C++的编译器,这些代码(指C语言写的静态库)是用C的风格去编译的,接下来你去链接的时候不要用StackPush@@YAXPAUStack@@H@Z这样的名字去找函数,而是用StackPush这样的名字去找,才能找到。
此时程序正确运行。
C++项目要用C实现的DS静态库:1.包含文件夹 2.在工程属性中配置静态库的目录和添加静态库。
四、C语言的项目调用C++的库
程序是C语言,库是C++
C程序报错。找不到函数,原因一样是函数名修饰规则。因为库是C++写的,它的函数名是被修饰的。所以C程序同样找不到。
所以我们就要在静态库里面进行修改。
此时发现C程序可以正常运行了。但是这个仅仅是个随机事件,这种情况是特定于使用Visual Studio 2019的C++编译器的优化行为,它检测出使用了extern C,用了C++的编译器去编译了.c代码。在别的环境下是会报错的,预处理阶段会把包含的.h文件展开,又因为extern C是C++特有的语法,C的编译器是压根不认识它的,所以就会报错。所以这样写是不行的,这个程序成功只是特殊情况,不具有普遍性。
解决方案
__cplusplus是C++特有的标识符,如果检测到__cplusplus说明是C++的编译器,如果没有检测到__cplusplus说明是C的。这样的意思就是如果检测到了__cplusplus就用extern C用C的函数名修饰规则。这样的话就既能让C++的动态库按C的函数名修饰规则去编译,又能让C程序栈预处理阶段不报错。
或者这样进行使用
五、总结
C++程序调用C的库,在C++程序中加extern "C"。在C++项目里,告诉C++编译器,extern C{}里面的函数是C编译器编译的,链接的时候用C的函数名规则去找,就可以链接上。
C程序调用C++的库。在C++库中加extern "C"。在C++静态库,extern C告诉编译器以下函数按照C的函数名修饰规则去处理。