引擎之旅 Chapter.3 文件系统
文章目录
- 引言
- 在此之前...
- Unicode和ASCII
- C风格字符串的操作函数集合
- 字符串操作
- 字符串类型转换
- Part1:操作文件名和文件路径
- Part2:单个文件的读写
- 文件打开的模式
- TFile的定义
- Part3:异步文件I/O
- 异步I/O线程
- 文件类中的异步方法
引言
为什么会将文件系统放在引擎的底层核心代码呢?对于游戏而言,游戏的本质就是多媒体体验(模型、声音、视频等),而游戏引擎引擎需要在底层实现相关文件的读取作为支撑。
文件系统和资源管理器是两个概念。文件系统的功能是文件的读写,针对的是单文件或文件夹;而资源管理器则是目录级别的增删查改,可以成为引擎文件的简易版数据库。
- 对于游戏引擎来说,文件系统应该要实现一下几个部分:
- 操作文件名和文件路径
- 开、关、读、写单独的文件
- 处理异步文件输入/输出(IO)请求(做串流之用)
下面我们这几部分进行逐一分析,并展示实现的代码。
在此之前…
对于文件系统。字符串应该是密不可分的。且不说很多文件都与文本(字符串组成),文件路径也是通过字符串来表达的。因此,对字符串的处理对于文件系统来说是十分重要的。幸运的是,C提供了许多对字符串的操作函数,我们需要对它们进行封装整合即可。
Unicode和ASCII
关于使用ASCII还是Unicode,我的建议是尽量两种都实现,毕竟我们不知道仅仅使用一种类型的字符会不会在未来的开发中遇到麻烦,多多益善。
例如:在文件流处理中,我发现了宽字符版本的fputws和fgetws使用时其fopen()不能基于文本,只能基于二进制。而二进制对于要记录文字可读性是非常差的。因此,最好使用ASCII版的 fputs和fgets
而在Windows中字符和字符串的类型有众多定义(天晓得为什么会则么多)。为代码类型统一性,我现在这里做一个规范。在此引擎代码中,我将使用如下字符类型。
字符集 | 字符类型 | 常量字符类型 | 字符指针类型 | 常量字符串类型 |
---|---|---|---|---|
ASCII | CHAR | const CHAR | PSTR | PCSTR |
Unicode | WCHAR | const WCHAR | PWSTR | PCWSTR |
C风格字符串的操作函数集合
接下来我们要将C风格字符串的操作函数用自己的命名风格对其进行封装。代码虽然比较多,但不复杂。每一种函数都要写ASCII和Unicode两种脚本。函数按功能分类主要有:
- 按功能分类
- 获取字符串长度
- 字符串复制
- 字符串拼接
- 字符串比较
- 字符串中查找字符(从左往右和从右往左两个版本)
- 字符格式化
- CHAR字符串和WCHAR字符串之间的相互转换
字符串操作
//TEString.h
//-------------------------------------------------------------------------
#include <wchar.h>
#include <tchar.h>//获取宽字符串长度
//param:
// str:计算的字符串
//return:
// 返回字符串长度
inline size_t TStrLen(PCWSTR str)
{return _tcslen(str);
}//获取字符串长度
//param:
// str:计算的字符串
//return:
// 返回字符串长度
inline size_t TStrLen(PCSTR str)
{return strlen(str);
}//获取[char16_t]字符串长度
//param:
// str:计算的字符串
//return:
// 返回字符串长度
inline size_t TStrLen(register const char16_t* str)
{if (!str)return 0;register size_t len = 0;while (str[len++]);return len - 1;
}//获取[char32_t]字符串长度
//param:
// str:计算的字符串
//return:
// 返回字符串长度
inline size_t TStrLen(register const char32_t* str)
{if (!str)return 0;register size_t len = 0;while (str[len++]);return len - 1;
}//宽字符串复制
//param:
// dest:目标缓存指针
// destlen:目标缓存大小
// source:拷贝源字符串
inline errno_t TStrCpy(PWSTR dest, size_t destlen, PCWSTR source)
{return _tcscpy_s(dest, destlen, source);
}//char字符串复制
//param:
// dest:目标缓存指针
// destlen:目标缓存大小
// source:拷贝源字符串
inline errno_t TStrCpy(PSTR dest, size_t destlen, PCSTR source)
{return strcpy_s(dest, destlen, source);
}//宽字符串复制
//param:
// dest:目标缓存指针
// destlen:目标缓存大小
// source:拷贝源字符串
// cpylen:拷贝字符串长度
inline errno_t TStrCpy(PWSTR dest, unsigned int destlen, PCWSTR source, unsigned int cpylen)
{return _tcsncpy_s(dest, destlen, source, cpylen);
}//将宽字符串endStr拼接到dest上
//param:
// dest:目标缓存指针
// destlen:目标缓存大小
// endStr:拼接的字符串
inline errno_t TStrCat(PWSTR dest, size_t destlen, PCWSTR endStr)
{return _tcscat_s(dest, destlen, endStr);
}//将宽字符串endStr拼接到dest上
//param:
// dest:目标缓存指针
// destlen:目标缓存大小
// endStr:拼接的字符串
// catlen:需要从拼接字符串获取的长度
inline errno_t LStrCat(PWSTR dest, size_t destlen, PCWSTR endStr, int catlen)
{return _tcsncat_s(dest, destlen, endStr, catlen);
}//将字符串endStr拼接到dest上
//param:
// dest:目标缓存指针
// destlen:目标缓存大小
// endStr:拼接的字符串
inline errno_t TStrCat(PSTR dest, size_t destlen, PCSTR endStr)
{return strcat_s(dest, destlen, endStr);
}//宽字符串比较函数
//param
// str1:比较字符串1
// str2:比较字符串2
inline int TStrCmp(PWSTR str1, PWSTR str2)
{return _tcscmp(str1, str2);
}//宽字符串比较函数
//param
// str1:比较字符串1
// str2:比较字符串2
// cmplen:比较的长度
inline int TStrCmp(PWSTR str1, PWSTR str2, size_t cmplen)
{return _tcsncmp(str1, str2, cmplen);
}//宽字符查找函数(从左往右找)
//param
// opStr:查找字符串
// c:目标字符
inline PWSTR TStrChr(PWSTR opStr, WCHAR c)
{return wcschr(opStr, c);
}//字符查找函数(从左往右找)
//param
// opStr:查找字符串
// c:目标字符
inline PCSTR TStrChr(PCSTR opStr, CHAR c)
{return strchr(opStr, c);
}//宽字符查找函数(从右往左找)
//param
// opStr:查找字符串
// c:目标字符
inline PWSTR TStrRChr(PWSTR opStr, WCHAR c)
{return wcsrchr(opStr, c);
}//字符查找函数(从右往左找)
//param
// opStr:查找字符串
// c:目标字符
inline PCSTR TStrRChr(PCSTR opStr, CHAR c)
{return strrchr(opStr, c);
}
字符串类型转换
Windows提供了API用以ASCII和Unicode之间的转化。
/** 将多字节字符串转换为宽字符串* @param* uCodePage:标识一个与多字节字符串相关的代码页号。一般设定为CP_ACP* dwFlags:设定另一个控件,它可以用重音符号之类的区分标记来影响字符* pMultiByteStr:要转换的多字节字符串* cchMultiByte:转换字符串的长度。如果传递-1,则该函数用于确定源字符串的长度* pWideCharStr:指向转换后获得的字符串缓存* cchWideChar:缓存的最大值*/
int MultiByteToWideChar(UINT uCodePage,DWORD dwFlags,PCSTR pMultiByteStr,int cchMultiByte,PWSTR pWideCharStr,int cchWideChar
);/** 将宽字符串转换为多字节字符串* @param* uCodePage:标识一个与多字节字符串相关的代码页号。一般设定为CP_ACP* dwFlags:设定另一个控件,它可以用重音符号之类的区分标记来影响字符.一般设为0* pWideCharStr:要转换的款宽字符串* cchMultiByte:转换字符串的长度。如果传递-1,则该函数用于确定源字符串的长度* pWideCharStr:指向转换后获得的字符串缓存* cchWideChar:缓存的最大值* pDefaultChar:当一个字符转换失败时的默认替代字符* pfUsedDefaultChar:本次操作是否存在为转换成功而用了默认字符的情况。有为TRUE,无为FALSE*/
int MultiByteToWideChar(UINT uCodePage,DWORD dwFlags,PCWSTR pWideCharStr,int cchMultiByte,PSTR pMultiByteStr,int cchWideChar,PCSTR pDefaultChar,PBOOL pfUsedDefaultChar
)
关于这两个函数如何使用,我在TEString.h实现了ASCII和Unicode字符串。
//ASCII(char)字符串转Unicode
inline void TAsciiToUnicode(PCSTR source, PSTR dest, int destBufferSize)
{size_t size = MultiByteToWideChar(CP_ACP, 0, source, -1, NULL, 0);size = size > destBufferSize ? destBufferSize : size;ULONGLONG len = sizeof(WCHAR) * size;MultiByteToWideChar(CP_ACP, 0, (LPCCH)source, -1, (LPWSTR)dest, len);dest[size + 1] = '\0';
}//Unicode字符串转ASCII(char)
inline void TUnicodeToAscii(PCWSTR source, PSTR dest, int destBufferSize)
{size_t size = WideCharToMultiByte(CP_ACP, NULL, source, -1, NULL, 0, NULL, FALSE);size = size > destBufferSize ? destBufferSize : size;WideCharToMultiByte(CP_ACP, NULL, (LPCWCH)source, -1, (LPSTR)dest, size, NULL, FALSE);
}
这样,我们得到了一些简单的字符串操作函数合集。这些函数对于文件系统来说足够使用了。
Part1:操作文件名和文件路径
文件名和文件路径构成了文件的一个标识。对于文件路径,我们想要获取的属性主要有: