• 针对动态加载方式的C/C++动态链接库编写


    0、前言
    笔者为客户提供C/C++动态链接库调用WEBSOCKET功能时,最初错误地认定客户采用静态加载的方式使用DLL库,导致使用其它编程语言的客户无法使用。考虑到为客户服务常常要跨语言和跨IDE,最好的DLL库的使用方式是动态调用,并且要减少DLL库的依赖库,避免对Windows下VS自带库的调用。本文针对动态调用提出一起DLL编写注意事项。

    1、静态调用与动态调用
    1.1 静态调用
    使用这种方式调用DLL库的步骤(摘自网上url)为,youApp是你DLL的工程名,需要dlllibh头文件:
    ①把你的youApp.DLL拷到你目标工程(需调用youApp.DLL的工程)的Debug目录下;
    ②把你的youApp.lib拷到你目标工程(需调用youApp.DLL的工程)目录下;
    ③把你的youApp.h(包含输出函数的定义)拷到你目标工程(需调用youApp.DLL的工程)目录下;
    ④打开你的目标工程选中工程,选择Visual C++的Project主菜单的Settings菜单;
    ⑤执行第4步后,VC将会弹出一个对话框,在对话框的多页显示控件中选择Link页。然后在Object/library modules输入框中输入:youApp.lib
    ⑥选择你的目标工程Head Files加入:youApp.h文件;
    ⑦最后在你目标工程(*.cpp,需要调用DLL中的函数)中包含你的:#include "youApp.h"
    此种调用方式的优点是:DLL的函数名是通过.h文件和lib文件寻找到,实际的接口名可能和函数名不一致,但是不会导致无找不到入口的情况。它的缺点就是:非C/C++语言无法加载.h头文件,跨语言时会遇到各种问题。
    1.2 动态调用
    动态调用的方法,先LoadLibrary,再GetProcAddress(即找到DLL中函数的地址),不用后FreeLibrary。具体示例代码(摘自网上)如下:

    {
        HINSTANCE hDllInst = LoadLibrary("youApp.DLL");
        if(hDllInst){
            typedef DWORD (WINAPI *MYFUNC)(DWORD,DWORD);
            MYFUNC youFuntionNameAlias = NULL; // youFuntionNameAlias 函数别名
            youFuntionNameAlias = (MYFUNC)GetProcAddress(hDllInst,"youFuntionName");
            // youFuntionName 在DLL中声明的函数名
            if(youFuntionNameAlias){
                youFuntionNameAlias(param1,param2);
            }
        FreeLibrary(hDllInst);
        }
    }

    动态调用优点在于:不需要依赖.h和lib文件,更加便捷。缺点在于:生成DLL库接口名需要和函数名一致,否则无法找到函数的入口点

    2、DLL库接口名和函数名的关系
    2.1 接口名和函数名分析
    如果接口名和函数名不一致找不到dll中的函数,出现“无法定位程序输入点”的问题,如下图所示。

    对于DLL库,查看它的接口名称可以使用Depends工具,如需要可以联系我。使用depends可以看到如下示例,在这个例子中Function的名称即接口名称,它和函数名称是一致,这样可以正常调用DLL。

    而函数名称与接口名不一致情况笔记忘了保存,举例说明,sendMessage的Function name为_sendMessage@12,其中符号"_"和"@12"导致接口名和函数名不一致。这种情况使用在GetProcAddress函数中将函数名作为参数是无法找到函数入口点的。所以使用动态调用时,最好确保函数名和接口名的一致。
    2.2 如何确保函数名和接口名一致
    C++编译器在生成DLL时,会对导出的函数进行名字改编,并且不同的编译器使用的改编规则不一样,因此改编后的名字也是不同的(一般涉及到C++ 中的重载等)。一般而言,生成dll有两种方法,一是使用def文件,二是在函数定义前加_declspec(dllexport)。如果要导出C++文件中的函数,并且不让编译器改动函数名,用def文件导出函数并且在函数名前加extern "C"。
    def的定义示例如下:

    LIBRARY PrinterManager
    EXPORTS
    	initPrinterManager
    	setRecvDataCallback
    	sendMessage
    	closePrinterManager

    关于DLL导出名如下图(来源于网上文章url),如果采用extern "C"和def则函数名和接口名一致,C++采用_declspec(dllexport)即函数名和接口名一致。其它情况函数名会被编译器改编。


    3、参数入栈顺序(__stdcall和__cdecl),参考网上文章url
    接口函数最好不用使用std中的容器,如string和vector等。这是因为计算机给这个函数传递参数,传递参数的工作必须由函数调用者和函数本身来协调,即函数传递参数的方式需要一致才能正确传递参数。计算机使用栈来支持参数传递,函数调用时,调用者依次把参数压栈,然后调用函数,函数被调用以后,在堆栈中取得数据,并进行计算。函数计算结束以后,或者调用者、或者函数本身修改堆栈,使堆栈恢复原装。
    问题出现了,当参数个数多于一个时(数组为多个参数),按照什么顺序把参数压入堆栈,函数调用后,由谁来把堆栈恢复原装 在高级语言中,通过函数调用约定来说明这两个问题。常见的调用约定有:stdcall、cdecl、fastcall等,本文仅介绍stdcall和cdecl。
    3.1 __stdcall
    声明方法:int __stdcall function(int a,int b)
    __stdcall的调用约定意味着:1)参数从右向左压入堆栈,2)函数自身修改堆栈 3)函数名自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸。
    在DLL的生成代码中和使用代码中都需要声明__stdcall。跨语言推荐使用该方法
    3.2 __cdecl
    cdecl调用约定又称为C调用约定,是C语言缺省的调用约定,它的定义语法是:
        int function (int a ,int b) //不加修饰就是C调用约定
        int __cdecl function(int a,int b)//明确指出C调用约定
    参数采用从右到左的压栈方式,传送参数的内存栈由调用者维护。_cedcl约定的函数只能被C/C++调用,故跨语言不应该使用该方法。

    4、减少DLL库的依赖库,避免对Windows下VS自带库的调用
    如果对外提供的DLL库使用VS自带库,那么其它语言很有可能就因为没有VS自带库而无法运行。根据笔者的经验,以下两个步骤一定要使用来将减少DLL库的依赖
    4.1 步骤一:发布注意使用release方式而不能是debug
    选择方法如下图所示:

    4.2 步骤二:将项目中的MD改为MT
    /MT是 "multithread, static version ” 意思是多线程静态的版本,定义了它后,编译器把LIBCMT.lib 安置到OBJ文件中,让链接器使用LIBCMT.lib 处理外部符号。
    /MD是 "multithread- and DLL-specific version” ,意思是多线程DLL版本,定义了它后,编译器把MSVCRT.lib 安置到OBJ文件中,它连接到DLL的方式是静态链接,实际上工作的库是MSVCR80.DLL。
    故采用MD的方式会使用额外的库,如vcruntime140.dll或msvcp140.dll,而这两库在ISV处很可能是没有的。
    修改方法:
    ①打开项目的“属性页”对话框;
    ②展开“C/C++”文件夹;
    ③选择“代码生成”属性页;
    ④修改“运行库”属性。
    如下图所示:


    5、总结
    C/C++动态链接库的使用充满了复杂,很多新接触者会无缘无故地陷入各种问题当中,故写本文给看到的人以减少他们的弯路。特别感谢阿里实习同事的帮助,他们在我确决问题中给了我不少的指点。

  • 相关阅读:
    js的event对象 详解
    RestSharp使用详解(1)调用阿里巴巴开放存储服务
    RestSharp使用详解(2)RestSharp的BUG和不足
    WF实例学习笔记:(2)通过Workflow 调用 WCF Data Services 获取数据
    译文:SQL Azure客户端瞬态错误处理最佳实践
    Windbg 基本命令
    RestSharp使用详解(3)OSS文件上传的问题
    Transient Fault Handling and Retry Logic: 瞬间错误处理——重试
    推荐一本免费的Node.js电子书(台湾)
    CSS导航菜单应用滑动门技术的玻璃效果菜单
  • 原文地址:https://www.cnblogs.com/ycloneal/p/5722928.html
Copyright © 2020-2023  润新知