c++创建dll导出函数名称

0. 生成dll时,导出函数的两种方法

0.1 __declspec(dllexport)关键字

使用 __declspec(dllexport) 从 DLL 导出

0.2 .def文件

使用 DEF 文件从 DLL 导出

0.3 确定要使用的导出方法

闭眼使用.def文件,因为__declspec(dllexport)生成的名字前后加了很多编译相关的字符,而且不同版本编译器可能会有区别。即使extern "C"后,仍然会根据调用方式和参数不同会有区别。而.def不存在这些问题;而且.def可以为每个函数名定义序号。如果需要删除某个导出的函数,.def文件只需要删除那一行即可,不需要修改代码文件;而__declspec(dllexport)需要在代码文件上修改。
最好__declspec(dllexport)和.def文件不要同时使用,用一种即可,虽然大部分时候同时用也不会有太大问题。
参考:确定要使用的导出方法

1. extern "C"的作用

extern "C"的作用是声明以c语言的格式编译当前代码:

  • c语言没有函数重载
  • 编译后的函数名若有参数以"xxx@数字"结尾,“数字"为所有参数占用的内存大小(4位对齐);若无参数则结尾不含”@数字"
  • 编译后的开头字符与调用约定__cdecl(无开头字符)、__stdcall(以‘_’开头)、__fastcall(以‘@’开头)有关

上代码,两个函数,分别以c和c++格式编译,看看效果是什么:

//ApiExport.h

// extern "C" 与 默认c++ 方式的区别
extern "C" __declspec(dllexport) void func1_c();
__declspec(dllexport) void func1_cpp();

//ApiExport.cpp
void func1_c(){}
void func1_cpp(){}

很简单的两个函数,没有输入也没有输出。编译,查看生产的dll导出的函数名称:
在这里插入图片描述
可以看到声明extern "C"编译的函数名和我们定义的函数名是一致的,而以c++方式编译的函数名加了前缀和后缀。
再分析一下编译后的“func1_c”,既没有前缀又没有后缀,说明调用约定是__cdecl(默认调用约定)。

2. __cdecl、 __stdcall、__fastcall

调用约定,约定的什么?
在表面看来,约定了编译后的函数名称;实际约定的是由调用者还是被调用者清理堆栈上的参数

有两派:
__cdecl :调用者清理
__stdcall :被调用者清理
__fastcall比较特殊,没有被标准化,各个编译器实现不一样。
但是,但是!!!在Windows x64环境下编译代码时,只有一种调用约定,也就是说,32位下的各种约定在64位下统一成一种了。(X86调用约定 - 维基百科)

再深入就涉及到汇编了,到此为止;下面讲一下编译后的函数名称的区别。
还是上图上代码:
这里只写出声明代码,实际是没有定义的函数是不会编译的。

//ApiExport.h

#ifdef DLLAPI
#define DLLAPI __declspec(dllexport)
#else
#define DLLAPI __declspec(dllimport)
#endif

#ifndef DLLAPI_EXTERN_C
#define DLLAPI_EXTERN_C extern "C" DLLAPI
#endif

// 以c++方式编译
DLLAPI_EXTERN_C void __stdcall fun_extern_c_stdcall();
DLLAPI_EXTERN_C int __stdcall fun_extern_c_stdcall0();
DLLAPI_EXTERN_C int __stdcall fun_extern_c_stdcall1(int param);
DLLAPI_EXTERN_C int __stdcall fun_extern_c_stdcall2(int param, char param2);
DLLAPI_EXTERN_C int __stdcall fun_extern_c_stdcall3(int param, char param2,long param3);
DLLAPI_EXTERN_C int __cdecl fun_extern_c_cdecl(int param);
DLLAPI_EXTERN_C int __fastcall fun_extern_c_fastcall(int param);

// 以c方式编译
DLLAPI void __stdcall fun_cpp_stdcall();
DLLAPI int __stdcall fun_cpp_stdcall0();
DLLAPI int __stdcall fun_cpp_stdcall1(int param);
DLLAPI int __stdcall fun_cpp_stdcall2(int param, char param2);
DLLAPI int __stdcall fun_cpp_stdcall3(int param, char param2, long param3);
DLLAPI int __cdecl fun_cpp_cdecl(int param);
DLLAPI int __fastcall fun_cpp_fastcall(int param);

