您的位置:首页 > 编程语言 > C语言/C++

第一个完整c/c++框架的构造学习——出自传智扫地僧c++教程

2018-03-19 12:05 525 查看
虚拟的数据传输接口
c实现(指针做函数参数的练习)
c_socket.h文件
//头文件写函数声明 .c文件写函数实现

//头文件注意点1  如果未定义,则定义,把函数声明包含进去
1#ifndef __C_SOCKET_H__
1#define __C_SOCKET_H__

//头文件注意点2
2#ifdef  __cplusplus//活动预处理  如果是c++编译器,引用语言的规范去编译
2extern "C" {
2#endif


//首先抽象出接口函数,实现 初始化 发送 接收 释放资源

int socketInit(void **handle/*void隐藏数据结构,**输出类型比输入高一*/);//构思需要哪些参数,在函数内构造出数据结构块,并传出内存块(指针+长度)
int socketSend(void *handle/*in*/,unsigned char *buf/*in*/,int buflen/*in*/ );
//参数  传入待传内存块(指针+长度)  此处的参数为什么都是输入类型的  
//传来的数据是用内存块的数据模拟的
int socketReceive(void *handle/*in*/,unsigned char *buf/*in*/,int *buflen/*out */);
//参数 待接收内存块(指针+长度)与 储存参数的内存块(指针+长度)
int socketDestroy(void *handle);//参数 所有分配的内存块(指针+长度)  释放

//第二套与第一套的区别
//------------------第二套接口---Begin--------------------------------//
//    int cltSocketInit2(void **handle);

    //客户端发报文
//    int cltSocketSend2(void *handle, unsigned char *buf,  int buflen);
    //客户端收报文
//    int cltSocketRev2(void *handle, unsigned char **buf, int *buflen);    出现二级指针,则在函数内分配用来接收报文的内存空间
//    int cltSocketRev2_Free(unsigned char **buf);                          报文使用完之后把buf释放(free(*buf))且置成NULL(*buf = NULL)
    //客户端释放资源

//    int cltSocketDestory2(void **handle);                                  出现二级指针,把handle释放且置成NULL
//------------------第二套接口---End--------------------------------//

//头文件注意点3
3#ifdef  __cplusplus//
3}
3#endif

1#endif



c_socket.c文件#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "c_socket.h"

//要点 把数据抽象出一个结构体隐藏数据结构
typedef struct socketHandle
{
//没有考虑到的点
char version[64]; //传递的内存包的日志信息
char ip[128]; //传来的数据的ip地址
int port;//使用了串口几
unsigned char *p;
int plen;
} SCK_HANDLE;

//写出具体函数实现

int socketInit(void **handle/*void隐藏数据结构,**输出类型比输入高一*/)
{
int ret = 0 ;
SCK_HANDLE *hdl = NULL; //将数据块分配在堆上,而不是栈上(Init函数生命周期结束后会释放栈上的空间,导致数据块无法被其他函数使用)
hdl = (SCK_HANDLE*)malloc(sizeof(SCK_HANDLE));
if (hdl == NULL )
{
ret = -1;
printf ("socketInit()err:%d",ret);
return ret ;
}
memset(hdl,0,sizeof(SCK_HANDLE));
//将分配好的内存空间首地址赋值给handle指针,*handle间接改变handle的指向
*handle = hdl;//操作handle指向hdl分配好的数据块

strcpy(hdl->version,"1.1.1");
strcpy(hdl->ip,"192.168.1.1");
        //strcpy与memcpy的区别:strcpy仅用于字符串的复制,遇到\0自动停止,容易溢出,memcpy用于内存块的复制,多一个参数len,不易溢出
        hdl->port = 1;

return ret;
}
//指针输入输出的判定:
//输出是函数内部分配内存返回函数指针 需要操作指针,故需要比所操作的指针高一级的指针
//输入是通过指针传递内存中的数据 需要操作内存数据,只需要内存块的地址就够了

 //构思需要哪些参数,在函数内构造出数据结构块,并传出内存块(指针+长度)
