模块文件*.def存在的必要性
原以为在dll的编写中,函数的导出使用__declspec(dllexport)就十分方便了,为什么还会有*def存在的必要呢? 直到发现这种情况才知道*.def文件还是有用的。
项目中使用VS2005来开发dll,为了能在多种平台下使用在定义API接口时是使用的如下形式: extern "C" __declspec(dllexport) int __stdcall add(int a, int b); 一开始我使用的是调用静态调用的方式来访问这些API,直接用这些函数名就可以了。[静态调用: #include "头文件.h", #pragma comment(lib, "*.lib")] 可是在使用动态调用时就发现找不到函数的问题[GetProcAddress(m_hModule, "add") == NULL]。
查找原因: 使用Dependency Walker查看dll, 发现函数名变成了: _add@8,查阅资料才发现这是使用 __stdcall 的原因,但是又必须使用原来那种形式来定义API函数,怎样才能使导出的函数名不发生改变呢? 问题分析及解决方案:http://blog.csdn.net/blpluto/archive/2010/07/06/5715182.aspx
这篇文章很清楚的说明了动态链接库在使用中出现的问题及解决方案, 也正是这文章让我自己了*.def 文件的作用。在我现在做的这项目中还真的只能使用*.def来解决了。
C語言中extern c
c与c++程序连接问题
它们之间的连接问题主要是因为c c++编绎器对函数名译码的方式不能所引起的,考虑下面两个函数
/* c*/
int strlen(char* string)
{
...
}
//c++
int strlen(char* string)
{
...
}
两个函数完全一样。在c在函数是通过函数名来识别的,而在C++中,由于存在函数的重载问题,函数的识别方式通函数名,函数的返回类型,函数参数列表三者组合来完成的。因此上面两个相同的函数,经过C,C++编绎后会产生完全不同的名字。所以,如果把一个用c编绎器编绎的目标代码和一个用C++编绎器编绎的目标代码进行连接,就会出现连接失败的错误。
解决的方法是使用extern C,避免C++编绎器按照C++的方式去编绎C函数
在头文件中定义:
extern "C" int strlen(char* string)
或
extern "C"
{
int strlen(char* string)
}
当C编绎器遇到extern "C"的时候就用传统的C函数编译方法对该函数进行编译。由于C编绎器不认识extern "C"这个编绎指令,而程序员又希望C,C++程序能共用这个头文件,因此通常在头文件中使用_cplusplus宏进行区分:
#if define _cplusplus
extern "C"{
#endif
int strlen(char* string)
#ifdefine _cplusplus
}
#endif
#define CALLBACK __stdcall
#define WINAPI __stdcall
那么,除了__stdcall,还有别的调用类型吗?究竟什么是调用类型呢?我的理解是:调用类型就是如何使用函数参数的一种规则。有三种调用类型:__fastcall、__cdecl、__stdcall。
1、__cdecl调用类型:
这是C的调用规则。对于所有非C++成员函数或未标有__stdcall或__fastcall的函数来说,这是默认调用规则。
2、__fastcall调用类型:
从字面意思可知,这是一种快速调用。因为CPU的寄存器会被使用来存放函数参数列表中的头几个参数。而剩下参数将被从右至左地推倒堆栈上。被调用函数将从寄存器和堆栈获得函数参数。在x86中,ECX和EDX一般被用来存放开始的参数。在.NET中,为了性能上的快速,就是使用ecx和edx来实现 __fastcall的。
3、 __stdcall调用类型:
该调用只是通过堆栈来push和pop参数。push参数时,顺序是从右到左。
现在,你应该明白了吧。最后,我带一句。三种调用类型在VC编译器中对应/Gd、/Gr、/Gz三个编译选项
调用C/C++制做的DLL文件中导出函数的几点说明
用某种语言调用C/C++制做的DLL文件中导出函数时,有几点注意:
一、“_stdcall”的作用
在C/C++中函数默认Calling Conventions(调用约定)是:
参数由右向左压入栈,由调用者清空栈。,
在FORTRAN、PASCAL、VisualBASIC等语言中,函数的Calling Conventions是:
参数由右向左压入栈,由被调函数清空栈。
那么在C/C++中使用_stdcall声明导出函数,就可以指定C/C++按照FORTRAN等的调用约定申明该函数,这时FORTRAN等其它语言中再调用这些函数就不会出现报错:run-time '49':Bad DLL call conventions.
二、加“extern "C"”的作用
extern "C" int _stdcall Extest();
如上的原型,函数名符号会被VC编译器处理为 _Extest@0
int _stdcall Extest();
如上的原型,函数名符号会被VC编译器处理为 ?Extest@@YGXXZ
C语言的编译链接环境不能处理第二种方式的修饰名,所以如果你的DLL要在C和C++的环境下都适用,那么应该使用第一种命名方式。
例如:在MFC的类中调用自己写的C函数,有时出现错误说无法找到函数的定义,原因是由于C 和C++的编译器对函数的修饰名不同,C++的修饰名中包括了各参数类型,因此通常情况下,C++程序无法找到C库中的函数,需要在声明C函数时加上 extern "C"的说明: extern "C" void foo();
C++编译器就会用C的修饰名方式来进行连接调用。同样,当C需要调用C++函数时,该C++函数也必须声明为extern "C"。通常可以在C的头文件里这样定义:
#ifdef __cplusplus
extern "C" {
#endif
...
#ifdef __cplusplus
}
#endif
就可兼容C和C++程序。