函数指针基础及应用(正向调用、动态库升级成框架)

1、数组指针

    二维数组。

2、函数指针

      函数指针做函数参数的思想精华——调用者任务的人  和 实现任务的人  解耦合
    (回调函数的本质:
提前做了一个协议的约定把函数的参数、函数返回值提前约定))
    (C++编译器通过多态的机制(提前布局vptr指针和虚函数表,找虚函数入口地址来实现))

1)基本功能演示:

#define  _CRT_SECURE_NO_WARNINGS 
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

//=====> 函数指针做函数参数的思想精华:
// 调用者任务的人  和 实现任务的人  解耦合

int myadd(int a, int b)    //原来子任务
{
	int c = 0;
	c = a + b;
	printf("func myadd() do \n");
	return c;
}

int myadd2(int a, int b)  //后续子任务 
{
	int c = 0;
	c = a + b;
	printf("func myadd2() do \n");
	return c;
}

int myadd3(int a, int b)  //后续子任务 
{
	int c = 0;
	c = a + b;
	printf("func myadd3() do \n");
	return c;
}

int myadd4(int a, int b)  //后续子任务 
{
	int c = 0;
	c = a + b;
	printf("func myadd4() do \n");
	return c;
}

// int (*myFuncVar)(int a, int b)——函数指针变量——返回类型为int,两个int型变量
int myMianOp(  int(*myFuncVar)(int a, int b)  )   
{
	myFuncVar(10, 20);  //间接的调用 myadd函数
	return 0;
}

void main()
{
	myMianOp(myadd);  //调用者
	myMianOp(myadd2); //调用者
	myMianOp(myadd3); //调用者
	myMianOp(myadd4); //调用者
}
函数指针基础及应用(正向调用、动态库升级成框架)_第1张图片

2)函数指针应用一——正向调用:甩开C++编译器,自己找到DLL中函数的地址,然后去调用函数

a)新建一个MFC应用程序(以便使用系统函数),拖一个Button控件(DllPlay),双击控件编写代码;

           函数指针基础及应用(正向调用、动态库升级成框架)_第2张图片
         b)一般情况下,提供DLL时还会同时提供一个.h文件,通过这个.h文件,我们就可以知道DLL提供了哪些函数。(如果没有提供,我们可以借助VC6.0Tools——Depends,导入DLL,查看DLL提供的Function)。下述SOCKETCLIENT.DLL可在http://yun.baidu.com/s/1c0x2kP6函数指针动态库下载。
函数指针基础及应用(正向调用、动态库升级成框架)_第3张图片
       c)接着a),双击进去以后,首先在该文件的全局域声明要使用的函数的函数指针类型,然后在Button的单击事件中继续编写。

//声明一个函数指针类型
//客户端初始化 获取handle上下
typedef int(*CltSocketInit)(void **handle /*out*/);

//客户端发报文
typedef int(*CltSocketSend)(void *handle /*in*/, unsigned char *buf /*in*/, int buflen /*in*/);

//客户端收报文
typedef int(*CltSocketRev)(void *handle /*in*/, unsigned char *buf /*in*/, int *buflen /*in out*/);

//客户端释放资源
typedef int(*CltSocketDestory)(void *handle/*in*/);

void C函数指针正向调用Dlg::OnBnClickedButton1()
{
	HINSTANCE hInstance;
	hInstance = ::LoadLibrary("c:/socketclient.dll");  //导入动态库(先把DLL放到C盘下)
	if (hInstance == NULL)
	{
		AfxMessageBox("LoadLibrary(DLL) 失败!");
		return;
	}
	//首先获得了"cltSocketInit"函数的地址(GetProcAddress),然后再转换成CltSocketInit类型,再利用上面定义的函数指针类型定义变量cltSocketInit
	CltSocketInit cltSocketInit = (CltSocketInit)::GetProcAddress(hInstance, "cltSocketInit");   
	if (cltSocketInit == NULL)
	{
		return;
	}
	CltSocketSend cltSocketSend = (CltSocketSend)::GetProcAddress(hInstance, "cltSocketSend");
	CltSocketRev cltSocketRev = (CltSocketRev)::GetProcAddress(hInstance, "cltSocketRev");
	CltSocketDestory cltSocketDestory = (CltSocketDestory)::GetProcAddress(hInstance, "cltSocketDestory");

	//执行动态库函数调用
	unsigned char buf[128];
	int buflen = 128;
	unsigned char outbuf[4096];
	int outbuflen = 4096;

	strcpy_s((char *)buf,128, "aaaaaaaaaafffffffffdddddd");
	buflen = 9;

	void *handle = NULL;
	int ret = 0;

	//利用函数指针变量间接调用函数
	ret = cltSocketInit(&handle);
	ret = cltSocketSend(handle, buf, buflen);
	ret = cltSocketRev(handle, outbuf, &outbuflen);
	ret = cltSocketDestory(handle);

	if (memcmp(buf, outbuf, outbuflen) == 0)
	{
		AfxMessageBox("发送数据和接受的数据一样 ok");
	}
	else
	{
		AfxMessageBox("发送数据和接受的数据不一样");
	}
}
           选择项目属性->配置属性->常规->字符集,改为“未设置”(error C2665: “AfxMessageBox”: 2 个重载中没有一个可以转换所有参数类型)。运行,出现以下错误:
