在Visual Studio中用C++语言创建DLL动态链接库图文教程

什么是DLL(动态链接库)?

DLL是一个包含可由多个程序同时使用的代码和数据的库。例如:在Windows操作系统中,Comdlg32 DLL执行与对话框有关的常见函数。因此,每个程序都可以使用该DLL中包含的功能来实现“打开”对话框。这有助于促进代码重用和内存的有效使用。这篇文章的目的就是让你一次性就能了解和掌握DLL。

为什么要使用DLL(动态链接库)?

代码复用是提高软件开发效率的重要途径。一般而言,只要某部分代码具有通用性,就可以将它构造成相对独立的功能模块并在之后的项目中重复使用。比较常见的例子是各种应用程序框架,它们都以源代码的形式发布。由于这种复用是源代码级别的,源代码完全暴露给了程序员,因而称之为“白盒复用”。白盒复用有以下三个缺点:

1.暴露源代码,多份拷贝,造成存储浪费;
2.容易与程序员的本地代码发生命名冲突;
3.更新模块功能比较困难,不利于问题的模块化实现;

为了弥补这些不足,就提出了“二进制级别”的代码复用了。使用二进制级别的代码复用一定程度上隐藏了源代码,对于“黑盒复用”的途径不只DLL一种,静态链接库,甚至更高级的COM组件都是。

使用DLL主要有以下优点:

1.使用较少的资源;当多个程序使用同一函数库时,DLL可以减少在磁盘和物理内存中加载的代码的重复量。这不仅可以大大影响在前台运行的程序,而且可以大大影响其它在Windows操作系统上运行的程序;
2.推广模块式体系结构;
3.简化部署与安装。

创建DLL

打开Visual Studio 2012,创建如下图的工程:

输入工程名字,单击[OK];

单击[Finish],工程创建完毕了。

现在,我们就可以在工程中加入我们的代码了。加入MyCode.h和MyCode.cpp两个文件;在MyCode.h中输入以下代码:

代码如下:

#ifndef _MYCODE_H_
#define _MYCODE_H_
#ifdef DLLDEMO1_EXPORTS
#define EXPORTS_DEMO _declspec( dllexport )
#else
#define EXPORTS_DEMO _declspec(dllimport)
#endif
extern "C" EXPORTS_DEMO int Add (int a , int b);
#endif

在MyCode.cpp中输入以下代码:

代码如下:

#include "stdafx.h"
#include "MyCode.h"
int Add ( int a , int b )
{
       return ( a + b );
}

编译工程,就会生成DLLDemo1.dll文件。在代码中,很多细节的地方,我稍后进行详细的讲解(工程下载)。

使用DLL

当我们的程序需要使用DLL时,就需要去加载DLL,在程序中加载DLL有两种方法,分别为加载时动态链接和运行时动态链接。

1.在加载时动态链接中,应用程序像调用本地函数一样对导出的DLL函数进行显示调用。要使用加载时动态链接,需要在编译和链接应用程序时提供头文件和导入库文件(.lib)。当这样做的时候,链接器将向系统提供加载DLL所需的信息,并在加载时解析导出的DLL函数的位置;

2.在运行时动态链接中,应用程序调用LoadLibrary函数或LoadLibraryEx函数以在运行时加载DLL。成功加载DLL后,可以使用GetProcAddress函数获得要调用的导出的DLL函数的地址。在使用运行时动态链接时,不需要使用导入库文件。

在实际编程时有两种使用DLL的方法,那么到底应该使用那一种呢?在实际开发时,是基于以下几点进行考虑的:

1.启动性能如果应用程序的初始启动性能很重要,则应使用运行时动态链接;
2.易用性在加载时动态链接中,导出的DLL函数类似于本地函数,我们可以方便地进行这些函数的调用;
3.应用程序逻辑在运行时动态链接中,应用程序可以分支,以便按照需要加载不同的模块。

