DLL的导出函数

      我们在编写动态库时,经常会用到类似extern “C”, __declspec(dllexport)的修饰符,以及有时候还会有def文件,__declspec(dllexport)和def文件是生成导出函数的两种方式,选用不同的导出方式对dll中的导出函数有什么影响,以及extern “C”的使用与否有什么区别,本文对上述限定符分别进行验证。
      使用vs2017生成一个默认的dll工程,新建一个.h文件,加入以下代码:

bool __stdcall Test(int a, int b)
	{
		std::cout << __FUNCTION__ << std::endl;
		return true;
	}

      编译该dll工程,生成了Dll1.dll,使用Dependency Walker打开会发现该dll没有导出函数:
在这里插入图片描述

一,__declspec(dllexport)

      给Test函数加上__declspec(dllexport)修饰符,那么它就是一个导出函数了,编译dll工程,使用Dependency Walker打开Dll1.dll,此时有一个导出函数,但是函数名却不是代码中的Test,而是一个加了很多字符的新函数:?Test@@YG_NHH@Z:
在这里插入图片描述
      这是由于我们的导出函数是__stdcall的调用约定,?表示一个函数的开始,函数名后面以"@@YG"标识参数表的开始,后跟参数表,比如H表示一个int类型,具体的细节可以去查阅资料,本文重点不在这。
      我们再给导出函数加上一个extern “C”的限定,打开编译后的Dll1.dll:
在这里插入图片描述
      此时的导出函数为_Test@8,如果大家熟悉c语言的dll导出函数命名规则的话,就知道这是c语言的命名规范,@8表示参数占8个字节。也就是说extern "C"会以c命名规范来修改我们的导出函数名(准确来说,extern “C” 是告诉编译器,让它按C的方式编译),它只能用于导出全局函数这种情况 而不能导出一个类的成员函数。

二,.DEF模块定义文件

      去掉Test函数前的__declspec(dllexport)修饰符,新建一个Dll1.def文件
在这里插入图片描述
      在def文件中加入以下内容:

LIBRARY
EXPORTS
    		; Explicit exports can go here
		Test

      其中Test就是我们的导出函数,编译工程,打开生成的Dll1.dll:
在这里插入图片描述
      终于看到了熟悉的Test函数了,先不要激动,我们再来给Test加上extern “C”试试,打开编译后的Dll1.dll,可以发现使用def文件声明导出函数生成的dll中导出函数符号与代码中函数名完全一致,且加不加extern “C”修饰都是一样的。

三,__declspec(dllexport)和def文件的区别

      动态库的加载使用有两种方式:显示调用和隐式调用。显示调用指的是使用LoadLibrary将dll加载到我们进程空间内,然后再调用GetProcAddress获取指定名称的导出函数的地址,转换为我们声明的对应参数和返回值的指针,调用这个指针,则完成dll中导出函数的使用;隐式调用指的是依赖于.h文件,和.lib文件,此处的.lib中保存的是dll的导出函数符号表,并不保存具体的代码实现,编译时编译器会根据lib自动找到对应导出函数的声明,完成编译,然后在运行时,再动态加载lib对应的dll文件,很明显,lib中保存了对应dll的名称,用notepad++打开Dll1.lib可以验证:
在这里插入图片描述

1,显式调用

1,def方式
      新建一个win32控制台工程,测试代码如下:

#include <iostream>
#include <windows.h>

typedef bool(__stdcall *Fun)(int a, int b);

int main()
{
	HMODULE hLib = LoadLibraryA("Dll1.dll");
	if (nullptr == hLib)
	{
		std::cout << "LoadLibraryA fail, error:" << GetLastError() << std::endl;
		return 0;
	}

	Fun fun = (Fun)GetProcAddress(hLib, "Test");
	if (nullptr == fun)
	{
		std::cout << "GetProcAddress fail, error:" << GetLastError() << std::endl;
		return 0;
	}

	fun(1, 2);

    std::cout << "Hello World!\n"; 
	getchar();
}

      输出结果如下:
在这里插入图片描述
      很明显,使用def方式的导出函数可以显示加载并调用。

2,__declspec(dllexport)方式
      此处我们先不加extern “C”,使用同样的测试代码,输出如下:
