二、 VS2015显示加载Dll及常用的创建方式

创建DLL动态库

1、新建Dll项目

文件->新建->项目->Visual C++ win32控制台程序(填写项目名MyDll)->下一步->(应用程序类型)勾选:DLL,(附加选项)勾选:空项目->完成

2、添加新建项MyDll.cpp

因显示加载dll时仅需要用到项目生成的dll文件,所以这里可以不使用.h文件。

#include <iostream>

int __stdcall Add(int a, int b)
{
    return a + b;
}

void __stdcall myPrint(const char* s)
{
    std::cout << s << std::endl;
}

__stdcall是Microsoft对编译器的一个扩展,是一种用于调用Win32 API函数的调用约定。使用__stdcall约定时函数栈的清理由被调用方执行,因此可以解决不同的编译器生成函数栈方式不同的问题,也就是使用该约定生成的dll可以跨编译器使用。另外如果导出函数需要给win32汇编使用或者作为系统回调函数使用,使用__stdcall声明方式是非常重要的。

3、添加.def文件(模块定义文件)

这里写图片描述
在新添加的.def文件中写入以下内容:

LIBRARY MyDll
EXPORTS
    Add
    myPrint

通常情况下这里只需要用到两种模块定义语句,LIBRARY和EXPORTS。这两条语句的使用规则如下:

LIBRARY [library][BASE=address]

library 参数指定 DLL 的名称。 也可以使用 /OUT 链接器选项指定 DLL 输出名。BASE=address 参数设置操作系统用来加载 DLL 的基址。 该参数可重写默认的 DLL 位置。

EXPORTS  
   definition

第一个 definition 可以和 EXPORTS 关键字在同一行或在下一行上。 .DEF 文件可以包含一个或多个 EXPORTS 语句。
导出 definition 的语法为:

entryname[=internalname] [@ordinal [NONAME]] [[PRIVATE] | [DATA]]
  • entryname 是要导出的函数名或变量名。 这是必选项。 如果导出的名称与 DLL 中的名称不同,则使用 internalname 指定 DLL 中导出的名称。
  • 可选@ordinal 用于指定序号。除非 DLL 的客户端需要按序号导出函数以支持旧版代码,否则不建议使用该功能。
  • 可选 NONAME 关键字,用于指定只按序号导出,从而减小生成的 DLL 中导出表的大小。
  • 可选 PRIVATE 关键字用于禁止将 entryname 包含在由 LINK 生成的导入库中。
  • 可选 DATA 关键字指定导出的是数据,而不是代码。

4、编译生成dll文件

生成结果如下所示
这里写图片描述

使用Dependency Walker工具可以打开dll文件查看导出的函数格式
这里写图片描述

显示加载方式使用DLL动态库

通过显式加载的方式访问DLL,可以在需要时加载所需的DLL,也就是说,在需要时DLL才会被加载到内存中,并被映射到调用进程的地址空间中。而在不需要时将其释放掉,从而可以减少资源占用。相比于使用隐式加载的程序启动时加载所有动态库,使用显示加载的程序启动更快。

显示加载方式的局限是只能导出全局函数,不能导出类成员函数。但用于抽象接口类时,这条缺点不会有影响,具体用法将在下一篇中讲解。

1、新建测试项目

文件->新建->项目->Visual C++ win32控制台程序(填写项目名testDll)->下一步-> (附加选项)勾选:空项目->完成

2、添加新建项main.cpp文件

#include <iostream>
#include <Windows.h>

using namespace std;

int main(void)
{
    HMODULE hMod = LoadLibrary("MyDll.dll");
    if (hMod == nullptr) {
        cout << "load dll error!" << endl;
        return -1;
    }
    typedef int (__stdcall* TypeFunAdd)(int, int);
    typedef int(__stdcall* TypeFunMyPrint)(const char*);
    TypeFunAdd AddT = (TypeFunAdd)GetProcAddress(hMod, "Add");
    if (AddT == nullptr) {
        cout << "load func error!" << endl;
        return -1;
    }
    cout << "Test Add Func: 2 + 5 = " << AddT(2, 5) << endl;

    TypeFunMyPrint myPrintT = (TypeFunMyPrint)GetProcAddress(hMod, "myPrint");
    if (myPrintT == nullptr) {
        cout << "load func error!" << endl;
        return -1;
    }
    myPrintT("Test myPrint Func.");

    FreeLibrary(hMod);
    return 0;
}

相比于隐式加载,这里不需要加入相关的lib依赖项。且隐式加载时,dll文件需放置在可执行文件的同一目录下,此处显示加载时dll文件应放置在项目根目录下。

上面的 LoadLibrary() 函数的作用是将指定的dll模块映射到调用进程的地址空间。GetProcAddress()函数的作用是获取dll中导出函数的地址,需要使用导出的函数名作参数。同时使用对应的函数指针变量接收返回的函数地址,另外此处函数指针的声明方式应该与dll中函数的声明方式一致。

3、编译运行

这里写图片描述

注:

编译时若出现错误,无法将参数 1 从“const char [10]”转换为“LPCWSTR”。只需要设置属性 使用多字节字符集 即可。
这里写图片描述

猜你喜欢

转载自blog.csdn.net/blade1080/article/details/81538384
今日推荐