下面,我将分别使用两种方法调用DLL动态链接库。

加载时动态链接:

代码如下:

#include <windows.h>
#include <iostream>
//#include "..\\DLLDemo1\\MyCode.h"
using namespace std;
#pragma comment(lib, "..\\debug\\DLLDemo1.lib")
extern "C" _declspec(dllimport) int Add(int a, int b);
int main(int argc, char *argv[])
{
      cout<<Add(2, 3)<<endl;
      return 0;
}

运行时动态链接:

代码如下:

#include <windows.h>
#include <iostream>
using namespace std;
typedef int (*AddFunc)(int a, int b);
int main(int argc, char *argv[])
{
      HMODULE hDll = LoadLibrary("DLLDemo1.dll");
      if (hDll != NULL)
      {
            AddFunc add = (AddFunc)GetProcAddress(hDll, "Add");
            if (add != NULL)
            {
                  cout<<add(2, 3)<<endl;
            }
            FreeLibrary(hDll);
      }
}

上述代码都在DLLDemo1工程中。(工程下载)。

DllMain函数

Windows在加载DLL时,需要一个入口函数,就像控制台程序需要main函数一样。有的时候,DLL并没有提供DllMain函数,应用程序也能成功引用DLL,这是因为Windows在找不到DllMain的时候,系统会从其它运行库中引入一个不做任何操作的默认DllMain函数版本,并不意味着DLL可以抛弃DllMain函数。

根据编写规范,Windows必须查找并执行DLL里的DllMain函数作为加载DLL的依据,它使得DLL得以保留在内存里。这个函数并不属于导出函数,而是DLL的内部函数,这就说明不能在客户端直接调用DllMain函数,DllMain函数是自动被调用的。

DllMain函数在DLL被加载和卸载时被调用,在单个线程启动和终止时,DllMain函数也被调用。参数ul_reason_for_call指明了调用DllMain的原因,有以下四种情况:

DLL_PROCESS_ATTACH:当一个DLL被首次载入进程地址空间时,系统会调用该DLL的DllMain函数,传递的ul_reason_for_call参数值为DLL_PROCESS_ATTACH。这种情况只有首次映射DLL时才发生;

DLL_THREAD_ATTACH:该通知告诉所有的DLL执行线程的初始化。当进程创建一个新的线程时,系统会查看进程地址空间中所有的DLL文件映射,之后用DLL_THREAD_ATTACH来调用DLL中的DllMain函数。要注意的是,系统不会为进程的主线程使用值DLL_THREAD_ATTACH来调用DLL中的DllMain函数;

DLL_PROCESS_DETACH:当DLL从进程的地址空间解除映射时,参数ul_reason_for_call参数值为DLL_PROCESS_DETACH。当DLL处理DLL_PROCESS_DETACH时,DLL应该处理与进程相关的清理操作。如果进程的终结是因为系统中有某个线程调用了TerminateProcess来终结的,那么系统就不会用DLL_PROCESS_DETACH来调用DLL中的DllMain函数来执行进程的清理工作。这样就会造成数据丢失;

DLL_THREAD_DETACH:该通知告诉所有的DLL执行线程的清理工作。注意的是如果线程的终结是使用TerminateThread来完成的,那么系统将不会使用值DLL_THREAD_DETACH来执行线程的清理工作,这也就是说可能会造成数据丢失,所以不要使用TerminateThread来终结线程。以上所有讲解在工程DLLMainDemo(工程下载)都有体现。

函数导出方式

在DLL的创建过程中,我使用的是_declspec( dllexport )方式导出函数的,其实还有另一种导出函数的方式,那就是使用导出文件(.def)。你可以在DLL工程中,添加一个Module-Definition File(.def)文件。.def文件为链接器提供了有关被链接器程序的导出、属性及其它方面的信息。

对于上面的例子,.def可以是这样的:

代码如下:

LIBRARY     "DLLDemo2"
EXPORTS
Add @ 1 ;Export the Add function

