以下开发环境为visual studio中。
常规dll应该配备的3类文件:
1.dll文件:必备
2.lib文件:非必备。含有dll函数入口信息,用于隐式加载,具体看下面贴的博客
3.头文件.h:非必备。在头文件中写好声明(因此是隐式加载),一来方便给人看有哪些东西,二来方便别人用时不再用写一大堆声明。
文件的摆放位置
显式加载:只需要把dll放到与可执行文件同一目录下即可
隐式加载:把dll和lib放到项目文件夹下。或者“右键项目属性--链接器--常规--附加库目录”中添加dll和lib所在文件夹
不重复打,右转大佬博客:C++编程笔记:dll的生成与使用
总的意思就是写dll时声明定义前加
extern "C" __declspec(dllexport)
(或用.def文件),使用时加extern "C" __declspec(dllimport)
这里提一下,和大佬博客交互来看:
1.__declspec
是关键字,用于表示该函数、变量是导出、导入的,括号里dllexport
意为其将要导出,dllimport
意为其将要导入。
2.extern "C"
用于指定编译器编译后的函数别名,这样使用时才能正确查找到。即对于变量extern int a;
这样的直接写为extern "C" int a;
即可,函数同理。
3.(.def)文件的语法:entryname[=internalname] [@ordinal [NONAME]] [[PRIVATE] | [DATA]]
方括号内为可选项,详细右转msdn
4.共享出去给别人的头文件通常有这样一段(宏变量名随意):
#ifdef AAA
#define BBB __declspec(dllexport)
#else
#define BBB __declspec(dllimport)
#endif
extern "C" BBB int method();
这样效果:写dll的人先定义宏AAA,编译时函数method当导出,使用dll的人没有定义AAA,于是函数method为导入,方便写好头文件直接共享出去,不用多作修改。
5.在vs中,如果使用了非空项目,vs将自动生成如上面说的“AAA”,并添加到预编译中。该设置保存在.vcxproj类型的文件中,位置在该文件的PreprocessorDefinitions标签中,并且有4个,分别对应debug、release和x86、x64的组合。想要修改请可以直接去该文件修改相应宏的名字
6.博客中显式加载typedef int(*SUBPROC)(int a, int b);
,意思为定义一个类型SUBPROC,该类型对应dll的函数原形,但仍然需要强制转换。
7.使用时,声明仍然需要添加extern "C"
在开头,博客中声明外部函数时没有添加,将导致外部符号找不到连接(实际目的就是要使两个项目的函数别名对应起来。若其中一个交由编译器自主处理可能造成别名不一致)
8.如何去获取dll中的类的实例?
假定:把类class A声明写在头文件,实现写在cpp。
写一个函数A* getA(){ return new A(); }
,然后如上所述导出导入该函数去获取就是。
9.DllMain函数是什么回事?
dll的函数入口,不写系统自动帮你搞,但有可能出bug。
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;
}
APIENTRY:API ENTRY,api入口,就这个意思
HMODULE:实际上typedef HINSTANCE HMODULE
,现在32、64位系统上与HINSTANCE没有区别
DWORD:调用dll时系统传入的变量类型。看上面4个case的英文,用途是当进程(线程)加载(卸载)该dll时你可以让该dll做一些额外的(初始化、回收指针等)操作。
LPVOID :待定,保留待以后使用(就是目前没有用)