前面一篇博文《嵌入式軟件架構設計之分層設計》給大家分享了程序分層設計的一些個人觀點。裏面有提到接口統一規範的問題,下面這篇博文詳細闡述一下關於代碼可移植性的問題。代碼可移植性非常重要!在這裏有的人很納悶,有人會問:除了彙編語言的移植性很差以外,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平臺還是其他嵌入式操作系統或非操作系統平臺,應用程序對文件的操作的接口函數都是一樣的了,這樣就做到了應用程序不同平臺的兼容性。