前面一篇博文嵌入式软件架构设计之分层设计給大家分享了程序分层设计的一些个人观点。里面有提到接口统一规范的问题,下面这篇博文详细阐述一下关于代码可移植性的问题。代码可移植性非常重要!在这里有的人很纳闷,有人会问:除了汇编语言的移植性很差以外,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_CREATE表示创建一个新的文件

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

原型:

int dev_FileRemove(const char *FileName);

功能:

删除文件

参数:

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/"。若为NULLSD卡自动挂载到"/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/"。若为NULLU盘自动挂载到"/mnt/usb/"目录。

Mode(输

TRUE

挂载U

FALSE

卸载U

返回:

0

挂载成功

-1

挂载失败

注释:

应用层不需要关心挂载的设备名和filesystem类型。

挂载U盘命令:mount -t vfat /dev/sda1 /mnt/usb

统一接口后,凡是对文件的操作,应用程序或者SDK层都只能调用这些接口,这样不管是linux平台还是其他嵌入式操作系统或非操作系统平台,应用程序对文件的操作的接口函数都是一样的了,这样就做到了应用程序不同平台的兼容性。