C/C++ 编译调用 DLL

本文详细介绍了C/C++中如何编译和调用DLL,包括导出函数的__declspec关键字使用、函数导入导出的格式、动态加载与静态编译的区别,以及如何使用LoadLibrary和GetProcAddress等API。还探讨了extern "C"的作用,以及不同调用约定(__cdecl,__stdcall)对函数名称的影响。示例代码展示了DLL的创建和调用过程。
摘要由CSDN通过智能技术生成

综述

C 和 C++ 只有共同a的一种方式来导出函数,用的是 __declspec 关键字。关键字带参,其参有两种值可选:dllexport 和 dllimport。顾名思义,前者为函数导出,后者为函数导入。不过需要说明的是:C 和 C++ 导出表默认采用了不同的解析形式,哪怕同为__cdecl 约定格式,或是 __stdcall 约定,二者的格式也都不一样。因而大部分 DLL 的导出表及对应的导入表均需统一格式,这在 C 或 C++ 中有一个通用的扩展修饰:

extern "C" {
    //code blocks...
}

这种修饰,指定在花括号内,只使用 C 标准的函数名解析形式,其实主要是去除C++ 扩展,但在这种情况则是仅影响了修饰名(Mangled Name)。

值得一提的是,在 GCC 和 VC 所编译好的 DLL 中也有所不同,除用 C 格式修饰的以外,其他部分的修饰名均不兼容。如用 IDA 的朋友会有些了解,所谓 Demangled Name就是还原其被修饰的函数名。因此,使用 extern "C" 显式声明使用 C 语言的标准,能取得更好的兼容性,不失为一个良好的习惯。


导入导出

函数及其他变量的导出方式有一个较为常见:使用 __declspec。其格式如下:

__declspec(specifier) type var_name(param_list, ...);
其中 specifier 在这里共有两个值,dllexport 和 dllimport,前者导出,后者导入。先说函数导出,所以为 dllexport。type 修饰 var_name 的类型,若 var_name 为变量名,则 type 指定变量类型,若 var_name 为函数,则 type 为其返回值类型。var_name 为欲导出的变量名或函数名,如为变量,则其后的括号及参数列表 param_list可以省略,否则仅能省略参数。其余格式遵守声明规定。同样的,可直接函数定义,也可先声明后定义。

不过仍有一点需要注意,经导出的函数不支持函数重载,因为不支持同名变量(即使只是形式上的“同名”)。

仍有另一种导出方法,那便是在编译过程中生成的汇编代码中添加导出信息,或是在GCC 中用选项指定导出表,不过并不常用,将会在日后专写一篇相关的文章,在此不再累述。


函数的导入和导出大同小异,格式如下:

__declspec(dllimport) type var_name(param_list, ...);
可见发生改变的部分就仅仅是 dllexport 改为了 dllimport,不过导入和导出毕竟还是有些区别。使用 __declspec 导入属于静态编译的一种,不过其不太灵活,实际在程序开发中往往会有“插件”一类的概念,此时便不再适用静态编译的方法,而是使用动态加载的方式。

动态加载使用一个 LoadLibrary 的 API,讲一个 DLL(或是具有导出表的 EXE 等)加载到内存中,并执行其预定义的 DllMain 函数,然后回交控制权给调用程序,此时由程序调用 GetProcAddress 这个 API 获取指定的欲导入函数的入口地址(Entry Point)方可调用该函数。说来复杂,其实也就三行代码、两个调用,其余过程均交予系统处理。不过在程序结束前或是不需要在使用该导入函数时,应用 FreeLibrary 释放该 DLL。


静态编译并非严格意义上的“静态”,目标函数代码并没有加入到源代码中,而是标记在导入表上,由程序启动时,令系统为程序搜索指定 DLL 并加载其数据,如果没有找到对应文件或是对应入口点,则会报错,程序无法运行。对此,动态加载有一个优势,那便是不必在程序执行前搜索被导入文件,因而其可扩展性更高些,并且可以自己实现对应的异常处理函数。

此外,静态的函数导入需要在编译时提供对应 DLL 的 .lib 文件,无法在缺少 .lib文件的情况下导入 DLL。不过,这个问题已可以被 GCC 解决,指定的源中,加入 DLL 文件或是 .lib 文件,而无需在代码中添加相关内容即可完成对目标程序的编译连接及对DLL 的调用。


代码实例

由于作者不喜用 VS 等图形化界面的编译器,故在作者一般以 VC 的 CL 以及 GCC 作为编译工具,并配合 eXeScope 与 Dependency Walker 查看程序的导入导出表。

先看下如何编写 DLL,一般来说,高级的编译器可以帮助编写者补全一部分代码,因而我们在刚开始可以只定义自己的函数。比如我们写一个函数 foo,使其返回值类型为 void。

void foo(void) { pri
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值