分类:
2006-06-11 19:20:58
链接指示符(linkage directive) extern “C” 的问题:
如果我们在自己的程序中要用到其它语言所编写的函数,那么我们的调用函数就必须告诉编译器使用不同的要求。
因为函数在函数名的生成,参数的入栈,栈的清空等等方面与所使用的语言是密切相关的。
Linkage director 告诉编译器,该函数是用其他的程序设计语言编写的。Linkage director 有两种形式:
1.单一的语句(single statement)形式:
extern “C” void exit(int );
2.复合的语句(compound statement)形式:
extern “C”
{
int printf(const char *…..);
int scanf(const char *..);
}
或:
extern “C”
{
#include
}
这里{ }只是一个分割符,用来说明在那个链接指示符用在那些声明上,没有其它的意义了,也就是说在花括号内声明的函数是可见的了!不要胡思乱想!
extern “XX”可以告诉编译器,该函数是用其它语言来编写的,但强制这些函数采用XX语言的方式进行编译。
举个例子说吧!
C接口的方法:
C中调用C++函数:
生成一个工程叫demo
加入一个demo.c 内容如下:
int add(int a,int b );//有无都可
int main()
{
int i=add(3,2);
printf("i=%d",i);
return 0;
}
再加入一个add.cpp,(注意扩展名,强制控制台生成两种文件类型)内容如下:
int add(int a,int b)
{
return a+b;
}
如果这样编译的话,会产生下面的错误:Linking...
demo.obj : error LNK2001: unresolved external symbol _add
Debug/demo.exe : fatal error LNK1120: 1 unresolved externals
Error executing link.exe
根据提示说是没有找到-add,可是我明明定义了!.
为了搞清这个这个问题,就要搞清楚到底C的函数和CPP的函数在编译之后,会变成什么样子!
C++支持重载,C中不支持,而C++怎么区分这些重载函数呢,究竟调用那下一个呢?C++会能过不同的参数来选择不同的 具有相同名称的不同的实现函数的调用。C++函数被编译以后,函数会附带一些附加的参数信息!
我们以实事说话:
在add.cpp中再加入一个同名函数实现重载
float add(float fa,float fb)
{
return fa+fb;
}
int add(int a,int b)
{
return a+b;
}
单独编译此文件没有问题!
输出一个map文件,找到add函数:
Address Publics by Value Rva+Base Lib:Object
0001:00000030 _main 00401030 f demo.obj
0001:00000080 ?add@@YAMMM@Z 00401080 f add.OBJ
0001:000000b0 ?add@@YAHHH@Z 004010b0 f add.OBJ
明白了吧!
精华都在这里了!
提示说找不到_add,原来是在C语言被编译过程中,自动加上了下画线了!
看看main就知道了!
而在C++中,函数被扩展了,?add@@YAMMM@Z,前面两个@相当于左括号,后面一个相当于右括号,中间的就是用来表示参数的了!简单吧!
两个重载函数使用了不同的参数,一个是整数,一个是浮点数,当这两个函数进行连接时,在相应的C++的连接过程中,编译器也会以编译出来的名字到对应的OBJ文件中查找。在C++编译中,会把参数的类型,名字和函数的名字组合成为一个新的函数,这样就和程序定义的函数名这相一致!
现在在看看错误提示,我们的输出文件中也找不到这个_add,所以程序不可能连接上。知道原因就解决吧!
最直接的方法就是修改C++文件的编译结果,让它产生一个对应C的函数名字,也就是按C的方法来生成对应的函数。
在C++的头文件中加入:
extern "C" int add(int a,int b);
extern “C” 语句的作用就是把C++函数按C约定编译!
运行,OK!
Address Publics by Value Rva+Base Lib:Object
0001:00000030 _main 00401030 f demo.obj
0001:00000080 ?add@@YAMMM@Z 00401080 f add.obj
0001:000000b0 _add 004010b0 f add.obj
变了吧!
那么C++中调用C呢!
由于两者之间的关系,C++中可以说是直接调用就可以了!不过这是有条件的!
把demo.c改成:
int substract(int a,int b)
{
return a-b;
}
void hello()
{
printf("hello");
}
add.cpp改为:
void hello();
int substract(int a,int b);
int main()
{
hello();
int c=substract(4,1);
printf("c=%d",c);
return 0;
}
编译运行,出现了上次一样的错误:
加上
extern "C"
{ void hello();
int substract(int a,int b);
}
就OK了!
这是两者的map 比较:
0001:00000030 _substract 00401030 f demo.obj
0001:00000060 _hello 00401060 f demo.obj
0001:00000030 _substract 00401030 f demo.obj
0001:00000060 _hello 00401060 f demo.obj
发现是一样的!
问题在于你在add.cpp前面加入了:
void hello();
int substract(int a,int b);
这样就把两个函数编译成了C++方式,去找它的实现代码的时候,肯定出错了!
而在C调用C++的时候, int add(int a,int b );加与不加,系统只是会给出一个提示,
这或许就是C++强的地方吧!
把错误消灭在初期!
Extern “C”是唯一被保证由所有的C++实现都支持的。
链接库头文件:
//head.h
class A
{
public:
A();
virtual ~A();
int gt();
int pt();
private:
int s;
};
.cpp
//firstso.cpp
#include
#include "head.h"
A::A(){}
A::~A(){}
int A::gt()
{
s=10;
}
int A::pt()
{
std::cout< }
编译命令如下:
g++ -shared -o libmy.so firstso.cpp
这时候生成libmy.so文件,将其拷贝到系统库里面:/usr/lib/
进行二次封装:
.cpp
//secso.cpp
#include
#include "head.h"
extern "C"
{
int f();
int f()
{
A a;
a.gt();
a.pt();
return 0;
}
}
编译命令:
gcc -shared -o sec.so secso.cpp -L. -lmy
这时候生成第二个.so文件,此时库从一个类变成了一个c的接口.
拷贝到/usr/lib
下面开始调用:
//test.c
#include "stdio.h"
#include "dlfcn.h"
#define SOFILE "sec.so"
int (*f)();
int main()
{
void *dp;
dp=dlopen(SOFILE,RTLD_LAZY);
f=dlsym(dp,"f");
f();
return 0;
}
编译命令如下:
gcc -rdynamic -s -o myapp test.c
运行Z$./myapp
10
$