android可以调用dll吗_一文梳理vs2017中dll的使用

616317bc82ed9ff09203489e7cd3fa01.png

动态链接库(dll)

Windows下有静态链接(lib)库和动态链接库(dll)两种共享代码的方式。

本文将介绍dll的应用场景,以及在vs2017平台下的生成和使用。


[What] dll是什么

动态链接库(Dynamic Link Library)又称为“应用程序扩展”,在windows系统中,大多数应用程序并非仅有一个可执行文件exe,同时也包含一些相对独立(模块化)的dll文件。dll中存放函数代码实现,exe中存放dll中相应函数代码的地址,而且dll中的代码可以被多个exe调用而在内存中仅保留一份拷贝,从而节省了内存空间。

[How] 如何生成dll

1. 新建一个dll项目

选择“具有导出项的(DLL)动态链接库”,vs会帮我们自动创建与项目同名的.cpp文件和.h文件,并在.h文件中定义好相关导出符号;如果选择“动态链接库(DLL)”则不会创建上述文件。

dc4c15d99bc13e5e5001f803b84ba27c.png
dll新建项目

创建完成后,可以看到vs已经帮我们完成导出符号和预处理器的定义:

ef4681ed2bdeb2040377039bb18d7724.png
dll导出符号定义

b3de033f142953472a3e49688ad0c8df.png
dll预处理器定义

2. dll两种导出方式

vs官方文档中提供了两种方式可以导出dll中的函数:

