编写、调用DLL的步骤

       比较大的应用程序都由很多模块组成,这些模块分别完成相对独立的功能,它们彼此协作来完成整个软件系统的工作。其中可能存在一些模块的功能较为通用,在构造其它软件系统时仍会被使用。在构造软件系统时,如果将所有模块的源代码都静态编译到整个应用程序EXE文件中,会产生一些问题:一个缺点是增加了应用程序的大小,它会占用更多的磁盘空间,程序运行时也会消耗较大的内存空间,造成系统资源的浪费;另一个缺点是,在编写大的EXE程序时,在每次修改重建时都必须调整编译所有源代码,增加了编译过程的复杂性,也不利于阶段性的单元测试。 

  Windows系统平台上提供了一种完全不同的较有效的编程和运行环境,你可以将独立的程序模块创建为较小的DLL(Dynamic Linkable Library)文件,并可对它们单独编译和测试。在运行时,只有当EXE程序确实要调用这些DLL模块的情况下,系统才会将它们装载到内存空间中。这种 方式不仅减少了EXE文件的大小和对内存空间的需求,而且使这些DLL模块可以同时被多个应用程序使用。Microsoft Windows自己就将一些主要的系统功能以DLL模块的形式实现。例如IE中的一些基本功能就是由DLL文件实现的,它可以被其它应用程序调用和集成。

        底层库提供的是一种机制,而不是满足某一个人的策略。

   一般来说,DLL是一种磁盘文件(通常带有DLL扩展名),它由全局数据、服务函数和资源组成(说白了,就是一堆功能函数,有的是为了自己需要,有的是需要提供给外部使用——_declspec(dllexport)抛出去),在运行时被系统加载到进程的虚拟空间中,成为调用进程的 一部分。如果与其它DLL之间没有冲突,该文件通常映射到进程虚拟空间的同一地址上。DLL模块中包含各种导出函数,用于向外界提供服务。Windows 在加载DLL模块时将进程函数调用与DLL文件的导出函数相匹配。

编写动态链接库文件时,不要有中文!动态链接库工程其实就是一套API函数(可以被外部程序使用)的集成!

动态库工程会提供让函数导出来以便其他应用、工程使用的机制。在DLL代码中,必须像下面这样明确声明导出函数:_declspec(dllexport) int MyFunction(int n);

在开发调试阶段,每次对DLL工程的更新以后,一定要将新的.lib 文件和 .dll文件重新拷贝到外部应用程序的目录下。为了方便别人使用,使使用者对dll中的函数一目了然,还要编写一个.h文件,注明要抛出来的函数。

简述一下win32环境的DLL:

Debug对应Debug版本,Release对应Release版本,一定要注意版本、路径、配置动态库等对应。
1、编写win32程序下的DLL工程(注意:添加新项目一定要添加的是.c文件),并生成解决方案,在debug目录下找到如下文件并拷贝:

#include"stdio.h"
#include"stdlib.h"

_declspec(dllexport)
int add(int a, int b)
{
	return a + b;
}

2、另外新建项目,编写测试DLL工程:
#include"stdio.h"
#include"stdlib.h"

void main()
{
	printf("%d\n", add(5, 3));
}
把上述拷贝的文件粘贴在 以上.c文件的同级目录下。

3、右键测试工程——属性——连接器——输入——

编写、调用DLL的步骤_第1张图片
注意:填的是.lib文件的全名,不是.dll的全名。


一个较为复杂的socketdll.c展示:(里面含有很多其他的技术细节)

#define  _CRT_SECURE_NO_WARNINGS 
#include"stdlib.h"
#include"string.h"
#include"stdio.h"

//理解handle句柄(底层动态库的一套资源)的概念
typedef struct SCK_HANDLE
{
	char version[64];
	char ip[128];
	int port;
	char *p;      //结构体套一级指针,注意内存的释放问题
	int plen;
}SCK_HANDLE;//动态库过程内部的数据类型,不想让(也不需要)外部上层应用测试程序使用
//数据类型的封装

//------------------第一套API接口---Begin--------------------------------//

//客户端初始化——获取handle——handle是底层库的资源,记录着函数运行的上下文信息
_declspec(dllexport)
int cltSocketInit(void **handle /*out*/)
{
	printf("cltSocketInit begin\n");
	int ret = 0;

	SCK_HANDLE  *phandle=NULL;    //SCX_HANDLE是动态库工程的变量,所以要用malloc,要在堆上分配
	phandle = (SCK_HANDLE *)malloc(sizeof(SCK_HANDLE));
	if (phandle == NULL)
	{
		printf("func cltSocketInit err:%d\n",ret);
		return ret;
	}
	memset(phandle, 0, sizeof(SCK_HANDLE));    //把指针所指向的内存空间 赋值成 0;

	strcpy(phandle->ip, "192.168.1.128");
	phandle->port = 8081;

	*handle = phandle;   //间接赋值,把形参甩出去

	printf("cltSocketInit end\n");
	return 0;
}

//客户端发报文
_declspec(dllexport)
int cltSocketSend(void *handle /*in*/, unsigned char *buf /*in*/, int buflen /*in*/)
{
	int ret = 0;
	SCK_HANDLE *phandle = NULL;
	phandle = (SCK_HANDLE *)handle;

	phandle->p = (char *)malloc(buflen*sizeof(char));
	if (phandle == NULL)
	{
		ret = -1;
		printf("cltSocketSend err:%d\n", ret);
		return ret;
	}

	memcpy(phandle->p, buf, buflen);  //别人传过来的有可能不是C风格的字符串,所以按内存块拷贝
	phandle->plen = buflen;
	
	return 0;
}

//客户端收报文
_declspec(dllexport)
int cltSocketRev(void *handle /*in*/, unsigned char *buf /*in*/, int *buflen /*in out*/)
{
	int ret = 0;
	SCK_HANDLE *phandle = NULL;
	phandle = (SCK_HANDLE *)handle;

	memcpy(buf, phandle->p, phandle->plen);    //别人传过来的有可能不是C风格的字符串,所以按内存块拷贝
	buflen = phandle->plen;

	return 0;
}

//客户端释放资源
_declspec(dllexport)
int cltSocketDestory(void *handle/*in*/)
{
	int		ret = 0;
	SCK_HANDLE  *hdl = NULL;
	hdl = (SCK_HANDLE *)handle;

	if (hdl->p)   //先释放结构体 成员域的 指针 所指向的内存空间
	{
		free(hdl->p);
	}
	free(hdl);  //再释放整个结构体内存

	return 0;
}

//------------------第一套api接口---End-----------------------------------//

底层库分配的资源,一定要调用底层库的释放资源函数来释放资源,不要在主函数中简单的释放。



你可能感兴趣的:(C语言,dll)