简介本文介绍如何快速入门Windows DLL开发,感兴趣的朋友可以参考一下。
用户模块是由用户自己开发的、可以加入到最终用户(包括用户本人和其他使用该模块的人)应用程序中 提供某一特定功能的函数和类的集合 。
为了完成同样的工作,也可以向最终用户提供源程序。但是,使用用户模块有许多好处:
1.首先是省去用户管理源代码的烦恼,用户许多情况下往往并不关心模块的内部实现,他只是想把它作为一个黑匣子使用。
2.另外,模块的开发者有时候并不希望模块使用者看到源代码。
3.使用模块而不使用源代码还可以避免模块的函数名、变量名与最终用户的程序上的冲突。
用户模块可分为两大类:静态链接库和动态链接库。
静态链接库提供了函数的完整的目标代码,如果程序调用静态链接库中的 函数 ,则在 进行链接时连接程序将静态链接库中所包含的该函数的代码拷贝至运行文件中 。
动态链接库是一个可执行模块,其包含的函数可以由Windows应用程序调用以执行一些功能。动态链接库 主要为应用程序模块提供服务 。Windows内核的三个模块USER.EXE、KENERL.EXE和GDI.EXE实际上都是动态链接库,分别提供用户消息服务、进程管理、图形输出等服务。
动态链接库也包含了其所提供的函数的目标代码,但是在程序链接动态链接库中的函数时,链接程序并不将包含在动态链接库中的函数的目标代码拷贝至运行文件,而 只是简单地记录了函数的位置信息 (即包含于哪个动态链接库中以及在动态链接库中的位置)。有了这些信息后,程序在执行时,即可找到该函数的目标代码。因为只是在执行时才得到真正的链接,因此称为动态链接。提供函数在动态链接库中位置的信息存放在一个独立的文件中,这个文件就是引入库(IMPORT LIB)。
对比: 由于静态链接库将目标代码连接到应用程序中,当程序运行时,如果两个程序调用了同一静态库中的函数,内存中将出现该函数的多份拷贝。而动态链接库则更适合于多任务环境:当两个应用程序调用了同一动态链接库中的同一个函数时,内存中只保留该函数的一份拷贝,这样内存利用率更高。
在一些情况下,必须使用动态链接库:
在使用AppWizard生成应用程序时,我们可以指定资源文件使用的语言,这就是通过提供不同的动态链接库实现的。
VisualC++ 支持三种 DLL
非MFC动态库不采用 MFC 类库结构, 其导出函数为标准的 C接口, 能被非 MFC 或 MFC 编写的应用程序所调用;
MFC 规则 DLL 包含一个继承自CWinApp 的类,但其无消息循环 ;
MFC 扩展 DLL采用MFC的动态链接版本创建,它只能被用 MFC 类库所编写的应用程序所调用。
MFC支持两类动态链接库的创建:用户动态链接库、MFC扩展类库
第一步:实现文件 .h .cpp
导出整个class
在类的头文件中class和类名之间加上__declspec(dllexport);
同时在另外一份提供给客户端调用程序使用的类的头文件中class和类名之间加上__declspec(dllimport)。
这里提供更好的方式,头文件一份:
#ifdef DLLPROJECT_EXPORTS
#define MATH_LIBRARY_API __declspec(dllexport)
#else
#define MATH_LIBRARY_API __declspec(dllimport)
#endif
其中 DLLPROJECT_EXPORTS为DLL项目工程自定预定义的。
为了能让客户端程序和DLL程序公用该类的一份头文件,通常在类的头文件中使用宏和预编译指令来处理。
MathLibrary.h:
// MathLibrary.h
#ifndef MATHLIBRARY_H
#define MATHLIBRARY_H
// 定义类导出
#define MATH_LIBRARY_API __declspec(dllexport)
class MATH_LIBRARY_API CMathLibrary
{
public:
CMathLibrary();
~CMathLibrary();
static int Summary(int n);
static int Factorial(int n);
};
#endif
MathLibrary.cpp:
// MathLibrary.cpp
#include "stdafx.h"
#include"MathLibrary.h"
CMathLibrary::CMathLibrary()
{
}
CMathLibrary::~CMathLibrary()
{
}
int CMathLibrary::Summary(int n)
{
int sum = 0;
int i;
for (i = 1; i <= n; i++)
{
sum += i;
}
return sum;
}
int CMathLibrary::Factorial(int n)
{
int Fact = 1;
int i;
for (i = 1; i <= n; i++)
{
Fact = Fact*i;
}
return Fact;
}
第二步:定义动态链接库的入口函数,VS会自动创建。
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "stdafx.h"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
编译完成后提交的文件:
补充: 导出这个类的某个或者某几个方法:
这时,需要将_declspec(dllexport)放到成员函数名前,如DLLTest.h:
#ifdef DLL_TEST_API
#else
#define DLL_TEST_API _declspec(dllimport)
#endif
class CDLLTest
{
public:
CDLLTest();
~CDLLTest();
int DLL_TEST_API Add(int a, int b);
};
但是,如果仅仅是这样的话,当客户端程序#include这个头文件后,定义DLLTest这个类的一个对象后(静态方式链接DLL),客户端程序无法链接通过,会提示构造函数和析构函数无法解析, 此时,需要将构造函数和析构函数前也加上DLL_TEST_API宏即可 。
当然这里还有个问题就是类的函数在导出后,名字会发生变化,我们可以在函数名前再加上extern “C”,
如 extern “C” DLL_TEST_API int Add(int a ,int b);
但这只解决了C与C++调用时名字变更问题,可靠的方法还是增加一个模块定义文件def,在该文件中定义导出函数的名称,
类中:static __declspec(dllexport) double add(double a,double b)
C方式:extern "C" int __declspec(dllexport)add(int x, int y);
LIBRARY "DLLProject"
EXPORTS
;The names of the DLL functions
Summary
Factorial
1. 通过隐式链接
方法一:
项目->属性->配置属性->VC++ 目录-> 在“包含目录”里添加头文件testdll.h所在的目录
项目->属性->配置属性->VC++ 目录-> 在“库目录”里添加头文件testdll.lib所在的目录
项目->属性->配置属性->链接器->输入-> 在“附加依赖项”里添加“testdll.lib”(若有多个 lib 则以空格隔开)
方法二:
或者直接在项目中添加.h ,然后引入.lib:
#include <iostream>
#include"MathLibrary.h"
#pragma comment(lib,"DLLProject.lib")
int _tmain(int argc, _TCHAR* argv[])
{
std::cout << "Values: " << CMathLibrary::Factorial(5) << std::endl;
return 0;
}
2. 显式链接
使用这种方式的应用程序可以在执行过程可以随时可以加载DLL,也可以随时卸载,更具灵活性,特别适合解释性语言。
#include <iostream>
#include <windows.h>
#include "StaticLibrary.h"
HINSTANCE ghMathsDLL = NULL; // 句柄实例
//Declare the Summary() function from the DLL.
typedef int(*SUMMARY)(int);
SUMMARY Summary;
typedef int(*FACTORIAL)(int);
FACTORIAL Factorial;
// 载入库
void LoadDLL()
{
// 如果DLL已经载入,则返回
if (ghMathsDLL != NULL)
{
return;
}
// 载入
ghMathsDLL = LoadLibrary(_T("DLLProject.DLL"));
// 载入失败
if (ghMathsDLL == NULL)
{
std::cout << "Cannot load DLL file!" << std::endl;
}
// 获得DLL中Summary函数的地址
Summary = (SUMMARY)GetProcAddress(ghMathsDLL, "Summary");
// 获得DLL中Factorial函数的地址
Factorial = (FACTORIAL)GetProcAddress(ghMathsDLL, "Factorial");
}
int _tmain(int argc, _TCHAR* argv[])
{
//LoadDLL();
std::cout << "Values: " << Factorial(5) << std::endl;
// 释放
FreeLibrary(ghMathsDLL);
return 0;
}
LoadLibrary函数装入所需的动态链接库,并返回库的句柄。如果句柄小于32,则载入库失败,错误含义参见有关手册。GetProcAddress函数使用函数名字取得函数的地址。利用该函数地址,就可以访问动态链接库的函数了。
FreeLibrary通过检查动态链接库的引用计数器,判断是否还有别的程序在使用这个动态链接库。 如果没有,就从内存中移去该动态链接库;如果有,将动态链接库的使用计数器减1 。LoadLibrary则将引用计数加1。
使用VS创建一个StaticLibrary的静态库工程,然后在工程中加入以下两个文件。
//StaticLibrary.h
#pragma once
extern "C"
{
// 定义了Summary和Factorial两个函数,分别用于完成求和与阶乘。
// 注意这里使用C风格的函数,需要加入extern “C”关键字,表明它是C风格的外部函数。
int Summary(int n);
int Factorial(int n);
}
// StaticLibrary.cpp
#include "stdafx.h"
#include"StaticLibrary.h"
int Summary(int n)
{
int sum = 0;
int i;
for (i = 1; i <= n; i++)
{
sum += i;
}
return sum;
}
int Factorial(int n)
{
int Fact = 1;
int i;
for (i = 1; i <= n; i++)
{
Fact = Fact*i;
}
return Fact;
}
提示:用户在交付最终静态连接库时, 只需要提供.lib文件和头文件 ,不需要再提供库的源代码。
#include "stdafx.h"
#include <iostream>
#include "StaticLibrary.h"
int _tmain(int argc, _TCHAR* argv[])
{
std::cout << "Values: " << Factorial(5) << std::endl;
return 0;
}
本文向大家介绍一个C++实战项目:C++实现雪花算法(SnowFlake)产生唯一ID,主要涉及雪花算法、算法知识等,具有一定的C++实战价值,感兴趣的朋友可以参考一下。
本文介绍一个C++代码片段:如何在C++中删除一个文件目录下的所有文件及目录,感兴趣的朋友可以参考一下。
本文介绍C++实现C++实现8种排序算法,主要包括冒泡排序、插入排序、二分插入排序、希尔排序、直接选择排序、堆排序、归并排序、快速排序,直接上代码,感兴趣的朋友可以参考一下。
本文介绍C++实现线程同步的四种方式:事件对象、互斥对象、临界区、信号量,感兴趣的朋友可以参考一下。
本文介绍C++内存泄漏的检测与定位方法,感兴趣的朋友可以参考一下。
本文向大家介绍一个C++实战项目:C++实现一个多线程安全的队列容器模板类,主要涉及C++模板类的使用、互斥体实现多线程安全、队列数据结构等知识,具有一定的C++实战价值,感兴趣的朋友可以参考一下。