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

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

1、静态调用与动态调用
1.1 静态调用
使用这种方式调用DLL库的步骤(摘自网上url)为,youApp是你DLL的工程名,需要dll\lib\h头文件:
①把你的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为[email protected],其中符号"_"和"@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++动态链接库的使用充满了复杂,很多新接触者会无缘无故地陷入各种问题当中,故写本文给看到的人以减少他们的弯路。特别感谢阿里实习同事的帮助,他们在我确决问题中给了我不少的指点。

时间: 2024-08-19 08:52:05

针对动态加载方式的C/C++动态链接库编写的相关文章

APK动态加载框架(DL)解析

意义 这里说说这个开源项目的意义.首先要说的是动态加载技术(或者说插件化)在技术驱动型的公司中扮演这相当重要的角色,当项目越来越庞大的时候,需要通过插件化来减轻应用的内存和cpu占用,还可以实现热插拔,即在不发布新版本的情况下更新某些模块. 我 几个月前开始进行这项技术的研究,当时查询了很多资料,没有找到很好的开源.目前淘宝.微信等都有成熟的动态加载框架,包括apkplug,但是它们都是 不开源的.还有github上有一个开源项目AndroidDynamicLoader,其思想是通过Fragme

实现类似微博、QQ空间等的动态加载

微博.QQ空间等的动态加载方式属于滚屏加载技术,获取当前滚动条位置来触发onscroll()函数,向服务器发起请求,将请求得到的新的数据动态加载在页面上 本文利用该原理实现了动态加载,但不是检测当前滚动条位置来触发函数,而是由按钮事件触发,因此更简单一些. 走过的弯路 1) 将目前读取到的数据库中的位置存放在session中,当要加载更多的时候,去session中获得该值,动态加载后修改session中的值 错误原因:session是有缓存的,如果停留在当前页面,得到的值还是一开始的sessio

Qt动态连接库/静态连接库创建与使用,QLibrary动态加载库

版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:Qt动态连接库/静态连接库创建与使用,QLibrary动态加载库     本文地址:http://techieliang.com/2017/12/680/ 文章目录 1. 动态连接库创建与使用  1.1. 项目创建  1.2. 调用-使用.h文件 2. 静态库创建及使用  2.1. 创建  2.2. 使用 3. QLibrary动态加载动态库  3.1. 介绍  3.2. 范例  3.3.

Unity 利用UGUI打包图集,动态加载sprite资源

今天做了一个UI界面,这个界面是好友界面,该界面上有若干个好友item. 需要对每个tem的头像对象(image)动态显示对应的头像.尝试利用UGUI的图集来加载,具体实现如下: 1.首先,需要知道SpriteAtlas的功能,可以保存一些关于要打包进去的sprite的设置.(详细参数设置的意义有待进一步研究),其中的Objects For Packing可以关联到需要打包进这个已创建图集的Sprite,或者文件夹,或者texture.目前这里的做法是关联到了文件夹. 2.此时,unity已经给

C++之DLL的动态加载与静态加载初尝试

[环境:VS2019] [编写一个DLL并导出函数] 1.新建动态链接库:V_BJZ [framework.h] #pragma once #define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容 // Windows 头文件 #include <windows.h> extern "C" _declspec(dllexport) int ReturnSum(int a, int b); //导出声明,_declspec(

动态加载dex的两种方式

DexClassLoader 加载的类是没有组件生命周期的,也就是说即使DexClassLoader通过对dex的动态加载完成了对组件的加载,当系统启动该组件时,还会出现加载类失败的异常.有两种方式可以解决上面出现的问题: 方法一:http://blog.csdn.net/androidsecurity/article/details/8809542,更改系统的classloader使其为自定义的加载器. 特点:两个dex具有明显的分割线,第一个dex只起启动作用,后面不会出现第一个dex的类加

在页面中使用拼接字符串的方式显示动态加载的数据

在做页面的时候为了使页面美化,我们经常会用拼接字符串的方式,动态加载后台的数据,这里我们使用的前台框架是bootstrap,但是很多效果还是要用jquery来实现 (1)方法传参与字符串的拼接  (拼接用的replace) 先声明一个展示数据的页面模型(使用过的模型1) var userModel = "<div class=\"itemdiv commentdiv\" style=\"margin-left: 7%;\">"+ &q

jquery动态加载问题

对于append的元素,原有的方法不生效 解决:用on方法 找到的:http://www.zhidao91.com/jquery-html-live-on/ 解决使用jQuery采用append添加的元素事件无效的方法 2014年09月22日 | jQuery | 浏览: 1,118 当我们使用jQuery动态加载html元素,但是元素上面又绑定了Click等事件,针对新添加的元素这些事件是无效的,那么应该怎样解决呢? live方法 live( type, fn )jQuery 1.3中新增的方

Linux驱动的两种加载方式过程分析

一.概念简述 在Linux下可以通过两种方式加载驱动程序:静态加载和动态加载. 静态加载就是把驱动程序直接编译进内核,系统启动后可以直接调用.静态加载的缺点是调试起来比较麻烦,每次修改一个地方都要重新编译和下载内核,效率较低.若采用静态加载的驱动较多,会导致内核容量很大,浪费存储空间. 动态加载利用了Linux的module特性,可以在系统启动后用insmod命令添加模块(.ko),在不需要的时候用rmmod命令卸载模块,采用这种动态加载的方式便于驱动程序的调试,同时可以针对产品的功能需求,进行