上图:
c++创建dll导出函数名称_第1张图片

2.1 C++编译时函数名修饰约定规则:

__stdcall调用约定:
  1)、以"?“标识函数名的开始,后跟函数名;
  2)、函数名后面以”@@YG"标识参数表的开始,后跟参数表;
  3)、参数表以代号表示:
   X–void ,
   D–char,
   E–unsigned char,
   F–short,
   H–int,
   I–unsigned int,
   J–long,
   K–unsigned long,
   M–float,
   N–double,
   _N–bool,
   PA(32位)/PEA(64位)–表示指针,后面的代号表明指针类型,如果相同类型的指针连续出现,以"0"代替,一个"0"代表一次重复;
  4)、参数表的第一项为该函数的返回值类型,其后依次为参数的数据类型,指针标识在其所指数据类型前;
  5)、参数表后以"@Z"标识整个名字的结束,如果该函数无参数,则以"Z"标识结束。
  其格式为"?functionname@@YG***@Z"或"?functionname@@YG*XZ",例如:

  	int __stdcall func_cpp_stacall3(int param,char param2,long param3)
  	编译后函数名:“?func_cpp_stacall3@@YGHHDJ@Z”
  	“?”  以c++方式编译
  	“@@” 后面为参数定义
  	“YG” 调用约定为__stdcall
  	“H” 返回值为 int 型
  	“H” 第一个参数为 int 型
  	“D” 第二个参数为 char 型
  	“J” 第三个参数为 long 型
  	“@Z” 有参函数名后缀
  

		void __stdcall func_cpp_stdcall()
		编译后函数名:“?func_cpp_stdcall@@YGXXZ”
		“?” 以c++方式编译
		“@@” 后接参数定义
		“X” 返回值为 void 
		“XZ” 无参函数后缀

__cdecl调用约定:
  规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的"@@YG"变为"@@YA"。
  
__fastcall调用约定:
  规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的"@@YG"变为"@@YI"。

2.2 C编译时函数名修饰约定规则:

**__stdcall调用约定:**
	以“_”开头,以“@数字”结尾,数字为参数占用的内存字节数。
	
**__cdecl调用约定:**
	不改变函数名称。
	
**__fastcall调用约定:**
	以“@”开头,以“@数字”结尾,数字为参数占用的内存字节数。

总结:

假设函数为:int func(int arg1,char arg2,long arg3);
以下列方式编译后的函数名为:

cdecl stdcall fastcall
以C方式编译 func _func@12 @func@12
以C++方式编译 ?func@@YAHHDJ@Z ?func@@YGHHDJ@Z ?func@@YIHHDJ@Z

为什么是@12?int占4byte,char占1byte,long占4byte,4+1+4=9?只想说一句:内存4位对齐!。注意:指针在32位程序中是4字节,64位程序是8字节

3. 用.def文件定义导出函数

从上一节可以看出,只有以C方式编译且调用约定为“__cdecl”(extern “C” __declspec(dllimport) int function)时函数名不变,其他时候函数名都会变,在大部分时候都没有影响(c/c++提供.lib文件即可、c#需要声明调用约定的特性),但是在delphi调用c/c++ dll时不能直接使用定义的函数名,只能使用编译后的函数名。为了统一编译后的函数名称,只能使用模块定义文件(def文件)定义函数名称,使用模块定义文件可以不用__declspec(dllimport)声明函数,最好都统一在.def文件中声明,方便管理。

看一下.def文件格式:

LIBRARY dllExportTest
EXPORTS
fun_cpp_stdcall @1
  • 文件第一条LIBRARY语句不是必须的,但若有LIBRARY语句则后面必须是生成的dll的文件名;
  • EXPORTS语句之后每一行列出一个已定义的函数名称(未定义的名称也可以,但没什么用)
  • 函数名称后接“@函数序号”,函数序号取值范围为 1 - N(N为导出函数的总数),且不能重复

3.1 在Visual Studio中使用.def文件:

  • 为项目添加模块定义文件
  • 在文件中添加导出函数
  • 设置到项目环境(添加模块定义文件时会自动设置)

c++创建dll导出函数名称_第2张图片

你可能感兴趣的:(C/C++,c++,开发语言,dll导出函数名称,dll)