C++ DLL相关知识

一、基本概念

若要导出一个全局函数,就用关键字__declspec(dllexport)来声明

在Dll项目中建立一个全局函数

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

DLL项目设置:

  1. 取消"预编译头文件”
  2. 改为"/MTd"编译
  3. 修改输出Dll名字

 编译得到dll和lib。

.lib:包含一个列表,表明dll中含有哪些符号,每个符号对应在dll中的位置

简单调用dll:

#include <stdio.h>

//使用库
#pragma comment(lib,"TestDLL.lib")

//声明:此函数需要从dll导入
__declspec(dllimport) int Add(int a, int b);

int main()
{
	int result = Add(10, 11);
	return 0;
}

Dll放置位置:(其中第二条是指在调试的时候,项目的目录)

二、DLL加载和卸载

加载

在exe文件中有一些标识信息,表示exe依赖哪些dll文件,操作系统根据此去寻找,加载相应的dll文件。

当dll被加载时,代码段只被加载一次,是公有的;

数据段被每个程序各自拷贝一份,是私有的。

当动态库中定义了一个全局变量static int value =0;2个进程同时调用一个Dll,value的值互相不影响。

卸载

当所有调用进程都退出后,该Dll被卸载,这时候Dll才可以删除。

不同进程间不能比较变量的地址(因为是虚拟地址)

三、动态内存管理

在Dll中malloc申请的内存,必须在Dll中free,这是微软的规定,记住即可。

四、使用头文件

原则:

在Dll项目中,将函数声明为__declspec(dllexport)

在APP项目中,将函数声明为__declspec(dllimport)

使用条件编译,Dll部分:

//mydll.h
#ifndef _MYDLL_H
#define _MYDLL_H

#ifdef MYDLL_EXPORTS
#define MYDLL __declspec(dllexport)
#else
#define MYDLL __declspec(dllimport)
#endif

MYDLL int Add(int a, int b);

#endif
//mydll.cpp
#include <stdio.h>

#define MYDLL_EXPORTS
#include "mydll.h"

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

调用部分:

#include <stdio.h>
#include "mydll.h"

#pragma comment(lib,"my.lib")

int main()
{
    int result = Add(10,1);
    return 0;
}

五、导出类

要将类导出,只要在类前面加个标识符,如:

class MYDLL CMyClass

{ };

六、静态库

仅一个.lib文件,静态库中直接就含有代码段和数据段,在链接过程中,是直接把里面的东西链接过来,形成完整的可执行程序。

exe运行的时候不再依赖lib文件。

lib例子

//mylib.h
#ifndef _MYLIB_H
#define _MYLIB_H

int Add(int a, int b);

#endif
//mylib.cpp

#include "mylib.h"

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

测试lib例子:

#include <stdio.h>
#include "mylib.h"

#pragma comment(lib,"TestDLL_Static.lib")

int main()
{
	int result = Add(10, 14);
	printf("result: %d\n", result);
	
	return 0;
}

七、静态库和动态库对比

静态库优点:最后可执行程序执行对这个库不再依赖(已经把符号链接过来了)

静态库缺点:很多,一般使用Dll

动态库优点:便于升级更新,只要保持接口不变,更换Dll进行升级

八、动态库自动加载与手动加载(也叫动态加载)

自动加载:在编译的时候指定dll,当exe程序启动运行时,首先加载相关的dll。

手动加载:运行时候调用LoadLibrary来加载dll;使用FreeLibrary来卸载dll。动态加载增加了变成的灵活性。

手动加载对DLL的要求:

  1. 要求待调用的函数按"C"方式编译(符号名即为函数名)
  2. dll文件放在可被系统搜索到的文件夹

 例如:

C编译形式Dll

//mydll.h
#ifndef _MYDLL_H
#define _MYDLL_H

#ifdef MYDLL_EXPORTS
#define MYDLL __declspec(dllexport)
#else
#define MYDLL __declspec(dllimport)
#endif

extern "C" MYDLL int Add(int a, int b);

#endif
//mydll.cpp
#include <stdio.h>

#define MYDLL_EXPORTS
#include "mydll.h"

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

动态调用:

#include <stdio.h>

#include <WinSock2.h>
#include <Windows.h>

int main()
{
	HINSTANCE handle = LoadLibrary(L"TestDLL_CDll.dll");
	if(handle)
	{
		//定义要找的函数原型
		typedef int(*DLL_FUNCTION_ADD) (int, int);
		//找到目标函数的地址
		DLL_FUNCTION_ADD dll_func = (DLL_FUNCTION_ADD)GetProcAddress(handle, "Add");
		if(dll_func)
		{
			int result = dll_func(10, 20);
			printf("result:%d\n", result);
		}
		//卸载
		FreeLibrary(handle);
	}
}

