前面一篇博文《嵌入式软件架构设计之分层设计》給大家分享了程序分层设计的一些个人观点。里面有提到接口统一规范的问题,下面这篇博文详细阐述一下关于代码可移植性的问题。代码可移植性非常重要!在这里有的人很纳闷,有人会问:除了汇编语言的移植性很差以外,c语言,c++,java等其他高级语言的移植性不是都很棒的么。毋庸置疑,c语言的移植性是非常好。但本文不是在探讨一段编程语言的移植性问题,要探讨的是:在不同的嵌入式平台上,如何提高开发嵌入式产品的效率问题。换一种表达方式:如何提高嵌入式软件平台之间的可移植性。
在探讨之前先抛出一个问题:如何才能使开发的应用程序运行在不同的硬件平台上?这里所指的应用程序不是指pc端的应用也不是指的android,ios等平台的应用。而是指的是其他嵌入式平台的应用,他们更多的是c语言实现。我们都知道windows平台也好还是ios,android平台也罢,他们的应用是不同的编程语言实现的,但是他们有一个共同的特点:給应用开发人员提供标准的sdk接口。正是这些标准的接口使得应用开发和硬件以及底层相分离。大大提高了应用程序的可移植性。
目前来看,嵌入式产品若不是android系统,是没有统一的接口规范的。公司在开发设计某款嵌入式产品时,涉及到如下内容:硬件平台选型,软件平台选型,底层软件开发,应用软件开发。底层软件和硬件是紧密联系在一起的,而应用软件可以做到与硬件平台无关。要做到无关就得定义统一的接口规范。让应用程序调用接口规范规定的接口,底层软件根据不同的平台封装不同的程序来实现其接口功能。如此大大提高应用程序不同硬件平台的可移植性。这样做也规范了编程,增加了程序的可读性。
下面以文件操作为例
1.本模块相关宏定义
//本模块相关宏定义以及结构定义如下: // 错误码定义 #define FILE_EXIST 1 #define FILE_NOEXIST 2 #define MEM_OVERFLOW 3 #define TOO_MANY_FILES 4 #define INVALID_HANDLE 5 #define INVALID_MODE 6 #define FILE_NOT_OPENED 8 #define FILE_OPENED 9 #define END_OVERFLOW 10 #define TOP_OVERFLOW 11 #define NO_PERMISSION 12 #define FS_CORRUPT 13 //以上错误码与标准linux的错误码errno有差异,对于Linux系统的机型,错误码保留linux标准的errno定义。 // 文件打开模式,可使用"或"组合 #ifndef O_RDONLY #define O_RDONLY 00 #endif #ifndef O_WRONLY #define O_WRONLY 01 #endif #ifndef O_RDWR #define O_RDWR 02 #endif #ifndef O_NONBLOCK #define O_NONBLOCK 04000 #endif #ifndef O_CREAT #define O_CREAT 0100 #endif // 文件定位起点 #ifndef SEEK_SET #define SEEK_SET 0 #endif #ifndef SEEK_CUR #define SEEK_CUR 1 #endif #ifndef SEEK_END #define SEEK_END 2 #endif // 文件信息 typedef struct { char fid; char attr; char type; char name[17]; int length; } FILE_INFO;
2.接口函数规范说明
2.1 dev_FileOpen
原型: | int dev_FileOpen(const char *FileName, int Mode); | |||
功能: | 打开本应用所属的文件 | |||
参数: | FileName(输入) | 1-16Bytes | 文件名,最多可以是16个字符,以‘\x00’结尾,超过16个字符的只取前16个。 | |
Mode(输入) | O_CREAT | 0100 | ||
O_RDWR | 02 | 文件打开方式,O_RDWR表示打开文件用于读/写, | ||
返回: | [0,255] | 成功,返回句柄号 | ||
-1 | 失败,错误码放在errno中 | |||
注释: | 如果以O_CREATE | O_RDWR的方式打开文件,若文件已存在,则以O_RDWR的方式打开,否则创建该文件。已打开的文件可以重复打开,每次打开后,文件指针移到文件开头。 错误码介绍: INVALID_MODE mode不为O_RDWR/O_CREATE FILE_NOEXIST 文件不存在 FILE_EXIST 当文件存在时以O_CREATE的方式打开 MEM_OVERFLOW空间不足 TOO_MANY_FILES 文件句柄太多,超过255,无法创建新文件 FILE_OPENED 文件已打开 | |||
示例: | 打开文件”Demo.bin”, 如果不存在该文件,则创建该文件。 int Ret = 0; Ret = fileOpen(“Demo.bin”,O_RDWR); // 以读写方式打开该文件 if(Ret < 0) // 该文件不存在,则重新创建 { Ret = fileOpen(“Demo.bin”, O_CREAT); if(Ret < 0) { return Ret; // 创建失败,则返回错误码 } } ………………… // 对这个文件进行其他的读写操作等 |
2.2 dev_FileSeek
原型: | int dev_FileSeek(int FileID, int Offset, int Whence); | |||
功能: | 对打开的文件指针进行定位 | |||
参数: | FileID(输入) | [0,255] | 文件句柄号,从打开文件获得的返回句柄 | |
Offset(输入) | 从Whence指定的位置到要移到的位置的字节数(有符号值) | |||
Whence(输入) | SEEK_SET | 0 | 表示从文件头开始 | |
SEEK_CUR | 1 | 从当前文件指针开始 | ||
SEEK_END | 2 | 从文件尾开始。 | ||
返回: | >=0 | 成功, 返回的数值是文件当前指针相对文件头的位置 | ||
-1 | 失败,参数错误或偏移目标超出文件范围,错误码放在errno中。 | |||
注释: | 错误码介绍: INVALID_FILEID无效的文件句柄 FILE_NOT_OPENED 文件未打开 END_OVERFLOW 后移时超出文件长度的偏移 TOP_OVERFLOW 前移时出错 | |||
示例: | 调用该函数前,必须先确认指定文件已经被正确打开了: fd = fileOpen(“filename”, O_RDWR); 1、定位到文件的第2个字节位置: Ret = fileSeek(fd, 2, SEEK_SET); 2、定位到文件的倒数第2个字节位置: Ret = fileSeek(fd, -2, SEEK_END); 3、定位当前位置的后面第2个字节处: Ret = fileSeek(fd, 2, SEEK_CUR); |
2.3 dev_FileRead
原型: | int dev_FileRead(int FileID, void *DataBuf, int Len); | ||
功能: | 从文件的当前定位位置开始读取文件数据 | ||
参数: | FileID(输入) | [0,255] | 文件句柄号,从打开文件获得的返回句柄 |
DataBuf(输出) | 应用指定的用来存放读取数据的缓冲区 | ||
Len(输入) | 期望读取的数据字节数。 | ||
返回: | >=0 | 读取成功,返回实际读到的字节数。 | |
-1 | 失败,错误码放在errno中 | ||
注释: | 读取后文件指针位置将移动到读取后的位置 错误码介绍: FILE_NOT_OPENED文件未打开 INVALID_FILEID无效的文件句柄 | ||
示例: | 调用该函数之前,先调用fileOpen打开指定的文件: fd = fileOpen(“Filename”, O_CREAT|O_RDWR);
1、从文件起始处读取16字节 Ret = fileSeek(fd, 0, SEEK_SET); Ret = fileRead(fd, Buff, 16);
2、从当前光标位置处开始读取16字节: Ret = fileSeek(fd, 0, SEEK_CUR); Ret = fileRead(fd, Buff, 16);
3、读取文件的最后16个字节 Ret = fileSeek(fd, -16, SEEK_END); Ret = fileRead(fd, Buff, 16); |
2.4 dev_FileWrite
原型: | int dev_FileWrite(int FileID, const void *DataBuf, int Len); | ||
功能: | 从文件的当前定位位置开始写入数据 | ||
参数: | FileID(输入) | [0,255] | 文件句柄号,从打开文件获得的返回句柄 |
DataBuf(输入) | 应用指定的存放写入数据的缓冲区 | ||
Len(输入) | 需要写入数据的字节数。 | ||
返回: | >=0 | 写入成功,返回实际写入的字节数。 | |
-1 | 失败,错误码放在errno中 | ||
注释: | 写入后,文件指针位置将移动到写入后的位置 错误码介绍: INVALID_FILEID 无效的文件句柄 FILE_NOT_OPENED 文件未打开 MEM_OVERFLOW 空间不足或文件句柄太多 | ||
示例: | 调用该函数之前,必须先调用fileOpen打开指定的文件: fd = fileOpen(“filename”, O_RDWR|O_CREAT);
1、修改文件的头16个字节: Ret = fileSeek(fd, 0, SEEK_SET); Ret = fileWrite(fd, Buff, 16);
2、从当前光标处开始写入16字节: Ret = fileSeek(fd, 0, SEEK_CUR); Ret = fileWrite(fd, Buff, 16);
3、从文件结束处再写入16字节: Ret = fileSeek(fd, 0, SEEK_END); Ret = fileWrite(fd, Buff, 16); |
2.5 dev_FileTruncate
原型: | int dev_FileTruncate(int FileID, int Len); | ||
功能: | 截短文件 | ||
参数: | FileID(输入) | [0,255] | 文件句柄号,从打开文件获得的返回句柄 |
Len(输入) | 从文件头保留的文件数据长度。 | ||
返回: | 0 | 成功截断。 | |
-1 | 失败,错误码放在errno中。 | ||
注释: | 该函数将文件截断为Len长度,原文件中从Len到结尾的内容全被截去。文件指针移至截短后的文件的最后。 错误码介绍: NO_FILESYS文件系统未建立 INVALID_FILEID 无效的文件句柄 FILE_NOT_OPENED 文件未打开 TOP_OVERFLOW 长度小于0 END_OVERFLOW 长度超出文件长度 | ||
示例: | 只保留指定文件的前256字节内容: fd = fileOpen(“filename”, O_RDWR);
Ret = fileTruncate(fd, 256); |
2.6 dev_FileClose
原型: | int dev_FileClose(int FileID); | ||
功能: | 关闭已打开的文件 | ||
参数: | FileID(输入) | [0,255] | 文件句柄号,从打开文件获得的返回句柄 |
返回: | 0 | 成功关闭文件 | |
-1 | 失败,错误码放在errno中。 | ||
注释: | 错误码介绍: INVALID_FILEID无效的文件句柄 | ||
示例: | 先打开文件: fd = fileOpen(“filename”, O_RDWR | O_CREAT); ………………… // 文件相关处理 关闭文件: Ret = fileClose(fd); |
2.7 dev_FileRemove
原型: | |||
功能: | 删除文件 | ||
参数: | FileName(输入) | 1-16Bytes | 文件名,最多可以是16个字符,以‘\x00’结尾,超过16个字符的只取前16个。 |
返回: | 0 | 成功 | |
-1 | 失败,错误码放在errno中。 | ||
注释: | 错误码介绍: FILE_OPENED 文件已打开 FILE_NOEXIST 文件不存在 | ||
示例: | 删除指定文件: Ret = fileRemove(“filename”); |
2.8 dev_FileSize
原型: | int dev_FileSize(const char *FileName); | ||
参数: | FileName(输入) | 1-16Bytes | 文件名,最多可以是16个字符,以‘\x00’结尾,超过16个字符的只取前16个。 |
返回: | >=0 | 文件的大小 | |
-1 | 失败,错误码放在errno中。 | ||
注释: | 错误码介绍: FILE_NOEXIST 文件不存在 | ||
示例: | fileLen = fileSize(“filename”); // 查询文件大小 |
2.9 dev_FileExist
原型: | int dev_FileExist(const char *FileName); | ||
功能: | 判断文件是否存在 | ||
参数: | FileName(输入) | 1-16Bytes | 文件名,最多可以是16个字符,以‘\x00’结尾,超过16个字符的只取前16个。 |
返回: | [0,255] | 文件序号。 | |
-1 | 在当前应用中没有指定的文件 | ||
注释: | 无 | ||
示例: | 判断某个文件是否存在,如存在,则获取其大小: Ret = fileExist(“filename”); if(Ret >= 0) { fileLen = fileSize(“filename”); } |
2.10 dev_FileRename
原型: | int dev_FileRename(const char *OldFileName, const char *NewFileName); | ||
功能: | 修改属于本应用的数据文件文件名 | ||
参数: | OldFileName(输入) | 1-16Bytes | 原文件名,最多可以是16个字符,以‘\x00’结尾,超过16个字符的只取前16个。 |
NewFileName(输入) | 1-16Bytes | 新文件名,最多可以是16个字符,以‘\x00’结尾,超过16个字符的只取前16个。 | |
返回: | 0 | 成功 | |
-1 | 失败,错误码放在errno中。 | ||
注释: | 错误码介绍: FILE_OPENED 文件已打开 FILE_NOEXIST 文件不存在 FILE_EXIST 新文件已经存在 | ||
示例: |
2.11 dev_FileFreeSpace
原型: | int dev_FileFreeSpace(void); |
功能: | 返回文件系统剩余可写的字节数 |
参数: | 无 |
返回: | 返回文件系统总共剩余的空间大小(字节数) |
注释: | 无 |
示例: | 判断文件系统还有多少剩余空间: FreeLen = fileFreeSpace(); |
2.12 dev_MntSDCard
原型: | int dev_MntSDCard(const char *TargetDir, int Mode); | ||
功能: | 挂载SD卡。 | ||
参数: | TargetDir(输入) | 指定的挂载SD卡的目录,默认为"/mnt/sdcard/"。若为NULL则SD卡自动挂载到"/mnt/sdcard/"目录。 | |
Mode(输入) | TRUE | 挂载SD卡 | |
FALSE | 卸载SD卡 | ||
返回: | 0 | 挂载成功 | |
-1 | 挂载失败 | ||
注释: | 应用层不需要关心挂载的设备名和filesystem类型。 挂载SD卡命令:mount -t vfat /dev/sdd1 /mnt/sdcard |
2.13. dev_MntUSBDisk
原型: | int dev_MntUSBDisk(const char *TargetDir, int Mode); | ||
功能: | 挂载U盘。 | ||
参数: | TargetDir(输入) | 指定的挂载U盘的目录,默认为"/mnt/usb/"。若为NULL则U盘自动挂载到"/mnt/usb/"目录。 | |
Mode(输入) | TRUE | 挂载U盘 | |
FALSE | 卸载U盘 | ||
返回: | 0 | 挂载成功 | |
-1 | 挂载失败 | ||
注释: | 应用层不需要关心挂载的设备名和filesystem类型。 挂载U盘命令:mount -t vfat /dev/sda1 /mnt/usb |
统一接口后,凡是对文件的操作,应用程序或者SDK层都只能调用这些接口,这样不管是linux平台还是其他嵌入式操作系统或非操作系统平台,应用程序对文件的操作的接口函数都是一样的了,这样就做到了应用程序不同平台的兼容性。