Module-Definition File(.def)文件的格式如下:

1.LIBRARY语句说明.def文件对应的DLL;
2.EXPORTS语句后列出要导出函数的名称。可以在.def文件中的导出函数名后加@n,表示要导出函数的序号为n(在进行函数调用时,这个序号有一定的作用)。

使用def文件,生成了DLL,客户端调用代码如下:

代码如下:

#include <windows.h>
#include <iostream>
using namespace std;
typedef int (*AddFunc)(int a, int b);
int main(int argc, char *argv[])
{
      HMODULE hDll = LoadLibrary("DLLDemo2.dll");
      if (hDll != NULL)
      {
            AddFunc add = (AddFunc)GetProcAddress(hDll, MAKEINTRESOURCE(1));
            if (add != NULL)
            {
                  cout<<add(2, 3)<<endl;
            }
            FreeLibrary(hDll);
      }
}

可以看到,在调用GetProcAddress函数时,传入的第二个参数是MAKEINTRESOURCE(1),这里面的1就是def文件中对应函数的序号。(工程下载)

extern “C”

为什么要使用extern “C”呢?C++之父在设计C++时,考虑到当时已经存在了大量的C代码,为了支持原来的C代码和已经写好的C库,需要在C++中尽可能的支持C,而extern “C”就是其中的一个策略。在声明函数时,注意到我也使用了extern “C”,这里要详细的说说extern “C”。

extern “C”包含两层含义,首先是它修饰的目标是”extern”的;其次,被它修饰的目标才是”C”的。先来说说extern;在C/C++中,extern用来表明函数和变量作用范围(可见性)的关键字,这个关键字告诉编译器,它申明的函数和变量可以在本模块或其它模块中使用。extern的作用总结起来就是以下几点:

1.在一个文件内,如果外部变量不在文件的开头定义,其有效范围只限定在从定义开始到文件的结束处。如果在定义前需要引用该变量,则要在引用之前用关键字”extern”对该变量做”外部变量声明”,表示该变量是一个已经定义的外部变量。有了这个声明,就可以从声明处起合理地使用该变量了,例如:

代码如下:

/*
** FileName     : Extern Demo
** Author       : Jelly Young
** Date         : 2013/11/18
** Description  : More information
*/
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
      extern int a;
      cout<<a<<endl;
}
int a = 100;

2.在多文件的程序中,如果多个文件都要使用同一个外部变量,不能在各个文件中各定义一个外部变量,否则会出现“重复定义”的错误。正确的做法是在任意一个文件中定义外部变量,其它文件用extern对变量做“外部变量声明”。在编译和链接时,系统会知道该变量是一个已经在别处定义的外部变量,并把另一文件中外部变量的作用域扩展到本文件,这样在本文件就可以合法地使用该外部变量了。写过MFC程序的人都知道,在在CXXXApp类的头文件中,就使用extern声明了一个该类的变量,而该变量的实际定义是在CXXXApp类的实现文件中完成的;

3.外部函数,在定义函数时,如果在最左端加关键字extern,表示此函数是外部函数。C语言规定,如果在定义时省略extern,则隐含为外部函数。而内部函数必须在前面加static关键字。在需要调用此函数的文件中,用extern对函数作声明,表明该函数是在其它文件中定义的外部函数。

接着说”C”的含义。我们都知道C++通过函数参数的不同类型支持重载机制,编译器根据参数为每个重载函数产生不同的内部标识符;但是,如果遇到了C++程序要调用已经被编译后的C函数,那该怎么办呢?比如上面的int Add ( int a , int b )函数。该函数被C编译器后在库中的名字为_Add,而C++编译器则会生成像_Add_int_int之类的名字用来支持函数重载和类型安全。由于编译后的名字不同,C++程序不能直接调用C函数,所以C++提供了一个C连接交换指定符号extern “C”来解决这个问题;所以,在上面的DLL中,Add函数的声明格式为:extern “C” EXPORTS_DEMO int Add (int a , int b)。这样就告诉了C++编译器,函数Add是个C连接的函数,应该到库中找名字_Add,而不是找_Add_int_int。当我们将上面DLL中的”C”去掉,编译生成新的DLL,使用Dependency Walker工具查看该DLL,如图:

请注意导出方式为C++,而且导出的Add函数的名字添加了很多的东西,当使用这种方式导出时,客户端调用时,代码就是下面这样:

代码如下:

#include <windows.h>
#include <iostream>
using namespace std;
typedef int (*AddFunc)(int a, int b);
int main(int argc, char *argv[])
{
     HMODULE hDll = LoadLibrary("DLLDemo1.dll");
     if (hDll != NULL)
     {
          AddFunc add = (AddFunc)GetProcAddress(hDll, "?Add@@YAHHH@Z");
          if (add != NULL)
          {
               cout<<add(2, 3)<<endl;
          }
          FreeLibrary(hDll);
     }
}

请注意GetProcAddress函数的第二个参数,该参数名就是导出的函数名,在编码时,写这样一个名字是不是很奇怪啊。当我们使用extern “C”方式导出时,截图如下:

注意导出方式为C,而且函数名现在就是普通的Add了。我们再使用GetProcAddress时,就可以直接指定Add了,而不用再加那一长串奇怪的名字了。

DLL导出变量

DLL定义的全局变量可以被调用进程访问;DLL也可以访问调用进程的全局数据。(工程下载)

DLL导出类

DLL中定义的类,也可以被导出。详细工程代码,请参见(工程下载)

总结

对DLL的讲解就到此结束,由于MFC在现在的环境下使用较少,此处不予讲解,如果以后做项目遇到了MFC的DLL相关知识,我再做总结。最后,希望大家给我的博客提出一些中肯的建议。

(0)

相关推荐

  • PHP中调用C/C++制作的动态链接库的教程

    一般而言,php速度已经比较快,但是,对于一些较高级开发者而言,如果想要追求更快的速度,那毫无疑问可以通过自己写c代码,并编译为动态链接库(常为.so文件),然后php通过创建一个新的扩展(extension),并在扩展里调用该.so文件,同时对外暴露出php函数接口. 在实际使用中,只要调用该函数接口,即可使用底层更快速的c函数服务. 一.动态链接库(shared) 动态链接库的文件名后缀通常是 ".so".在Windows系统中,其文件名后缀是".dll". 程

  • 浅析C/C++中动态链接库的创建和调用

    DLL 有助于共享数据和资源.多个应用程序可同时访问内存中单个DLL 副本的内容.DLL 是一个包含可由多个程序同时使用的代码和数据的库.下面为你介绍C/C++中动态链接库的创建和调用. 动态连接库的创建步骤: 创建Dll有两种方式. 一.创建Non-MFC DLL动态链接库 1.打开File -> New -> Project选项,选择Win32 Dynamic-Link Library ->sample project ->工程名:DllDemo 2.新建一个.h文件DllDe

  • 在C语言中调用C++做的动态链接库

    今天在做东西的时候遇到一个问题,就是如何在C语言中调用C++做的动态链接库so文件 如果你有一个c++做的动态链接库.so文件,而你只有一些相关类的声明, 那么你如何用c调用呢,别着急,本文通过一个小小的例子,让你能够很爽的搞定. 链接库头文件: head.h class A { public: A(); virtual ~A(); int gt(); int pt(); private: int s; }; firstso.cpp #include <iostream> #include &

  • Python 调用VC++的动态链接库(DLL)

    1. 首先VC++的DLL的导出函数定义成标准C的导出函数: 复制代码 代码如下: #ifdef LRDLLTEST_EXPORTS #define LRDLLTEST_API __declspec(dllexport) #else #define LRDLLTEST_API __declspec(dllimport) #endif extern "C" LRDLLTEST_API int Sum(int a , int b); extern "C" LRDLLTE

  • C++编写DLL动态链接库的步骤与实现方法

    本文实例讲述了C++编写DLL动态链接库的步骤与实现方法.分享给大家供大家参考,具体如下: 在写C++程序时,时常需要将一个class写成DLL,供客户端程序调用.这样的DLL可以导出整个class,也可以导出这个class的某个方法. 一.导出整个class 方法很简单,只需要在类的头文件中class和类名之间加上_declspec(dllexport),同时在另外一份提供给客户端调用程序使用的类的头文件中class和类名之间加上_declspec(dllimport).为了能让客户端程序和D

  • Python调用C/C++动态链接库的方法详解

    本文以实例讲解了Python调用C/C++ DLL动态链接库的方法,具体示例如下: 示例一: 首先,在创建一个DLL工程(本例创建环境为VS 2005),头文件: //hello.h #ifdef EXPORT_HELLO_DLL #define HELLO_API __declspec(dllexport) #else #define HELLO_API __declspec(dllimport) #endif extern "C" { HELLO_API int IntAdd(in

  • 详解C++的JSON静态链接库JsonCpp的使用方法

    JsonCpp部署方法: 在http://sourceforge.net/projects/jsoncpp/中下载最新版本的jsoncpp库源码. 之后将jsoncpp-src-版本号-tar.gz解压出来,打开makefiles中的jsoncpp.sln进行编译,之后build文件夹下的vs71\debug\lib_json中会有一个.lib静态链接库. JsonCpp主要包含三种类型的class:Value Reader Writer. jsoncpp中所有对象.类名都在namespace

  • C++ 中动态链接库--导入和导出的实例详解

    C++ 中动态链接库--导入和导出的实例详解 __declspec(dllexport)和__declspec(dllimport): __declspec(dllexport):编译器看到一个变量.函数或者C++类被它修饰,那么它就知道应该在生成的DLL 模块中导出该变量.函数或C++类. __declspec(dllimport):编译器看到一个变量.函数或者C++类被它修饰,那么它就知道可执行文件或DLL的源文件需要从其它DLL模块中导入一些变量和函数. DLL的导入段: 构建可执行模块时

  • 在Visual Studio中用C++语言创建DLL动态链接库图文教程

    什么是DLL(动态链接库)? DLL是一个包含可由多个程序同时使用的代码和数据的库.例如:在Windows操作系统中,Comdlg32 DLL执行与对话框有关的常见函数.因此,每个程序都可以使用该DLL中包含的功能来实现"打开"对话框.这有助于促进代码重用和内存的有效使用.这篇文章的目的就是让你一次性就能了解和掌握DLL. 为什么要使用DLL(动态链接库)? 代码复用是提高软件开发效率的重要途径.一般而言,只要某部分代码具有通用性,就可以将它构造成相对独立的功能模块并在之后的项目中重复

  • Visual Studio Code 使用Git进行版本控制(图文教程)

    本来认为此类教程,肯定是满网飞了.今天首次使用VS Code的Git功能,翻遍了 所有中文教程,竟没有一个靠谱的.遂动笔写一篇. 请确保你安装了最新的VS Code.http://code.visualstudio.com/ 请确保安装了最新版的Git.https://git-scm.com/download.git安装到环境变量里, 确保任意路径可以访问. 参考链接:https://code.visualstudio.com/Docs/editor/versioncontrol VS Code

  • Visual Studio 2022 的安装和创建C++项目(图文教程)

    目录 1.下载 2.安装 3.接下来就是创建项目 4.如果你想添加多个.cpp,并且想要运行成功,可以右键.cpp.属性, 下面我们来讲一下如何下载安装VS 2022并且创建C++项目. 1.下载 首先,我们来到VS的微软官网下载地址:https://visualstudio.microsoft.com/zh-hans/vs/ 然后点击下图我画红圈的这个版本:Visual Studio Community(VS 社区版)这个版本是不要钱的,社区版其实就是个人版,在开发.调试上有一些限制,下面两个

  • Visual Studio 2022下载安装与使用超详细教程

    目录 前言 一.Windows安装Visual Studio 2022 1.1 下载地址 1.2 开始安装 1.3 打开Visual Studio 二.Visual Studio创建一个新的项目 2.1 一些准备工作 2.2 创建一个新的项目 2.3 创建C文件 前言 本文用于记录Visual Studio 2022的安装与使用,将持续进行更新.当前版本:V1,2021.12.04 一.Windows安装Visual Studio 2022 1.1 下载地址 首先登陆Visual Studio官

  • VC创建DLL动态链接库的方法

    本文实例讲述了VC创建DLL动态链接库的方法.分享给大家供大家参考.具体实现方法如下: 头文件中声明 t.h #ifdef DLL_API #else #define DLL_API extern "C" _declspec(dllimport) #endif DLL_API int a(int cnt); DLL_API int b(int cnt); .h文件引入 #define DLL_API extern "C" _declspec(dllexport) #

  • R语言的下载安装图文教程讲解

    什么是R语言 R语言是一个开源的数据分析环境,起初是由数位统计学家建立起来,以更好的进行统计计算和绘图,这篇wiki中包含了一些基本情况的介绍.由于R可以通过安装扩展包(Packages)而得到增强,所以其功能已经远远不限于统计分析,如果感兴趣的话可以到官方网站了解关于其功能的更多信息. 至于R语言名称的由来则是根据两位主要作者的首字母(Robert Gentleman and Ross Ihaka),但过于简短的关键词也造成在搜索引擎中很不容易找到相关的资料.不过这个专门的搜索网站可以帮到你.

  • 用Python创建简易网站图文教程

    前言 本方法基于web2py框架,使用web2py的完整网站数据包创建简单网站. web2py 是一个为Python语言提供的全功能Web应用框架,旨在敏捷快速的开发Web应用,具有快速.安全以及可移植的数据库驱动的应用,兼容 Google App Engine. (百度百科:https://baike.baidu.com/item/web2py/8111052?fr=aladdin) 教程 1.进入网址:http://www.web2py.com/init/default/download根据

  • 使用vue-cli创建项目的图文教程(新手入门篇)

    本文是针对对于完全没有了解过vue 和npm,连运行环境和项目构建的都不会的小白,对于前端老司机的就不用看了,浪费时间. 使用npm 与vue-cli 构建vue 项目 第一步:安装运行环境(node与npm) nodeJ官网:http://nodejs.cn/下载安装包( 安装) 安装完成后,需要检测是否安装成功 使用命令行cmd 打开dos 黑窗口,运行 node -v 和 npm –v,出现版号说明安装成功 注(npm 是node自带的,在安装node时已经安装了) 如何升级npm 的版本

  • Springboot创建项目的图文教程(idea版本)

    原文地址:http://www.javayihao.top/detail/84 一:概述 由于springboot项目,不管是java工程还是web工程都可以直接以jar方式运行,所以推荐创建jar工程,这里创建jar工程项目为例. 二:两种方式创建springboot项目 1.第一种方式 手动在idea中new一个新的项目.选择maven工程 完成的结构如图 然后在pom文件继承spring-boot-starter-parent依赖接口完成创建 <?xml version="1.0&q

  • Android Studio签名打包的两种方式(图文教程)

    签名打包的两种方式: 注:给我们自己开发的app签名,就代表着我自己的版权,以后要进行升级,也必须要使用相同的签名才行.签名就代表着自己的身份(即keystore),多个app可以使用同一个签名. 如果不知道签名是啥意思,请自行百度哦.在eclipse中签名的方法是:选中工程,邮件选择"export-android-export android application", 1.方式1:通过Android Studio进行签名: 选中app这个module,选择菜单栏"Buil

随机推荐