九、动态编译和静态编译/MT /MD

d:debug

M:多线程multi-threading

T:text代码

D:dynamic动态库

静态编译:/MT /MTd

                  是指使用libc和msvc相关的静态库(lib)

                  文件会比较大

动态编译:/MD /MDd

                  是指使用相应的DLL版本编译

                   会导致目标机器没有对应的动态库而运行不了

1、/MT /MD选择:

为什么选择/MD,不选/MT?

         (1)程序就不需要静态链接运行时库,可以减小软件的大小;

         (2)所有的模块都采用/MD,使用的是同一个堆,不存在A堆申请,B堆释放的问题。

为什么选择/MT,不选择/MD?

          (1)有些系统可能没有程序所需要版本的运行时库,程序必须把运行时库静态链接上

 多个模块,必须选择相同类型的运行时库,不要混合使用。

2.选择/MT需要解决的堆空间释放问题:

不同的模块各自有一份C运行时库代码,各个C运行库会有各自的堆,导致了各个模块会有各自的堆。如果在A堆中申请空间,到B堆中释放就会有崩溃,在模块A申请的空间,必须在模块A中释放。

         附件(下载地址:http://files.cnblogs.com/cswuyg/Test_MD_and_MT.rar)的DLL以及DLLUser代码,以STL的string为例,通过修改编译选项验证了这个问题。(string在赋值的时候需要释放掉原来的空间,然后再申请新的空间存储新的内容。)

        string在赋值的时候需要释放掉原来的内存空间,然后再申请新的内存空间存储新的内容,如果跨模块了,释放的时候就存在“A模块申请B模块释放”的问题,导致程序崩溃。

   (跨模块释放内存导致崩溃的内容,在《windows核心编程》第五版Page511谈DLL和进程的地址空间时有谈到)

3.选择/MD需要注意多个模块使用不同版本运行时库的问题:

     多个dll被一个exe LoadLibrary加载,如果这些dll使用的运行时库是不同的,那么可能出现加载失败,原因可能是旧版本的运行时库已经在了,而某个dll它需要的是新版本的运行时库,旧版本不符合要求。

     如果工程里所有的模块都是自己写的或者可以完全控制的,那么这个问题不难解决,只需要在工程设置里都设置/MD,然后在相同的环境下编译一次就行。但是假如这个模块是外界提供的呢?

     可能存在这种情况:A动态库使用了B静态库,B静态库使用了C动态库,B静态库是外界提供的,我们要使用它,但无法修改它,我们也无法接触到C动态库。如果C动态库使用的运行时库版本跟编译A动态库的本地使用的不一致,那么A动态库里的嵌入信息就会记录两个不同版本的运行时库,它被加载的时候,可能会选择版本新的。假设A动态库被一个exe LoadLibrary加载,而这个exe本身的运行时库是旧的,这样就会导致A动态库加载失败,即便把新的运行时库拷贝到目录下也不行,因为exe这个进程已经加载了那个旧的运行时库。这时候必须使用manifest文件指定嵌入到A动态库里的运行时库为某个版本,忽略掉C动态库使用的运行时库版本。

     这个问题挺复杂的,我心思没去验证windows的PE文件加载会对运行时库做什么样的优先选择、运行时库在静态库里的记录…。只要记住,给外界使用的组件版本尽量避免使用/MD(这样会导致膨胀吗?据说,安装包可以做字节流式压缩)。

  附上另一个问题:静态库的依赖关系:exe-->libA-->libB,现在不想让exe接触到libB,于是把libA的librarian选项-->General选项-->Link Library Dependencies设置为Yes,这样即可,libA会包含libB,exe只需要接触libA。另外需要特别注意,libA对libB的依赖只需要且只能在Solution的Project Dependencies里设置,如果在libA的代码里写了”#pragma comment(lib, "libB.lib")”,会导致exe在link libA的时候提示找不到libA。如果exe还出现link错误,那一定是VS抽筋了:)

vc++编译时运行库选择(/MT、/MTd、/MD、/MDd)https://blog.csdn.net/lwwl12/article/details/77045717

VS项目属性中C/C++运行库 、MT /MTd /MD /MDdhttps://blog.csdn.net/u010059658/article/details/51026662

猜你喜欢

转载自blog.csdn.net/daiyunxing0545/article/details/84846004