DLL

一、建立静态链接库.lib的方式


1、建立工程文件

win32 project


2、选择静态库

static library


3、lib的头文件

#ifndef _TEST_LIB_
#define _TEST_LIB_

extern "C" int add(int x, int y);

#endif

extern "C" 只有在编写C++代码时才使用,编写C代码时应该使用,该修饰符是为了兼容C语言

extern "C" 是用来告诉编译器不要对变量名或函数名进行改编,这样C\C++或其它语言可以访问


4、lib的实现文件

#include "stdafx.h"
#include "test_lib.h"

int add(int x, int y)
{
	return x + y;
}

 

5、exe中的调用

加载方式一。

#include <stdio.h>
#include "../test_lib/test_lib.h"

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

int main()
{
	printf("%d\n",add(1,2));
	return 0;
}

头文件是必须添加的。

#pragma comment可以有方式二


加载方式二。

Additional Library Directories : ..\debug
Additional Dependencies : test_lib.lib

在配置中设置lib


 

二、DLL导出函数

1、建立工程文件

win32 project


2、选择动态链接库

dll


3、dll的头文件

方式1:

#ifndef _TEST_DLL_
#define _TEST_DLL_

extern "C" int __declspec(dllexport) add_dll(int a, int b);

#endif

在函数里面添加__declspec(dllexport)来导出符号

此方式,使用在DLL模块和调用者模块,是同一编译器时使用,因为编译器会将函数改名。不同编译器规则不同,会出现未定义情况。

因此为不同的编译器开发DLL,使用方式2.


方式2:

不使用__declspec(dllexport)

添加.def文件

;lib.def : 导出DLL函数
LIBRARY test_dll

EXPORTS

add_dll @ 1
此方式,确保原函数名和更名后的函数名都将导出,可以兼容不同的编译器。

4、dll的cpp

#include "test_dll.h"

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

5、dll调用

#include <stdio.h>
#include <Windows.h>

typedef int(*lpAddFun)(int, int);

int main()
{
	HINSTANCE hDll = LoadLibrary(L"..\\debug\\test_dll.dll");
	if (hDll != NULL)
	{
		lpAddFun addFun = (lpAddFun) GetProcAddress(hDll,"add_dll");
		//lpAddFun addFun = (lpAddFun) GetProcAddress(hDll,MAKEINTRESOURCEA(1));	//.def对应的数值
		if (addFun != NULL)
		{
			printf("%d",addFun(1,2));
		}
		FreeLibrary(hDll);
	}
	
	return 0;
}

三、DLLMAIN

dllMain是dll的入口程序

在DllMain中应该值进行简单的初始化操作,避免使用依赖其它DLL的函数

因为在此时,其它依赖的DllMain还是可能还没有初始化,导致调用失败

//如果程序员没有为DLL模块编写一个DLLMain函数,系统会从其它运行库中引入一个不做任何操作的
//缺省DLLMain函数版本。在单个线程启动和终止时,DLLMain函数也被调用。正如由dwReason参数所
//表明的那样。

/*
    进程中的每个DLL模块被全局唯一的32字节的HINSTANCE句柄标识进程自己还有一个HINSTANCE句柄。
所有这些模块句柄都只有在特定的进程内部有效,它们代表了DLL或EXE模块在进程虚拟空间中的起始
地址。在Win32中,HINSTANCE和HMODULE的值是相同的,这两种类型可以替换使用。进程模块句柄几乎
总是等于0x400000,而DLL模块的加载地址的缺省句柄是0x10000000。如果程序同时使用了几个DLL模
块,每一个都会有不同的HINSTANCE值。这是因为在创建DLL文件时指定了不同的基地址,或者是因为
加载程序对DLL代码进行了重定位。 
*/
BOOL APIENTRY DllMain( HANDLE hModule,    DWORD  ul_reason_for_call,  LPVOID lpReserved )
{
	switch(ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		//只会加载一次,第一次被加载时
		break;
	case DLL_THREAD_ATTACH:
		//DLL被加载中,进程中创建新的线程,会被调用
		break;
	case DLL_THREAD_DETACH:
		//DLL被加载中,进程中有线程结束,会被调用
		break;
	case DLL_PROCESS_DETACH:
		//DLL被释放,撤销映射时
		break;
	}
	return TRUE;
}

1、返回值仅对DLL_PROCESS_ATTACH有影响,对其它无影响,如果返回FALSE,表示加载失败。

如果是隐式加载DLL的话,有DLL返回FALSE会导致程序启动失败。弹出错误对话框。

2、DLL计数为0时,会从程序空间中撤销映射,发送DLL_PROCESS_DETACH通知,进行释放等操作

程序要等全部DLL释放结束后,才会终止进程,因此DLL可能会阻碍进程的终止。

调用TerminateProcess结束进程时,不会进行此通知。可能会操作数据丢失等问题。

3、进程中有线程被创建会通知所有已加载的DLL,DLL_THREAD_ATTACH消息,进行与线程相关的初始化工作。

4、进程中的线程结束后会通知所有已加载的DLL,DLL_THREAD_DETACH消息,进行与线程相关的释放工作。

调用TerminateThread结束线程时,不会进行此通知。可能会操作数据丢失等问题。

5、当加载DLL和释放DLL的函数不在一个线程的时候,可能会出现问题:

线程加载DLL时候,会发送DLL_PROCESS_ATTACH通知,线程结束后,却会发送DLL_THREAD_DETACH通知,

而此线程并没有发送过DLL_THREAD_ATTACH通知


四、导出变量

1、在dll.h中

extern int dllGlobalVar;


2、在dll.cpp中

int dllGlobalVar;

dllGlobalVar = 100; //在dll被加载时,赋全局变量为100

3、在dll.def中

LIBRARY test_dll
EXPORTS
dllGlobalVar CONSTANT
;或dllGlobalVar DATA

4、调用方式1

#pragma comment(lib,"dllTest.lib")
extern int dllGlobalVar;
int main(int argc, char *argv[])
{
	printf("%d ", *(int*)dllGlobalVar);
	*(int*)dllGlobalVar = 1;
	printf("%d ", *(int*)dllGlobalVar);
	return 0;
}


方式2

#include <stdio.h>
#pragma comment(lib,"dllTest.lib")
extern int _declspec(dllimport) dllGlobalVar; //用
_declspec(dllimport)导入
int main(int argc, char *argv[])
{
	printf("%d ", dllGlobalVar);
	dllGlobalVar = 1; //这里就可以直接使用, 无须进行强制指针转换
	printf("%d ", dllGlobalVar);
	return 0;
}

方式3

int nDllGlobalVar = *(int*)GetProcAddress(hDll, "dllGlobalVar");

五、导出类

只有当DLL模块的编译器和调用DLL程序的编译器相同时,才使用导出类。

或者知道两者使用的是相当的工具包,否则应该避免使用

#pragma once

#ifdef TEST_DLL
#define TEST _declspec(dllexport)  //导出类
#else
#define TEST _declspec(dllimport) //导入类
#endif

class TEST CTestDll
{
public:
	void foo();
};

#ifndef TEST_DLL
#define TEST_DLL
#endif

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

void CTestDll::foo()
{
	printf("foo");
}

#include <stdio.h>
#include "../test_dll/TestDll.h"
#pragma comment(lib, "../debug/test_dll.lib")

int main()
{
	CTestDll dll;
	dll.foo();
	
	return 0;
}

使用预定义宏的好处。在dll实现文件中,定义了TEST_DLL 这样在dll中调用的就是_declspec(dllexport) 导出

而在调用程序中,没有定义宏,所以调用_declspec(dllimport)导入了类

六、已知的DLL

在注册表HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs

存放一些系统特殊处理的DLL,优先匹配的DLL文件名称。

其中DllDirectory为对应默认的DLL查找地址。

当用户调用loadLibrary("user32");时,

系统会将user32在这个注册表项中查找与之对应的值,在DllDirectory对应的地址下查找。


如果估计将DLL对应名称指定,比如,将注册表中的user32对应的对应的值改为“A.dll”

那么用户在调用loadLibrary("user32")时,加载的并不是user32.dll,而是a.dll




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值