【VC编程技巧】动态链接库☞1.2用模块导出接口

为什么要用模块(def文件)导出接口呢?


第一步,我先从函数调用方式说起,大家都知道函数调用有几种常见的方式,分别是__cdecl,__stdcall(pascall),__fastcall,和thiscall。下面我说一下这几种方式会在那些场合出现和它们的差异:

  1. __cdecl :(C和C++缺省调用方式)参数压栈顺序,从右到左;栈的清除,由调用者完成;
  2. __stdcall:(Win32 API的调用方式,并且WINAPI、CALLBACK和__stdcall都一样)参数压栈顺序,从右到左;栈的清除,由被调者完成;通过第一点和第二点我们知道每一个使用__cdecl约定的函数都要包含清理堆栈的代码,所以产生的exe模块要比__stdcall大。因为Win32 API采用这种调用方式,所以目前这种方式调用支持大多数语言。而__cdecl约定调用只适合C语言,另外如果接口函数有可变参数必须用__cdecl约定调用,__stdcall约定不行。
  3. __fastcall:(C++Builder默认的调用方式)它的主要特点就是快,因为它是通过寄存器来传送参数的。
  4. thiscall:(C++类成员函数调用方式)仅仅应用于 "C++" 成员函数。this 指针存放于 CX 寄存器,参数从右到左压。thiscall 不是关键词,因此不能被程序员指定。

综上 ,我们知道Dll要有良好的共性接口的调用约定应该是__stdcall,而如果只是C来用的话可以约定为__cdecl


第二步,当我们编写__stdcall形式的接口时,大家都知道C++编译器会对函数名做修改,使我们Dll的共性降低(只能在同一种编译器生成的模块之间调用)。

而通过关键字extern "C" __declspec(dllexport)可以保证__cdecl约定方式的函数名称不被改变,却不能保证__stdcall约定方式的函数。这样我们会因为__stdcall函数名称被修改,不能通过函数名来调用接口函数;


第三步,现在可以回答文章开头的问题了,因为向Dll中引入模块的正好解决了上面的问题(保证__stdcall约定方式的函数名称不变),同时它使接口形式更加清晰,为程序员带来了很大帮助。


第四步,我举个例子验证一下这个结论:关键字extern "C" __declspec(dllexport)可以保证__cdecl约定方式的函数名称不被改变,却不能保证__stdcall约定方式的函数。

  1. 创建Win32DLL工程,工程名:DllCall
  2. 添加求和函数add和求差函数subtract接口,如下:
    // DllCall.cpp : Defines the entry point for the DLL application.
    //
    
    #include "stdafx.h"
    
    BOOL APIENTRY DllMain( HANDLE hModule, 
                           DWORD  ul_reason_for_call, 
                           LPVOID lpReserved
    					 )
    {
        return TRUE;
    }
    
    
    extern "C" __declspec(dllexport) int __stdcall add (int a, int b)
    {
    	return a+b;
    }
    
    extern "C" __declspec(dllexport) int __cdecl subtract(int a, int b)
    {
    	return a-b;
    }

  3. 编译生成动态链接库DllCalldll,然后用VC6自带的depends工具查看DLL的借口,我们发现:add变成了_add@8,subtract还是subtract。


第五步,我们来证明向DLL中引入模块是可以解决这个问题的。

  1. 先创建Win32DLL工程,工程名:DllCall1
  2. 添加求和函数add和求差函数subtract接口,如下:
    // DllCall1.cpp : Defines the entry point for the DLL application.
    //
    
    #include "stdafx.h"
    
    BOOL APIENTRY DllMain( HANDLE hModule, 
                           DWORD  ul_reason_for_call, 
                           LPVOID lpReserved
    					 )
    {
        return TRUE;
    }
    
    int __stdcall add (int a, int b)
    {
    	return a+b;
    }
    
    int __cdecl subtract(int a ,int b)
    {
    	return a-b;
    }

  3. 添加模块文件DllCall1.def,各标签的内容都有注释:
    ;LIBRARY 指DLL的名称,必须和生成dll文件名称一直
    ;DESCRIPTION 指DLL的说明
    ;EXPORTS语法格式entryname[=internalname] [@ordinal[NONAME]] [DATA] [PRIVATE]
    ;EXPORTS 指要导出的接口
    
    LIBRARY DllCall1
    DESCRIPTION
    EXPORTS
    add
    subtract

  4. 编译生成动态链接库DllCall1.dll,然后用depends工具查看add是add,subtract还是subtract:

总的来说,编写动态库函数调用方式推荐__stdcall,接口导出时采用模块导出。


你可能感兴趣的:(VC编程技巧,【VC编程技巧】)