编写一个dll文件

使用记事本创建一个cpp文件,名为dll_nolib.cpp:

代码如下:

#include <objbase.h>
#include <iostream>

BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void *lpReserved)
{
	HANDLE g_hModule;
	switch (dwReason)
	{
	case DLL_PROCESS_ATTACH: //表示dll刚刚被加载到一个进程中
		std::cout << "Dll is attached!" << std::endl;
		g_hModule = (HINSTANCE)hModule;

		break;

	case DLL_PROCESS_DETACH: //表示dll刚刚从一个进程中卸载
		std::cout << "Dll is detached!" << std::endl;
		g_hModule = NULL;
		break;
	}

	return true;
}


1.在管理员权限下使用Developer Command Prompt for VS2013编译该文件,并生成中间文件:dll_nolib.obj文件:



2.将该obj文件链接生成dll文件:



加载dll(显示调用)

dll的使用分两种方式,显式调用和隐式调用。显式调用代码如下,创建一个名为dll_nolib_client.cpp的文件:

#include <windows.h>
#include <iostream>

int main(void)
{
	//加载我们的dll
	HINSTANCE hinst = ::LoadLibrary("dll_nolib.dll");
	
	if (NULL != hinst)
	{
		std::cout << "dll loaded!" << std::endl;
	}

	return 0;
}


编译执行:




以上是仅仅将dll加载到内存中,但是无法找到dll中的函数。

使用dumpbin命令可查看dll中的函数,可看到dll_nolib.dll中并没有函数:



在dll中定义输出函数有两种方法:

一是添加一个def定义文件,在此文件中定义dll中要输出的函数;

二是在源代码中待输出的函数前加上__declspec(dllexport)关键字;


在def文件中添加的情况:

写一个带有输出函数的dll,创建名为dll_def.cpp的文件,如下:

#include <objbase.h>
#include <iostream>

extern "C" __declspec(dllexport) void FuncInDll(void)
{
	std::cout << "FuncInDll is called!" << std::endl;
}

BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)
{

	HANDLE g_hModule;

	switch (dwReason)
	{
	case DLL_PROCESS_ATTACH:
		g_hModule = (HINSTANCE)hModule;
		break;

	case DLL_PROCESS_DETACH:
		g_hModule = NULL;
		break;
	
	}

	return TRUE;
}


再写一个dll的def文件,dll_def.def:

 1. 关键字LIBRARY,指定dll名字;

 2.一个可选的关键字DESCRIPTION,写上版权等信息;

3. EXPORT关键字,写上dll中所有要输出的函数名或变量名,接上@以及依次编号的数字(从1到N),若有第2个输出函数FuncInDll2,则在下一行写上FuncInDll2 @2 PRIVATE即可,则最后接上修饰符。如下:

;
;dll_def module-definition file
;
LIBRARY dll_def.dll
DESCRIPTION '(c)2011-2017 stanway'
EXPORTS
	FuncInDll @1 PRIVATE



编译dll_def.cpp,生成dll_def.obj文件:



链接dll_def.obj与dll_def.def文件生成dll_def.dll:



使用dumpbin查看生成的dll_def.dll文件,发现该dll输出了函数FuncInDll:



再来显式调用一下上面dll中的函数,创建文件名为dll_def_client.cpp的文件;

1.函数指针的声明

2.GetProcAddress的用来查找dll中的函数地址,第一个参数是dll的句柄,即LoadLibrary返回的句柄;第二个参数是dll中函数的名字,即dumpbin中输出的函数名(该函数名指的是编译后的函数名,不一定等于dll源代码中的函数名)


如下代码:

#include <windows.h>
#include <iostream>

int main(void)
{
	//定义一个函数指针
	typedef void (* DLLWITHLIB)(void);

	//定义一个函数指针变量
	DLLWITHLIB pfFuncInDll = NULL;

	//加载我们的dll
	HINSTANCE hinst = ::LoadLibrary("dll_def.dll");

	if (NULL != hinst)
	{
		std::cout << "dll loaded!" << std::endl;
	}

	//找到dll的FuncInDll函数
	pfFuncInDll = (DLLWITHLIB)GetProcAddress(hinst, "FuncInDll");

	//调用dll里的函数
	if (NULL != pfFuncInDll)
	{
		(*pfFuncInDll)();
	}

	return 0;
}

编译执行后:




使用__declspec(dllexport)在源代码中定义dll的输出函数:

写法同上,去掉def文件,在每个要输出的函数前面加上声明__declspec(dllexport),如下:




编译链接dumpbin查看如下即可:



编译后的函数名为 ?FuncInDll@@YAXXZ,而非FuncInDll。这是由于C++编译器基于函数重载的考虑,会更改函数名,若是这样,在使用显式调用的时候,也必须使用这个更改后的函数名,这会带来很大的麻烦。为了避免这种现象,使用extern "C"指令来命令C++编译器以C编译器的方式来命名该函数,因此修改后的函数为:






再次编译链接查看dll输出函数,看看到显示的名字正常了,这样之后,显式调用时只需要用FuncInDll函数名就可以了,实际上extern 'c'相当于一个编译开关,它可将C++函数编译为C函数的名字,保持编译后的函数符号等于源代码中的函数名:




隐式调用dll

显式调用每次都要调用LoadLibrary,且每个函数都必须使用GetProcAddrss来得到函数指针,对大量使用dll函数会造成困扰。然而,使用隐式调用能够像使用C函数库一样使用dll中的函数,非常方便。

创建两个文件dll_withlibAndH.cpp和dll_withlibAndH.h:

dll_withlibAndH.h:

extern "C" __declspec(dllexport) void FuncInDll(void);

dll_withlibAndH.cpp:

#include <objbase.h>
#include <iostream>

#include "dll_withLibAndH.h"

extern "C" __declspec(dllexport) void FuncInDll(void)
{
	std::cout << "FuncInDll is called!" << std::endl;
}

BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)
{
	HANDLE g_hModule;
	switch (dwReason)
	{
	case DLL_PROCESS_ATTACH:
		g_hModule = (HINSTANCE)hModule;
		break;

	case DLL_PROCESS_DETACH:
		g_hModule = NULL;
		break;
	}

	return TRUE;
}


再写一个客户端程序,dll_withlibAndH_client.cpp,里面只要包含dll_withlibAndH.h文件即可引用函数:

#include "dll_withlibAndH.h"

#pragma comment(lib, "dll_withLibAndH.lib")
int main(void)
{
	FuncInDll();
	return 0;
}

编译后得到:




上面的这种隐式调用方法很不错,但是在调用dll中的对象和重载函数时会出现问题,因为使用extern 'C'修饰输出函数后, 重载函数肯定会出现问题的,因为它们都被编译为同一个输出符号了(C语言不支持重载)。


实际上,不使用extern 'C'也是可行的,这时的函数会被编译为C++字符串,如   ?FuncInDll@@YAXH@Z,?FuncInDll@@YAXXZ这些。当客户端也使用C++时,也是能正确的进行隐式调用的。


配对使用__declspec(dllexport)以及__declspec(dllimport):

这时要考虑一个情况:若dll1.cpp是源,dll2.cpp使用dll1中的函数,但同时dll2也是一个dll,也要输出一些函数供client.cpp使用。那么在dll2中要如何声明所有的函数呢?它里面既包含了从dll1中引入的函数,又包含了自己要输出的函数。此时就需要使用关键字__declspec(dllexport)__declspec(dllimport)了。前者用来修饰本dll中的输出函数,后者用来修饰从其他dll中引入的函数。


为了验证这个问题,包括源代码dll1.h,dll1.cpp,dll2.h,dll2.cpp,client.cpp:

dll1.h:

#ifdef DLL_DLL1_EXPORTS
#define DLL_DLL1_API __declspec(dllexport)
#else
#define DLL_DLL1_API __declspec(dllimport)
#endif

DLL_DLL1_API void FuncInDll1(void);
DLL_DLL1_API void FuncInDll1(int);

dll1.cpp:

#define DLL_DLL1_EXPORTS

#include <objbase.h>
#include <iostream>
#include "dll1.h"

DLL_DLL1_API void FuncInDll1(void)
{
	std::cout<<"FuncInDll1 is called!"<<std::endl;
}

DLL_DLL1_API void FuncInDll1(int a)
{
	std::cout<<"FuncInDll1 is called! input param = "<<a<<std::endl;
}

BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved) 
{
	HANDLE g_hModule;
	switch(dwReason)
	{
	case DLL_PROCESS_ATTACH:
		g_hModule = (HINSTANCE)hModule;
		break;
	case DLL_PROCESS_DETACH:
		 g_hModule=NULL;
		 break;
	}

	return TRUE;
}


dll2.h:

#include"dll1.h"

#ifdef DLL_DLL2_EXPORTS
#define DLL_DLL2_API __declspec(dllexport)
#else
#define DLL_DLL2_API __declspec(dllimport)
#endif

DLL_DLL2_API void FuncInDll2(void);
DLL_DLL2_API void FuncInDll2(int);

dll2.cpp:

#define DLL_DLL2_EXPORTS

#include <objbase.h>
#include <iostream>
#include "dll2.h"

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

DLL_DLL2_API void FuncInDll2(void)
{
	FuncInDll1();
	std::cout<<"FuncInDll2 is called!"<<std::endl;
}

DLL_DLL2_API void FuncInDll2(int a)
{
	FuncInDll1(10);
	std::cout<<"FuncInDll2 is called! input param = "<<a<<std::endl;
}

BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved) 
{
	HANDLE g_hModule;
	switch(dwReason)
	{
	case DLL_PROCESS_ATTACH:
		g_hModule = (HINSTANCE)hModule;
		break;
	case DLL_PROCESS_DETACH:
		 g_hModule=NULL;
		 break;
	}

	return TRUE;
}

client.cpp:

#include "dll2.h"
#include <iostream>
//注意路径,加载 dll的另一种方法是 Project | setting | link 设置里
#pragma comment(lib,"dll2.lib")
#pragma comment(lib,"dll1.lib")

int main(void)
{
	std::cout<<"call dll"<<std::endl;
	FuncInDll1();//只要这样我们就可以调用dll里的函数了
	FuncInDll1(5);
	
	FuncInDll2();
	FuncInDll2(13);

	return 0;
}


一一编译并执行:



此时可看到:



dll中的全局变量和对象

需要注意语法问题,创建三个问文件,dll_object.h,dll_object.cpp, dll_object_client.cpp中:


dll_object.h:

#ifdef DLL_OBJECT_EXPORTS
#define DLL_OBJECT_API __declspec(dllexport)
#else
#define DLL_OBJECT_API __declspec(dllimport)
#endif

DLL_OBJECT_API void FuncInDll(void);

extern DLL_OBJECT_API int g_nDll;

class DLL_OBJECT_API CDll_Object {
public:
	CDll_Object(void);
	void show(void);
	// TODO: add your methods here.
};


dll_object.cpp:

#define DLL_OBJECT_EXPORTS 
#include <objbase.h>
#include <iostream>
#include "dll_object.h"

DLL_OBJECT_API void FuncInDll(void)
{
	std::cout<<"FuncInDll is called!"<<std::endl;
}

DLL_OBJECT_API int g_nDll = 9;

CDll_Object::CDll_Object()
{
	std::cout<<"ctor of CDll_Object"<<std::endl;
}
	
void CDll_Object::show()
{
	std::cout<<"function show in class CDll_Object"<<std::endl;
}

BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved) 
{
	HANDLE g_hModule;
	switch(dwReason)
	{
	case DLL_PROCESS_ATTACH:
		g_hModule = (HINSTANCE)hModule;
		break;
	case DLL_PROCESS_DETACH:
		 g_hModule=NULL;
		 break;
	}

	return TRUE;
}


编译链接查看dll文件显示可知,从上往下分别为:类CDll_Object,类的构造函数,函数FuncInDll,全局变量g_nDll和类成员函数show:




dll_object_client.cpp:

#include "dll_object.h"
#include <iostream>
//注意路径,加载 dll的另一种方法是 Project | setting | link 设置里
#pragma comment(lib,"dll_object.lib")

int main(void)
{
	std::cout<<"call dll"<<std::endl;
	std::cout<<"call function in dll"<<std::endl;
	FuncInDll();//只要这样我们就可以调用dll里的函数了
	std::cout<<"global var in dll g_nDll ="<<g_nDll<<std::endl;
	std::cout<<"call member function of class CDll_Object in dll"<<std::endl;
	CDll_Object obj;
	obj.show();

	return 0;
}


编译链接,显示可知在客户端成功访问了dll中的全局变量,并创建了dll中定义的C++对象,还调用了该对象的成员函数:



总结:

1.DLL是对应C语言的动态链接技术,在输出C函数和变量时很方便快捷;而在输出C++类,函数时需使用各种手段,而且解决方案还是不完美,除非客户端也使用C++。

2.只有COM是对应C++语言的技术

3.在只有一个调用的时候,使用显式调用较为合理,就是当客户端不是C/C++时,这时无法使用隐式调用的,例如用VB来调用C++写的dll

4.def的功能实际上是相当于extern 'C' __declspec(dllexport)的,所以它也是仅能处理C函数,而不能处理重载函数,而__declspec(dllexport)和__declspec(dllimport)的配合使用可以在任何情况下使用,因此不推荐用def。

5.从其他语言调用dll,有两大问题:

一是函数符号问题,若使用extern 'c',则函数名保持不变,调用较为方便,但不支持函数重载等一系列的C++功能;若不使用extern 'C',则调用前要查编译后的符号,不方便。

二是函数调用压栈顺序问题,即__cdecl和__stdcall问题。__cdecl是常规的C/C++调用约定,这种调用约定下,函数调用后栈的清理工作是由调用者来完成的。__stdcall是标准的调用约定,这种调用约定下,函数会在返回到调用者之前就将参数从栈中删除,即由被调用者清理栈。

这两个问题dll都不能很好的解决,但是在COM中,都能完美的解决。所以,要在Windows平台实现语言无关性,只能使用COM中间件。

因此,除非客户端使用C++,否则dll是不便于支持函数重载,类等C++特性的。dll对C函数支持很好,这也就是为什么windows的函数库使用C加dll实现的理由吧。


参考该网址,感谢大牛。

为何DLL 先看看静态库与DLL的不同之处 可执行文件的生成(Link期):前者很慢(因为要将库中的所有符号定义Link到EXE文件中),而后者很快(因为后者被Link的引入库文件无符号定义) 可执行文件的大小:前者很大,后者很小(加上DLL的大小就和前者差不多了) 可执行文件的运行速度:前者快(直接在EXE模块的内存中查找符号),后者慢(需要在DLL模块的内存中查找,在另一个模块的内存中查找自然较慢) 可共享性:前者不可共享,也就是说如果两个EXE使用了同一个静态库,那么实际在内存中存在此库的两份拷贝,而后者是可共享的。 可升级性:前者不可升级(因为静态库符号已经编入EXE中,要升级则EXE也需要重新编译),后者可以升级(只要接口不变,DLL即可被升级为不同的实现) 综合以上,选择静态库还是DLL 1. 静态库适于稳定的代码,而动态库则适于经常更改代码(当然接口要保持不变),当DLL更改(仅实现部分)后,用户不需要重编工程,只需要使用新的Dll即可。 2. 由于静态库很吃可执行文件的生成(Link期)时间,所以如果对可执行文件的Link时间比较敏感,那么就用DLL。 使用DLL 在介绍如何创建DLL之前,让我们先了解它是如何被使用的。 1. 显式调用(也叫动态调用) 显 示调用使用API函数LoadLibrary或者MFC提供的AfxLoadLibrary将DLL加载到内存,再用GetProcAddress()在 内存中获取引入函数地址,然后你就可以象使用本地函数一样来调用此引入函数了。在应用程序退出之前,应该用FreeLibrary或MFC提供的 AfxLoadLibrary释放DLL。 下面是个显示调用的例子,假定你已经有一个Test.dll,并且DLL中有个函数名为Test,其声明式是void(); #include < iostream > using namespace std; typedef void(*TEST )(); int main( char argc, char* argv[] ) { const char* dllName = "Test.dll"; const char* funcName = "Test"; HMODULE hDLL = LoadLibrary( dllName ); if ( hDLL != NULL ) { TEST func = TEST( GetProcAddress( hDLL, funcName ) ); if ( func != NULL ) { func(); } else { cout << "Unable to find function \'" << funcName << "\' !" << endl; } FreeLibrary( hDLL ); } else { cout << "Unable to load DLL \'" << dllName << "\' !" << endl; } return 0; } 注意 1. 显示调用使用GetProcAddress,所以只能加载函数,无法加载变量和类。 2. 此外GetProcAddress是直接在.dll文件中寻找同名函数,如果DLL中的Test函数是个C++函数,那么由于在.dll文件中的实际文件名会被修饰(具体被修饰的规则可参考函数调用约定详解或者使用VC自带的Depends.exe查看),所以直接找Test是找不到的,这时必须把函数名修改为正确的被修饰后的名称,下面是一种可能(此函数在DLL中的调用约定为__cdecl): const char* funcName = "?Test@@YAXXZ"; 2. 隐式调用(也叫静态调用) 隐式调用必须提供DLL的头文件和引入库(可以看作轻量级的静态库(没有符号定义,但是说明了符号处于哪个DLL中))。 有了头文件和引入库,DLL的使用就跟普通静态库的使用没啥区别,只除了DLL要和EXE一起发布。 显示调用与隐式调用的优缺点 显示调用使用复杂,但能更加有效地使用内存,因为DLL是在EXE运行时(run time)加载,必须由用户使用LoadLibrary和FreeLibrary来控制DLL从内存加载或卸载的时机。此外还可以加载其他语言编写DLL函数。 静态调用使用简单,但不能控制DLL加载时机,EXE加载到内存同时自动加载DLL到内存,EXE退出时DLL也被卸载。 创建DLL 下面我们着重讲解如何在VC下创建DLL 首先要建立一个Win32的DLL工程。再把普通静态库的所有文件转移到DLL工程中,然后: 为所有的类声明加上__declspec(dllexport)关键字,这有这样编译器才能自动为你产生引入库文件(否则你需要自己.def文件来产生,因为不常用所以在此不再阐述其细节) 对于DLL的用户来讲,类声明就需要用另外一个关键字__declspec(dllimport)(此关键字对于类和函数而言并非必须,但对于变量而言则是必须的)。所以通常我们会定义一个宏来包装之,比如 #ifdef MYDLL_EXPORTS # define MYDLL_API __declspec(dllexport) #else # define MYDLL_API __declspec(dllimport) #endif 这样我们就能出如下的类 class MYDLL_API MyClass { ... }; 当然在创建DLL的工程里需要包含preprocessor(预处理指示器)MYDLL_EXPORTS,而在用户工程里面则不应该包含MYDLL_EXPORTS。 其实上面说的VC早就帮我们做好了。如果你创建的DLL工程叫做Test,那么此工程自动包含TEST_EXPORTS。如果你在创建工程的时候选择了Exprot Symbols, 那么VC还会自动帮你创建一个示例文件Test.h,此文件定义出 #ifdef TEST_EXPORTS # define TEST_API __declspec(dllexport) #else # define TEST_API __declspec(dllimport) #endif 你自定义的文件都应该包含此文件以使用TEST_API。(如果没有选择Exprot Symbols,那么就得自己动手出这段宏了) 示例文件中还包括了一个类,变量,以及全局函数的法 class TEST_API CTest { public: CTest(void); // TODO: add your methods here. }; extern TEST_API int nTest; TEST_API int fnTest(void); 通过上面的示例我们也可以看出全局(或者名字空间)变量和函数的声明方法 细节讨论 1. DLL的入口函数DllMain并非必须,如果没有它,编译器会帮你自动生成一个不做任何事的DllMain。 2. 如果是可以定义在头文件里面的东西:包括宏,常量,内联函数(包括成员内联函数)以及模板。那么在DLL中的定义中可以不必包含TEST_API,和普通的定义没有区别。 如果一个类完全由内联函数和纯虚函数构成,那么也不需要TEST_API这样的关键字。一个不带成员函数的struct也一样。 3. 所有未经__declspec(dllexport)导出的类都只能作为DLL内部类使用。当然外部仍然可以使用其内联成员函数(前提是该成员函数不应该调用任何未经导出的函数或者类成员函数) 发布DLL 1. 程序库的作者应该将三件套:头文件,引入库文件DLL一同发布给用户,其中头文件和引入库是专为静态调用的用户准备,也就是C/C++用户。(此外有些 DLL内部使用的头文件,如果没有被接口头文件直接#include,那么该头文件就不必发布,这和静态库是一样的)。 2. DLL的用户只需将DLL和可执行程序一同发布即可。 C++程序使用C语言DLL(静态库规则一样) C不存在class, 所以由C创建的DLL必然也不会出现class;对于全局变量的使用C和C++没有什么不同,所以我们把焦点集中在全局函数上(此外全局变量的规则一样)。 我们知道C++程序要使用C语言的函数就必须在函数声明前加上extern "C"关键字,这对于静态库和DLL没有什么不同。 但是这个关键字不能直接出现在头文件函数声明中,否则DLL无法通过编译, 原因很简单,C语言并没有extern "C"这个关键字。 1. 一种作法是把C向C++迁移的责任交给DLL创建者。定义出一个宏,以使DLL(C工程)中不出现extern "C"或者只是extern,而在用户工程(C++工程)中保持原样。幸运的是windows早已为我们设计好一切,这个宏就是EXTERN_C(存在于 winnt.h中) : #ifdef __cplusplus #define EXTERN_C extern "C" #else #define EXTERN_C extern #endif 注意上面必须是extern而不是空。因为虽然C的函数声明不是必须添加extern,但是变量声明则必须添加extern。 有了EXTERN_C,在头文件中这样定义函数: EXTERN_C TEST_API int fnTest(void); 2. 另外一种做法是把把C向C++迁移的责任交给用户,用户在包含DLL文件的时候以extern "C"来include: extern "C" { #include "mydll.h" #include "mydllcore.h" ... } 可以把上述的extern "C"段放在新的头文件中和DLL一起发布,这样C++用户只需要包含这个头文件就可以了。比如Lua库就自带了一个etc/lua.hpp文件。通常此文件会由DLL作者提供,所以此时迁移的责任仍在DLL创建者。 注意用户不要试图以extern "C"来重新声明函数,因为重复声明是允许的,但是必须保证和头文件中的声明相同。 3. 这种做法的一个变形就是直接在C头文件中以extern "C"将所有函数和变量声明包含之,这样就无需像上面那样多提供一个额外的头文件了。通常像这样(mydll文件): #include 头文件段 #ifdef __cplusplus extern "C" { #endif 函数和变量声明 ... #ifdef __cplusplus } #endif 创建标准的DLL,使其可被其他语言使用 通常我们会希望DLL能够被其他语言使用,因而我们的DLL往往不会提供类定义,而只提供函数定义。(因为许多编程语言是不支持类的)。 此时函数的调用约定必须是__stdcall(在vc下默认是__cdecl,所以你不得不手工添置),因为大部分语言不支持__cdecl,但支持__stdcall(比如VBScript,Delphi等)。 此外我们希望导出到DLL中的函数名能够更易被识别(用户使用才会更方便),也就是说DLL应该编译出无修饰的C函数名。 所以我们可能会 1. 如果你只用C语言,那么必然以C文件创建DLL(自动编出C符号名),考虑到潜在的C++用户(此类用户多以静态调用方式使用DLL,因而需要看到其函数声明),我们还需要使用EXTERN_C关键字(详见上面的讨论)。 2. 如果你使用C++,那么必然以C++文件创建DLL,这时你应该直接以extern "C"修饰。 结论 所以要创建能够为任意编程语言使用之DLL,我们应该 1. 只创建函数 2. 声明__stdcall调用约定(或者WINAPI,CALLBACK,前提是你必须包含windows头文件) 3. 以CPP文件 + extern "C" 或者 C文件 + EXTERN_C 4. 当然还需要DLL_API的声明(如果光有dllexport,那么DLL也只能被显示调用)。 更完美一点 不应该使用dllexport和extern "C"而应该使用.def文件。虽然经过上面的4个步骤,我们的DLL里面的C函数名已经相当简洁了,但仍不是最完美的:比如一个声明为function的函数,实际在DLL中的名字确可能是function@#。而使用.def文件,我们可以让DLL中的函数名和声明的函数名保持一致。 关于.def的详细用法可参考: 微软DLL专题 使用 DEF 文件DLL 导出 在DLL与静态库之间切换 前面我曾经提到对于使用DLL的用户__declspec(dllimport)关键字可有可无,当然前提是DLL中不包括变量定义。 所以要把库既能够做成DLL,也能够做成静态库,那么就应该作出类似下面这样的定义: 1. DLL不包括变量的定义 #ifdef TEST_EXPORTS # define TEST_API __declspec(dllexport) #else # define TEST_API #endif 然 后只要把工程的配置属性(Configuration Type)简单地在Static Library (.lib)或者Dynamic Library (.dll)切换即可(VC会自动地为DLL添加预处理器TEST_EXPORTS,为静态库取消TEST_EXPORTS)。 2. DLL包含变量定义,也是标准的做法 #ifdef TEST_BUILD_AS_DLL #ifdef TEST_EXPORTS # define TEST_API __declspec(dllexport) #else # define TEST_API __declspec(dllimport) #endif #else #define TEST_API #endif 如果要将库做成DLL,那么需要DLL创建者添加预处理器TEST_BUILD_AS_DLL和TEST_EXPORTS,后者通常由编译器自动添加;如果做成静态库则不需要添加任何预处理器。 用户则可以通过添加或取消TEST_BUILD_AS_DLL来使用动态库或静态库。 对于DLL的用户,TEST_BUILD_AS_DLL这个名称似乎起得不好。下面的做法或许会更合理些: #if defined(TEST_API) #error "macro alreday defined!" #endif #if defined(TEST_BUILD_DLL) && defined(TEST_USE_DLL) #error "macro conflict!" #endif #if defined(TEST_BUILD_DLL) // build dll # define TEST_API __declspec(dllexport) #elif defined(TEST_USE_DLL) // use dll # define TEST_API __declspec(dllimport) #else // build or use library, no preprocessor needs # define TEST_API #endif 相关链接 微软DLL专题 Windows API编程之动态链接库(DLL
最简单的dll并不比c的helloworld难,只要一个DllMain函数即可,包含objbase.h头文件(支持COM技术的一个文件)。若你觉得这个头文件名字难记,那么用windows.H也可以。源代码如下:dll_nolib.cpp #include #include BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved) { HANDLE g_hModule; switch(dwReason) { case DLL_PROCESS_ATTACH: cout<<"Dll is attached!"<<endl; g_hModule = (HINSTANCE)hModule; break; case DLL_PROCESS_DETACH: cout<<"Dll is detached!"<<endl; g_hModule=NULL; break; } return true; } 其中DllMain是每个dll的入口函数,如同c的main函数一样。DllMain带有三个参数,hModule表示本dll的实例句柄(听不懂就不理它,过windows程序的自然懂),dwReason表示dll当前所处的状态,例如DLL_PROCESS_ATTACH表示dll刚刚被加载到一个进程中,DLL_PROCESS_DETACH表示dll刚刚从一个进程中卸载。当然还有表示加载到线程中和从线程中卸载的状态,这里省略。最后一个参数是一个保留参数(目前和dll的一些状态相关,但是很少使用)。 从上面的程序可以看出,当dll被加载到一个进程中时,dll打印"Dll is attached!"语句;当dll从进程中卸载时,打印"Dll is detached!"语句。 编译dll需要以下两条命令: cl /c dll_nolib.cpp 这条命令会将cpp编译为obj文件,若不使用/c参数则cl还会试图继续将obj链接为exe,但是这里是一个dll,没有main函数,因此会报错。不要紧,继续使用链接命令。 Link /dll dll_nolib.obj 这条命令会生成dll_nolib.dll。 注意,因为编译命令比较简单,所以本文不讨论nmake,有兴趣的可以使用nmake,或者个bat批处理来编译链接dll。 加载DLL(显式调用)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值