int socketSend(void *handle/*in*/,unsigned char *buf/*in*/,int buflen/*in*/ )
{
int ret = 0;
SCK_HANDLE *hdl = NULL;//用结构体把匿名的句柄接过来
if(handle == NULL || buf == NULL || buflen == NULL)
{
ret = -1;
printf ("socketSend()err:%d",ret);
return ret ;
}
hdl = (SCK_HANDLE*)handle;
hdl->p = (unsigned char *)malloc(buflen*sizeof(unsigned char));
if (hdl->p == NULL )
{
ret = -2;
printf ("socketSend()err:%d",ret);
return ret ;
}
memset(hdl->p,0,buflen);
memcpy(hdl->p,buf,buflen);
hdl->plen = buflen;

return ret;
}

//参数 传入待传内存块(指针+长度)
//此处的参数为什么都是输入类型的 因为是把本地的数据发出去
//发出的数据是将内存块的数据传入struct数据块中的
int socketReceive(void *handle/*in*/,unsigned char *buf/*in*/,int *buflen/*out */)
{
int ret = -1;
SCK_HANDLE *hdl = NULL;
if(handle == NULL || buf == NULL || buflen == NULL)
{
ret = -1;
printf ("socketReceive()err:%d",ret);
return ret ;
}
hdl = (SCK_HANDLE*)handle;

//模拟将数据块中的数据传到内存中去
memcpy(buf,hdl->p,hdl->plen);
*buflen = hdl->plen;

return ret;
}

//参数 待接收内存块(指针+长度)与 储存参数的内存块(指针+长度)
int socketDestroy(void *handle)
{
int ret = -1;
SCK_HANDLE *hdl = NULL;
if(handle == NULL)
{
ret = -1;
printf ("socketdestroy()err:%d",ret);
return ret ;
}
hdl = (SCK_HANDLE*)handle;
//此处要记得先释放struct中的指针,再释放结构体空间,从底向上释放
if(hdl->p != NULL)
{
free(hdl->p);
}
free(hdl);

return ret;
}

//参数 所有分配的内存块(指针+长度) 释放
测试框架#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

//包含头文件,实际这里做的处理是宏替换,直接将c_socket.c文件全部替换到这里
#include "c_socket.h"
//< >引用的是编译器的类库路径里面的头文件
//" "引用的是你程序目录的相对路径中的头文件


int main ()
{
//c的变量声明必须放在函数最前面 先定义后使用,否则编译出错
int ret = 0;
//void隐藏数据类型
void* hdl = NULL;

unsigned char inbuf[4096];
int inbuflen = 0;
unsigned char outbuf[4096] = "simulation data";
int outbuflen = strlen((char*)outbuf);

ret = socketInit(&hdl);

//调用发送函数
ret = socketSend(hdl ,outbuf ,outbuflen);

//调用接收函数
ret = socketReceive(hdl,inbuf,&inbuflen);

//调用析构函数
ret = socketDestroy(hdl);

printf("hello...\n");
system("pause");

}动态库的封装----------
1新建dll项目(工程1)
2在工程1 下建立.c文件, 将写好的函数实现粘贴进去,每个函数前加关键字 _declpec(dllexport),编译后即可在debug目录中生成.lib(资源模式文件) .dll(动态库)

3测试框架(工程2)动态库使用时需配合函数声明.h  [b].lib(资源模式文件)  .dll(动态库) 一起放置于调用dll的项目.cpp平行目录下,并在项目属性中->配置属性->链接器->输入->附加依赖项中 粘贴入.lib文件的文件名.lib;[/b]
[b]4重新生成项目即可调用dll;[/b]
[b]也可以在函数编写阶段实现动态库的调试编写,将工程1下打好桩的函数实现导出动态库调用到工程2下,在测试框架中调用函数,调试时完善函数。[/b]

c++(抽象类的继承  实现测试框架与接口分离)
定义抽象类,虚函数作为接口   抽象类.h
//避免函数头文件重复包含
#pragma once

//写出抽象类