在这里插入图片描述
      报错127,找不到指定的函数。然后我们再加上extern “C”,依旧还是报错127,大家可以自己验证。

      那么通过__declspec(dllexport)修饰过的导出函数是不能通过LoadLibrary来显示加载调用嘛,我们从两个方面再来验证这个问题:

第一种尝试:
      修改GetProcAddress的导出函数名,根据上面我们使用Dependency Walker查看的__declspec(dllexport)的非extern “C”模式的导出函数名为:?Test@@YG_NHH@Z,我们将代码改为:

Fun fun = (Fun)GetProcAddress(hLib, "?Test@@YG_NHH@Z");

显示加载调用成功。
在这里插入图片描述在这里插入图片描述
第二种尝试:
在dll工程中加入代码:

#pragma comment(linker, "/EXPORT:Test=?Test@@YG_NHH@Z")

这时我们再查看dll的导出函数,就会发现函数名字表中已经有了我们想要的Test。
在这里插入图片描述      但我们发现原来的那个 ?Test@@YG_NHH@Z 函数还在,这时就可以把 __declspec() 修饰去掉,只需要 pragma 指令即可。这样导出函数表中就只有一个Test函数了。
由此可见,在不修改原始dll代码的基础上,通过__declspec(dllexport)方式实现导出函数的dll库,也是可以通过LoadLibrary来显示加载调用的。

2,隐式调用

      通过.h和.lib加载并使用dll中的导出函数,__declspec(dllexport)和def没有区别,此处就不再演示,大家可以自己验证。(前提是工程中配置了生成导出符号文件.lib)

四,结论

      根据上述测试代码验证,我们可以得到以下结论:
1,dll要实现导出函数有两种方式:使用__declspec(dllexport)修饰导出函数,或者新增def文件加入导出函数名。

2,使用def文件生成的dll导出函数,加不加extern “C”都没区别,最终符号表中的符号就是代码中的函数名,可以直接用LoadLibrary来进行显示调用。

3,使用__declspec(dllexport)修饰的导出函数,不能直接使用LoadLibrary来显示调用,必须要结合lib来隐式调用,这是因为编译器帮我们进行了导出函数名转换这一步骤,我们也可以使用以下命令来进行手动转换(在dll中使用),此时可以不再需要__declspec(dllexport)。ps:可是去掉了又如何知道实际的导出函数名呢,这个用法比较尴尬 —_—

#pragma comment(linker, "/EXPORT:Test =?Test@@YG_NHH@Z ")

4,extern “C” 声明只对根据c++规则进行修改的导出函数才有用,并将该导出函数按照c的规则进行重命名,比如?Test@@YG_NHH@Z =》_Test@8。没有修改修改命名的导出函数,加不加extern “C” 没区别,比如def模块定义文件实现的导出函数。

5,如果要导出C++文件中的函数,并且不让编译器改动函数名,用def文件导出函数。

备注:如果大家感兴趣的话,可以自行验证一下c语言的dll导出函数。

  • 18
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
VS(Visual Studio)是一种集成开发环境,其中包含了许多工具和功能,用于帮助开发人员进行软件开发和调试。而查看DLL(动态链接库)的导出函数是一种技术,用于查看和分析DLL文件中所包含的函数。 在VS中,我们可以使用多种方法查看DLL导出函数。以下为其中几种常用的方法: 1. 使用"查看导出函数"工具:在VS中,我们可以使用内置的"查看导出函数"工具来查看DLL导出函数列表。通过打开该工具,然后选择要查看的DLL文件,即可显示出该DLL中所有的导出函数及其所在的模块和内存地址等信息。 2. 使用导入库文件:在创建或者使用DLL的项目中,可以通过使用相应的导入库文件来查看并调用DLL中的导出函数。通过在项目设置中引用相应的导入库文件,我们可以在代码中直接引用DLL中的函数,并通过VS的代码编辑器来查看函数的定义和实现等信息。 3. 使用反汇编工具:除了使用VS自带的工具外,我们还可以使用第三方的反汇编工具来查看DLL导出函数。这些工具可以将DLL文件进行反汇编,以获取其中的代码和函数等信息。通过分析反汇编结果,我们可以得知DLL导出函数的名称、参数、返回值和内部实现等信息。 总而言之,VS提供了多种方法来查看DLL导出函数,如使用内置工具、导入库文件和第三方反汇编工具等。这些方法都能帮助我们在开发和调试过程中了解DLL中的函数,从而更好地使用和调用它们。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Simple Simple

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值