Windows操作系统中使用C++生成动态链接库

重用是软件工程的重要方法。软件系统中可重用的部分包括:数据,文档,算法,代码,设计模式,软件架构以及环境等多方面内容。根据不同的抽象层次,软件代码重用大致可分为三类: 软件实现层重用,即软件源代码的重用;软件设计层重用,即重用软件系统的设计信息;软件架构层重用,即重用软件系统的架构。共享库是软件源代码重用的方法,动态链接库是保存软件外部共享库的文件格式。

通过外部共享库实现软件重用的流程有三步:
1) 生成外部共享库文件,
2) 编译整合外部共享库与宿主程序,
3) 启动宿主程序,宿主程序进程加载外部共享库。

外部共享库格式

动态链接指操作系统将外部共享库从持久性存储区(persistent storage)复制到内存,通过填写跳转表和重新定位指针等操作,把外部共享库注入当前进程[2]。不同操作系统中实现动态链接方法以及使用的文件格式各不相同。 动态链接库(Dynamic-link library, DLL) 是Windows操作系统在动态链接时使用的外部共享库的文件格式。

根据编译和使用方式的不同,Windows操作系统的外部共享库分为两种: 动态链接库和静态链接库,如下表所示。在使用外部共享库时,首先要将外部共享库代码编译成动态链接库文件或静态链接库文件。然后将链接库文件和宿主程序代码编译成宿主可执行文件。 Windows操作系统中外部共享库主要使用两类文件,文件名后缀分别是: .lib 和.dll。

静态链接库 动态链接库
内存使用 每一个宿主软件运行时都有一份静态链接库 多个宿主软件运行时,内存只需要复制一个动态链接库
软件依赖 宿主程序运行时不依赖静态链接库文件 宿主程序运行时依赖动态链接库文件
文件 .lib文件 .lib文件, .dll文件
软件更新 整个软件都需要更新 宿主程序和库文件相互独立更新
版本冲突 非常严重

VS(Visual Studio)构造静态链接库时,导出函数的声明和实现都放在lib文件中。 静态链接库中函数代码插入宿主程序中。宿主程序运行时不依赖lib文件。

VS构造动态链接库时,会同时生成lib文件和dll文件。动态链接库中lib文件的功能相当于头文件,只包含导出函数的声明,函数的实现放在dll文件中。主程序运行时依赖dll文件。

一个动态链接库实例可以被多个程序共同使用。这能实现代码共享并且可以隐藏实现软件代码细节,便于软件升级。 但是,这也带来一定的副作用。多个程序依赖于同一个动态链接库,当这些程序升级步调不一致,造成软件版本冲突,会引发一系列灾难性后果,也就是所谓的"DLL Hell".

动态链接库的调用方式

宿主程序加载动态链接库的方法有两种: 显式加载 和 隐式加载

显式加载

宿主程序代码中通过LoadLibrary()函数 和 FreeLibrary()函数指定动态链接库的加载和卸载的时机。
宿主程序启动后,宿主程序进程在遇到LoadLibrary()函数时才将动态链接库加载到进程的内存空间。

通过显式加载方法使用动态链接库,在编译整合动态链接库与宿主程序时只有需要使用dll文件。

隐式加载

隐式加载的宿主程序代码中没有直接使用LoadLibrary()函数 和 FreeLibrary()函数的代码,因此称为隐式加载。

隐式加载的宿主程序在启动时搜索到dll文件以后,将动态链接库加载到宿主程序进程的内存空间。 隐式加载实际上也是通过LoadLibrary()函数实现加载工作。

通过隐式加载方法使用动态链接库,在编译整合动态链接库与宿主程序时需要使用lib文件和dll文件。

函数调用协定

由于操作系统,编译器,硬件等环境因素的不同,程序使用的函数调用方法在寄存器使用,内存堆栈管理,函数参数传递,函数名修饰符等方面会有较大差异。每一种函数调用方式称为函数调用协定(Function Calling Convention)。

函数调用协定主要规定调用函数时下列几方面事项:

  • 函数参数在内存中的顺序
  • 传递参数的方式
  • 函数调用方要保留CPU寄存器
  • 内存堆栈的使用方式

下面是运行于x86架构微处理器的程序常用的函数调用协定 [3] [4]

协议名称 函数参数在堆栈中的顺序 清理堆栈中的函数参数占用的内存空间 备注
cdecl RTL(right-to-left) 函数调用方
sdtcall RTL 函数被调用方
Pascal LTR(left-to-right) 函数被调用方
fastcall LTR 函数被调用方
thiscall RTL 函数被调用方
vectorcall RTL

在C语言源代码中,如果改变函数调用协定,需将协定的标识符放在函数名和函数返回值类型声明之间. 编译器根据调用协定生成相应的机器语言代码。汇编代码的助记符与机器语言指令一一对应。查看函数的汇编代码,能深入了解不同函数调用协定的实现细节。

下面通过几个样例展示不同函数调用协定之间的差异。 样例中使用的函数名为test, 它接受两个整数型参数,并返回这两个整数的和。在主函数main中,将整数1和3作为参数,调用函数test. 具体代码如下:

int test(int a, int b)
{
	return (a + b);
}

int main()  
{  
    test(1, 3);
    return 0;  
}

将上述源代码保存到名为foo.cc文件中. 生成汇编代码, 使用编译参数 /FA, 然后再控制台环境使用cl.exe编译:

cl /FA foo.cc

编译完成后,会生成可执行文件foo.exe. 编译过程中生成的汇编代码会保存在foo.asm中。

cdecl Calling Convention

本样例中,test函数使用cdecl调用协定,具体C语言源代码如下:

int cdecl test(int a, int b)
{
	return (a + b);
}

int main()  
{  
    test(1, 3);
    return 0;  
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值