class CSocketAbstract
{
public:
//类的构造函数,析构函数
CSocketAbstract()
{
;
}
~CSocketAbstract()
{
a70d

;
}
/* 先写出接口
int CSocketInit();
int CSocketSend(unsigned char *buf /in/,int buflen /in/);
int CSocketReceive(unsigned char *buf /in/,int *buflen/out/);
int CSocketDestroy();
*/
//写成纯虚函数的形式(含有纯虚函数的类叫做抽象类)
virtual int CSocketInit() = 0;
virtual int CSocketSend(unsigned char *buf /*in*/,int buflen /*in*/) = 0;
virtual int CSocketReceive(unsigned char *buf /*in*/,int *buflen/*out*/) = 0;
virtual int CSocketDestroy() = 0;
//下一步,

};//类声明这个分号别忘抽象类作为对厂商程序的规范
厂商继承抽象类,将纯虚函数实例化,并加入需要的属性和方法 厂商类.h//厂商1的继承抽象函数,并声明函数
#include <iostream>
using namespace std;
#include "c++_socket.h"
//继承的语法
class CSocket1 : public CSocketAbstract

{
public:
virtual int CSocketInit();
virtual int CSocketSend(unsigned char *buf /*in*/,int buflen /*in*/);
virtual int CSocketReceive(unsigned char *buf /*in*/,int *buflen/*out*/);
virtual int CSocketDestroy();
private:
unsigned char * p;
int plen;
};//类声明这个分号别忘厂商类的具体实现.cpp#include <iostream>
using namespace std;
#include "c++_socket1.h"
//开始打桩 先打桩,写好测试框架再写函数的具体实现

int CSocket1::CSocketInit()
{
int ret = 0 ;
this->p = NULL;
this->plen = 0;
return ret ;
}

int CSocket1::CSocketSend(unsigned char *buf /*in*/,int buflen /*in*/)
{
int ret = 0 ;
if(buf == NULL)
{
ret = -1;
cout<<"CSocketSend()err:%d"<<ret<<endl;
return ret;
}
this->p = new unsigned char [buflen];
if(this->p == NULL)
{
ret = -2;
cout<<"CSocketSend()err:%d"<<ret<<endl;
return ret;
}

memcpy(this->p,buf,buflen);
this->plen = buflen ;

return ret ;
}

int CSocket1::CSocketReceive(unsigned char *buf /*in*/,int *buflen/*out*/)
{
int ret = 0 ;

memcpy(buf,p,plen);
*buflen = plen;

return ret ;
}

int CSocket1::CSocketDestroy()
{
int ret = 0 ;

if(p != NULL)
{
delete [] p ;
p = NULL;
plen = 0;
}

return ret ;
}
测试框架.cpp,发生多态的三个条件:1继承,2虚函数重写父类函数,3父类指针传入子类对象#include <iostream>
using namespace std;

#include "c++_socket1.h"

int Test(CSocketAbstract * test)
{
int ret = 0;
unsigned char outbuf[4096] = "simulation data ";

int outbuflen = strlen((char*)outbuf);
unsigned char inbuf[4096];
int inbuflen = 0;
ret = test->CSocketInit();

ret = test->CSocketSend( outbuf,outbuflen);

ret = test->CSocketReceive(inbuf,&inbuflen);

ret = test->CSocketDestroy();

return ret;
}
int main ()
{
int ret = 0;

CSocketAbstract * sp = NULL;
CSocket1 hdl ;
//*sp = hdl;//错误使用 抽象类不能实例化
sp = &hdl;//抽象类指针 = 子类对象的地址
//发生多态的三个条件
//1继承
//2虚函数重写父类的函数
//3父类指针传入子类对象,传入父类调父类,传入子类调子类
//不能声明抽象类对象,可以声明抽象类指针,,可以声明抽象类引用
ret = Test( sp);

cout<<"hello..."<<endl;
system("pause");
return ret;

}以上两个框架是通过c/c++分别完成了框架编程
抽象类的使用完成了厂家程序和框架的解耦合,两方都有统一的规范——接口
如何在c函数中实现解耦合呢
提出了函数指针做函数参数 回调函数的思想
将所使用的厂商程序函数入口地址作为参数传入框架或者封装入动态库,在使用动态库时,通过传入的参数自动调用动态库外的函数
函数指针的语法推演定义int add (int a ,int b)

{

return a+b;
}

1函数类型

typedef int (Mytypefunc)(int a,int b)

Mytypefunc* myfunc = NULL;

myfunc = & add;

2函数指针类型 约定接口类型参数

typedef int (*Mytypefunc)(int a ;int b);

Mytypefunc myfunc ;

myfunc = add;

myfunc(5,5);//调用函数

3函数指针变量 实例化函数指针传入函数

int (*Mytypefunc)(int a, int b);

