一、建立静态链接库.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;
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;
}
#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)导入了类
在注册表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