50a0d3653224801289abd9f4fd9b6ddf.png
dll两种导出方式
  • 模块定义文件(.def):通用性(指给其他语言eg. Java、C#调用)好,但操作相对复杂;
  • 关键字__declspec(dllexport):操作简单,但通用性较差。可见,vs创建dll项目时默认使用了该方式。

下面依次介绍这两种导出方式。

3. 模块定义文件(.def)

  • 新建.def文件

95f394fc7193a3e91a1f2ad3ecadda84.png
dll新建def文件

vs会自动添加.def文件为链接器输入:

0507fcda48880c1e80ec9e7d6be18a32.png
dll添加def为链接器输入
  • 实现一个dll函数

0e961ef5791ecf7fc3fe124738780550.png
dll函数实现_def文件
  • 模块定义语句规则
    模块定义语句有许多规则,这里只用到EXPORTS关键字:

c8bb50fe37168f02b2c52d08caa1993b.png
dll模块定义语句规则_EXPORTS
  • 编写.def文件如下:

53e685ab187ce3c0440e5863050f17a0.png
dll编写def文件

4. 关键字__declspec(dllexport)

分别以C++和C的方式实现两个导出函数,后面会看到这两个函数的不同之处:

882f0d3aae5c9b71110b229707db5963.png
dll函数实现_dllexport

5. 生成结果

27f6e11302d663a422c62ea3aa6add0f.png
dll生成结果

这里除了MYDLL.dll还生成了MYDLL.lib文件,它是dll的导入库,用于隐式链接dll

Visual Studio 命令提示符处启动DUMPBIN工具,执行dumpbin -exports pathtodll命令分析生成的dll,查看编译器产生的函数修饰名:

f03790df3ff9c239238e5c0308ac5b6a.png
dll_DUMPBIN分析

可以得出以下两个结论:

  • VC++编译器会针对C++函数使用名称修饰,而不会修饰以C方式声明的函数(其实也修饰了,在函数名前面加了一个下划线前缀,这是C语言的函数调用约定默认为__cdecl所导致);
  • .def文件的作用就是将被修饰过的C++函数重命名,使其可以被其他语言调用,具有通用性。

[How] 如何调用dll

新建一个控制台应用,在其中调用上述生成的dll。

调用dll有两种链接方式:隐式链接显式链接,无论哪种方式都要求将dll和exe放在同一目录下。

1. 隐式链接

  • 隐式链接需要三个文件:.h文件.lib文件.dll文件
  • 可以使用上一篇文章介绍的项目配置编译语句的方式将.h文件和.lib文件添加到项目中;如果dll项目和控制台项目在同一解决方案下,也可以采取直接引用的方式将dll项目添加到控制台项目。
  • 这里使用编译语句的方式,调用结果如下:

612057546b42d186586b0aa9476a596d.png
dll隐式链接调用结果

2. 显式链接

  • 显式链接只需要一个文件:.dll文件
  • 所谓显式链接,就是直接调用WIN32 API函数LoadLibraryGetProcAddressFreeLibrary显式地装载、卸载dll。它们的作用如下:

79dc92e66b7bd6b7e1c9a456db2be62b.png
dll显式链接 WIN32 API函数
  • 调用代码:
#include <iostream>
#include <Windows.h> // 必要头文件

void dllLinkExplicitly() {
    typedef void(*LPFNDLLFUNC)(std::ostream &);
    // equal to
    // using LPFNDLLFUNC = void(*)(std::ostream &);

    HINSTANCE hDLL;                     // Handle to DLL
    LPFNDLLFUNC lpfnMyDLLWithDllExport; // Function pointer
    LPFNDLLFUNC lpfnMyDLLWithExternC;   // Function pointer
    LPFNDLLFUNC lpfnMyDLLWithDefFile;   // Function pointer

    hDLL = LoadLibrary("MyDLL.dll");
    if (hDLL != NULL) {
        lpfnMyDLLWithDllExport = (LPFNDLLFUNC)GetProcAddress(hDLL, "fnMyDLLWithDllExport");
        lpfnMyDLLWithExternC = (LPFNDLLFUNC)GetProcAddress(hDLL, "fnMyDLLWithExternC");
        lpfnMyDLLWithDefFile = (LPFNDLLFUNC)GetProcAddress(hDLL, "fnMyDLLWithDefFile");

        if (!lpfnMyDLLWithDllExport) { // handle the error
            std::cout << "fnMyDLLWithDllExport load error.n";
        }
        else { // call the function
            lpfnMyDLLWithDllExport(std::cout);
        }

        if (!lpfnMyDLLWithExternC) {
            std::cout << "fnMyDLLWithExternC load error.n";
        }
        else {
            lpfnMyDLLWithExternC(std::cout);
        }

        if (!lpfnMyDLLWithDefFile) {
            std::cout << "fnMyDLLWithDefFile load error.n";
        }
        else {
            lpfnMyDLLWithDefFile(std::cout);
        }
    }
    FreeLibrary(hDLL);
}

int main() {
    dllLinkExplicitly();
    std::cout << "this is my exe.n";
    system("PAUSE");
}
  • 调用结果:

26a1a99bf7e8ae6e00a7703dd1d4a8d0.png
dll显式链接调用结果

可以看出,由于fnMyDLLWithDllExport的函数名被编译器修饰过,已经无法通过原来的函数名调用。这也暴露出显式链接的一个弊端:要求开发人员必须清楚地知道调用函数的导出名称传参格式

3. 小结

  • 隐式链接是程序载入内存时加载所需的dll,且该dll随主进程始终占用内存。
    • 优点:配置好.h文件和.lib文件的路径后调用方式简单直接;
    • 缺点:需要借助.h文件和.lib文件获得所需函数的入口(如使用#pragma comment语句),注意这里的lib是dll导入库,与静态链接库lib有所不同。
  • 显式链接是在程序运行过程中需要时使用LoadLibrary加载,不需要时则使用FreeLibrary将其卸载。如果加载时该dll已经在内存,则只需将其引用计数加1,如果其引用计数为0则移出内存。
    • 优点:不需要.h文件和.lib文件,可以动态加载、卸载dll;
    • 缺点:调用WIN32 API函数增加了编码量,需要开发人员对dll很了解。

[Why] dll的优缺点

  • 优点:相比lib加载全量代码到exe中,dll节省了内存空间资源;当程序更新时,仅需要以补丁或扩展的形式发布新的dll即可。
  • 缺点:dll需随exe一起发布,否则程序在运行时会产生错误。

[Summary] lib和dll的比较

4e4f9b888fd22584d1fd79825e51e329.png
lib和dll的比较

[Github] 代码

项目实例均在vs2017上测试,并上传至GitHub:

https://github.com/lyandut/EXE_LIB_DLL​github.com

[Reference] 参考

windows下LIB和DLL的区别与使用 - AlanTu - 博客园

lib 和 dll 的区别、生成以及使用详解

VS2017下dll和lib的使用_C#_weixin_43118068的博客-CSDN博客

使用VS2017创建DLL并链接至其他项目中【转】_运维_cynophile的博客-CSDN博客

VS2019 使用 C/C++ 动态链接库 并 进行调用

动态链接库基础-隐式链接与显式链接_运维_Nero Zhang的博客-CSDN博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android 平台上,可以通过使用 JNI(Java Native Interface)来调用 C++ 动态链接库(DLL)。JNI 是一个 Java 虚拟机规范,用于实现 Java 代码与本地代码的交互。通过 JNI,可以在 Java 声明本地方法,并将其实现委托给本地代码,以便在 Java 调用本地方法。 要在 Android 调用 C++ DLL,需要先将 DLL 编译为适用于 Android 平台的共享库(.so 文件),然后在 Java 代码使用 JNI 调用共享库。具体步骤如下: 1. 在 C++ 编写动态链接库,并将其编译为适用于 Android 平台的共享库(.so 文件)。 2. 在 Java 声明 native 方法,以便在 Java 调用 C++ 方法。例如: ```java public class MyNativeClass { public native int myNativeMethod(int arg); } ``` 3. 在 C++ 实现该方法,并将其封装为静态库(.a 文件)或共享库(.so 文件)。 4. 将 C++ 库与 Java 代码链接,并打包为 Android 应用程序。 5. 在 Java 代码使用 System.loadLibrary() 方法加载 C++ 库,并调用 native 方法。例如: ```java public class MyJavaClass { static { System.loadLibrary("MyNativeLibrary"); } public static void main(String[] args) { MyNativeClass myNativeClass = new MyNativeClass(); int result = myNativeClass.myNativeMethod(42); System.out.println("Result: " + result); } } ``` 需要注意的是,JNI 调用 C++ 方法的过程,需要进行参数类型转换和内存管理等操作,因此需要谨慎处理。另外,由于 Android 平台涉及多种架构,需要针对不同的架构编译对应的共享库。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值