Mytypefunc = add;

Mytypefunc(5,5);

利用函数指针类型再框架中预留第三方函数的位置
1 .h 头文件中声明函数指针类型 并更改函数声明//用户使用函数DesEnc加密
typedef int (*DesEncFuncType)(unsigned char *pInData,int nInDataLen,unsigned char *pOutData,int *pOutDataLen);
//用户使用函数DesDec解密
typedef int (*DesDecFuncType)(unsigned char *pInData,int nInDataLen,unsigned char *pOutData,int *pOutDataLen);
int socketSend(void *handle/*in*/,DesEncFuncType DesEncFunc ,unsigned char *buf/*in*/,int buflen/*in*/ );
int socketReceive(void *handle/*in*/,DesDecFuncType DesDecFunc ,unsigned char *buf/*in*/,int *buflen/*out */);
2 .c 文件中更改函数实现int socketSend(void *handle/*in*/,DesEncFuncType DesEncFunc ,unsigned char *buf/*in*/,int buflen/*in*/ )
{
int ret = 0;
SCK_HANDLE *hdl = NULL;//用结构体把匿名的句柄接过来
unsigned char tmpbuf[4096];
int tmpbuflen = 0;
if(handle == NULL || buf == NULL || buflen == NULL)
{
ret = -1;
printf ("socketSend()err:%d",ret);
return ret ;
}
hdl = (SCK_HANDLE*)handle;

//用户使用的函数
//int DesEnc(
//unsigned char *pInData,
//int nInDataLen,
//unsigned char *pOutData,
//int *pOutDataLen);
//集成加密函数 将加密后的文件发送至模拟云端结构体SCK_HANDLE
memset(tmpbuf,0,sizeof(tmpbuf));
tmpbuflen = strlen((char *)tmpbuf);
ret = DesEncFunc(buf,buflen,tmpbuf,&tmpbuflen);
if (ret != 0 )
{
printf ("DesFunc()err:%d",ret);
}

hdl->p = (unsigned char *)malloc(tmpbuflen*sizeof(unsigned char));
if (hdl->p == NULL )
{
ret = -2;
printf ("socketSend()err:%d",ret);
return ret ;
}

memset(hdl->p,0,tmpbuflen);
memcpy(hdl->p,tmpbuf,tmpbuflen);//拷贝时越界所以造成free(hdl->p)出错
hdl->plen = tmpbuflen;

return ret;
}
int socketReceive(void *handle/*in*/,DesDecFuncType DesDecFunc, unsigned char *buf/*in*/,int *buflen/*in,out */)
{
    int ret = -1;
    unsigned char tmpbuf[4096];
    int tmpbuflen = 0;
    SCK_HANDLE *hdl = NULL;
    if(handle == NULL || buf == NULL || buflen == NULL)
    {
        ret = -1;
        printf ("socketReceive()err:%d",ret);
        return ret ;
    }
    hdl = (SCK_HANDLE*)handle;
    memset(tmpbuf,0,tmpbuflen);
    ret = DesDecFunc(hdl->p,hdl->plen,tmpbuf,&tmpbuflen);
    //模拟将数据块中的数据经解密后传到内存中去
    memcpy(buf,tmpbuf,tmpbuflen);
    *buflen = tmpbuflen;
    
    return ret;
}


3 在.c测试框架中更改函数调用,添加第三方加解密头文件和函数实现文件到项目目录#include "des.h"//添加第三方加解密头文件

//调用发送函数
ret = socketSend(hdl ,DesEnc ,outbuf ,outbuflen);

//调用接收函数
ret = socketReceive(hdl,DesDec ,inbuf,&inbuflen);
free() 时堆出错
1 释放一个空指针

2 重复释放
3 释放一个非自己申请的内存,或者释放的指针指向的地方不是本进程申请的。
4 申请的内存块写过界了或者被写过界了,此时内存块就被破坏了,释放的时候为了避免释放掉其他有用的数据,是会报错。
情况4是本程序编写过程中出现的问题,在集成了加解密算法之后,加密后的字符长度发生了变化,而程序仍按照输入字符的长度来分配内存,虽然使用memcpy len限制来避免过界,仍造成了cpy的字符长度超出了分配内存的限制,导致free(hdl->p)时出现了堆错误
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: