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


    首先建立__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++导出函数名


  • 相关阅读:
    C语言寒假大作战01
    C语言I作业12—学期总结
    C语言I博客作业11
    C语言I博客作业10
    C语言I博客作业09
    C语言I博客作业08
    C语言寒假大作战04
    C语言寒假大作战03
    C语言寒假大作战02
    C语言寒假大作战01
  • 原文地址:https://www.cnblogs.com/silyvin/p/9106908.html
Copyright © 2020-2023  润新知