错误	1	error MSB8031: Building an MFC project for a non-Unicode character set is deprecated. You must change the project property to Unicode or download an additional library. See http://go.microsoft.com/fwlink/p/?LinkId=286820 for more information.	C:\Program Files\MSBuild\Microsoft.Cpp\v4.0\V120\Microsoft.CppBuild.targets	369	5	函数指针正向调用
        这是因为:用于多字节字符编码 (MBCS) 的 MFC 库 (DLL) 不再包含于 Visual Studio 中,但是可用作插件,您可以在任何装有 Visual Studio Professional、Visual Studio Premium 或 Visual Studio Ultimate 的计算机上下载和安装。(在 Visual Studio 中,必须启用 MFC。)包括英语(美国)和 DLL 的本地化版本。
需要下载安装Multibyte MFC Library for Visual Studio 2013:http://www.microsoft.com/zh-cn/download/details.aspx?id=40770

运行:

          

        3)函数指针应用二——动态库升级成框架(动态库:抽象类一个套接口,单独封装成模块,供别人调用;无法扩展。框架:能自由的扩展,方便后续产品入围,而不轻易改变框架)
需求:比如在一个socket通信库中,完成数据加密功能,有n个厂商的加密产品供你选择,还有后续的加密产品,所以就需要实现动态库和第n个厂商产品的解耦合,支持后续加密产品的入围。

         函数指针基础及应用(正向调用、动态库升级成框架)_第4张图片
实现步骤:
    1)、动态库中定义协议,并完成任务的调用:

//完成发送报文的时候,进行数据加密
//定义函数指针类型,通过函数指针类型 来约定 厂商 去实现 加密解密函数的原型
//方式一:
typedef int (*EncData)(unsigned char *in, int inlen, unsigned char *out, int *outlen);
typedef int (*DecData)(unsigned char *in, int inlen, unsigned char *out, int *outlen);
int socketclient_sendAndEnc1(void *handle, unsigned char *buf, int buflen, EncData encDataCallback);

//方式二:直接函数指针做参数
int socketclient_sendAndEnc2(void *handle, unsigned char *buf, int buflen,
					int (*EncData)(unsigned char *in, int inlen, unsigned char *out, int *outlen)
	);
//动态库中定义实现以上函数
__declspec(dllexport)
int socketclient_sendAndEnc1(void *handle, unsigned char *buf, int buflen, EncData encDataCallback)     //encDataCallback函数指针类型变量
{
	int		ret = 0;
	unsigned char   cryptbuf[4096];
	int		ryptbuflen = 4096;

	Sck_Handle	*tmpHandle = NULL;
	if (handle == NULL || buf==NULL || encDataCallback==NULL)
	{
		ret = -1;
		printf("func socketclient_sendAndEnc1() err :%d  check handle == NULL err \n", ret);
		return ret;
	}

	ret = encDataCallback(buf, buflen, cryptbuf, &cryptbuflen);    //间接的调用子任务,实现第三方加密产品和调用者的分离
	if (ret != 0)
	{
		ret = -2;
		printf("func socketclient_sendAndEnc1() err :%d  check handle == NULL err \n", ret);
		return ret;
	}

	tmpHandle = (Sck_Handle *)handle;
	tmpHandle->len = cryptbuflen;

	tmpHandle->p = (unsigned char *)malloc(cryptbuflen);
	if (tmpHandle->p == NULL) 
	{
		ret = -3;
		printf("func socketclient_sendAndEnc1() err :%d  mallocerr \n", ret);
		return ret;
	}

	//把加密的明文  缓存到 内存中
	memcpy(tmpHandle->p, cryptbuf, cryptbuflen);
	
	return 0;
}
     2)、加密厂商完成协议函数的编写:
//某后续第三方加密产品实现
int Hw_EncData(unsigned char *in, int inlen, unsigned char *out, int *outlen)
{
	printf("func Hw_EncData begin....\n ");   //此处不是具体加密实现
	strcpy((char *)out, "123456789");   //伪加密
	*outlen = 9;

	printf("func Hw_EncData end....\n ");
	return 0;
}
     3)、对接调试——通过函数指针做参数,加入到动态库中。也就是说,函数指针建立起了第三方和主动态库之间的联系。
int ret = 0;
ret = socketclient_sendAndEnc1(handle, buf, buflen, Hw_EncData);   //把第三方加密产品——Hw_EncData注入

       分析具体是怎么分离的:main函数调用动态库的socketclient_sendAndEnc1(发送并加密),但是并不调用自己的加密函数,而是通过其中的函数指针转而去调用第三方加密产品Hw_EncData。也就是说不管第三方产品有多少,我的动态库都不用改变!实现了动态库和第三方加密产品的解耦合!赋予了动态库生命力!

       回调函数:利用函数指针做函数参数,实现的一种调用机制,具体任务的实现者,可以不知道什么时候被调用。
       回调机制原理:
           当具体事件发生时,调用者通过函数指针调用具体函数
           回调机制将调用者和被调函数分开,两者互不依赖
           任务的实现 和 任务的调用 可以耦合  (提前进行接口的封装和设计)


上述使用到的动态库和完整工程下载:http://pan.baidu.com/s/1c0x2kP6

你可能感兴趣的:(C++,函数指针)