您的位置:首页 > 其它

__cdecl与__stdcall 调用约定在动态链接库调用中不同的表现

2012-04-06 19:35 375 查看
首先建立__cdecl 调用约定函数的动态链接库。

FirstDll.cpp

#include <windows.h>

BOOL APIENTRY DllMain( HANDLE hModule,
DWORD  ul_reason_for_call,
LPVOID lpReserved
)
{
return TRUE;
}

DLL入口函数。

lib.h

#ifndef LIB_H
#define LIB_H
extern "C" int __declspec(dllexport) add(int x, int y);
#endif
申明导出函数。

lib.cpp

#include "lib.h"

int add(int x, int y)
{
return x + y;
}
实现函数,采用默认的__cdecl调用约定。

编译:

cl FirstDll.cpp /c

cl lib.cpp /c

这里用缺省C运行时库libc.lib,相当于/ML
链接:

link FirstDll.obj lib.obj /nodefaultlib kernel32.lib libc.lib /dll

生成两个文件:FirstDll.lib,FirstDll.dll,前者为后者的导入库

主函数调用,这里先考虑静态调用方式。

//Main.cpp

#include <stdio.h>
#include <stdlib.h>
extern "C" int add(int , int );
int main()
{
int a, b;
a = b = 1;

int c = add(a, b);
printf("%d\n", c);
system("pause");

return 0;
}

编译:

cl Main.cpp /c

链接:

link MainD.obj FirstDll.lib /nodefaultlib kernel32.lib libc.lib

调用成功,屏幕输出2,再考虑动态链接:

//MainD.cpp

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
int main()
{
int a, b;
a = b = 1;
HMODULE hMod = LoadLibrary("FirstDll.dll");
typedef int (*pfunc)(int,int);
pfunc add = (pfunc)GetProcAddress(hMod, "add");
int c = add(a, b);
FreeLibrary(hMod);
printf("%d\n", c);
system("pause");

return 0;
}

可以得到同样的结果,用dumpbin查看FirstDll.dll 中的函数:

dumpbin FirstDll.dll /exports > info.txt

可以看到

    ordinal hint RVA      name

          1    0 00001010 add

也就是说 cdecl 调用约定没有对函数进行重命名。接下去考虑 stdcall 调用约定。

分别调整lib.h,lib.cpp

//lib.h
#ifndef LIB_H
#define LIB_H
extern "C" int __declspec(dllexport) __stdcall add(int x, int y);
#endif

lib.cpp
#include "lib.h"

int __stdcall add(int x, int y)
{
return x + y;
}
其中,显示申明为stdcall调用约定。

编译链接后同样生成lib文件和dll文件。

调整Main.cpp中的函数申明语句为:

extern "C" int __stdcall add(int , int );


编译链接后可以使用,但是用dumpbin查看此时的dll则有

          1    0 00001010 _add@8

这里显示stdcall以将函数重命名。其中8指的是参数占8个字节。

那么再考虑动态链接:

将MainD.cpp中的

typedef int (*pfunc)(int,int);
改为

typedef int (__stdcall *pfunc)(int,int);
运行时出错,而用"_add@8"函数名调用成功。说明显式调用dll函数在stdcall调用约定时需要额外的步骤。

这里考虑两种解决方案:

(1) 使用 link.exe 的 EXPORT 参数;

(2) 使用 DEF 文件。

先考虑第一种方案,调整 FirstDll 的链接命令:

link FirstDll.obj lib.obj /nodefaultlib kernel32.lib libc.lib /dll /export:add=_add@8

替换掉原先的lib和dll,此时调用GetProcAddress中的函数名为"add" 和 "_add@8" 都可以调用成功。

再考虑第二种方案,使用DEF文件:

写一个 t.der 文件:

LIBRARY "FirstDll"
EXPORTS
add @ 1
此外可以调整lib.h中的导出函数申明:

int __stdcall add(int x, int y);

即省略 extern "C" (2013.5.28 批注:静态调用时函数申明也应当省略,并且确保dll的编译器和主调程序的编译器为同一,以确保编译器生成的函数名相同,链接器可以找到外部函数)和 __declspec(dllexport) ,调整FirstDll的链接命令:

link FirstDll.obj lib.obj /nodefaultlib kernel32.lib libc.lib /dll /def:t.def

此时GetProcAddress 中 "add" 为有效函数名,调用成功。除此之外,还可以用函数序号调用:

pfunc add = (pfunc)GetProcAddress(hMod, (LPCSTR)1);


无论是显式动态加载链接(LoadLibrary , GetProcAddress),还是隐式申明函数、链接导入库FirstDll.lib ,都能使用此DLL文件。

小结:当导出函数使用stdcall调用约定时,推荐的方案为:定义一个DEF文件,重新命名函数,同时在dll头文件与调用文件中申明应相同,即同时存在或不存在extern "C" 。

对于 extern "C"

附:lib.h 中申明,lib.cpp 包含 lib.h ,lib.cpp 无论申明与否都按C语言导出函数名

lib.h 中不申明,lib.cpp 包含 lib.h ,lib.cpp 申明则编译不通过

抛开 lib.h ,直接在 lib.cpp 中申明按C语言导出函数名

除此之外按C++导出函数名
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  dll system c library