PSP 开发/文件系统/读取写入文件
读取和写入文件是需要了解的重要内容。从保存配置,加载配置,包含额外的文件,加载图像等任务都变得可用。
文件句柄是唯一的标识符,指向一个特定的打开的文件。PSPDEV 使用术语唯一标识符 (UID)。文件句柄可以通过在系统中打开文件生成。可以同时打开多个文件,但会增加不必要的复杂性。没有已知的文件打开容量限制。在程序的整个生命周期中打开所有文件是一种不好的做法。除了代码复杂性之外,在多线程情况下,可能会在文件进行 IO 操作时关闭并重新打开文件。
PSPDEV 提供了 sceIoOpen() 函数,它接收一个 const char* 路径,一系列附加的位布尔值(一个 int)和一个 IEEE 标准 chmod 八进制整数。路径是一个指向目标文件在文件系统中的位置的简单字符串。前一篇文章 讨论了不同的文件系统。该函数返回用于 IO 操作的文件的 UID(文件句柄)。sceIoOpenAsync() 函数是异步版本,它不会在打开时阻塞当前线程。
如何访问文件以位布尔值的方式表示。PSP_O_RDONLY 允许从文件读取,但如果按位或 PSP_WRONLY,它将允许读取和写入文件。使用 PSP_O_RDWR 代替,它已经被计算为 PSP_O_RDONLY | PSP_O_WRONLY。打开一个不存在的文件将返回小于 0 的错误,除非提供了 PSP_O_CREAT。提供 PSP_O_CREAT 仅确保文件存在。所有写入操作都将从第一个字节开始修改,覆盖任何数据。为了防止这种情况,使用 PSP_O_TRUNC,它会清除文件中的其余部分,只保留写入的数据。如果想写入文件的末尾,使用 PSP_O_APPEND。PSP_O_APPEND 适用于多次写入。例如,实时追加到日志。PSP_O_EXCL 应该在文件已存在时设置一个错误,但是,在 PPSSPP 中它被破坏了。
文件权限超出了本文的范围。最后一个数字是一个八进制数字,表示文件权限。查看 w:Chmod 了解更多信息。通常使用 0777,这意味着创建者可以读写执行,用户组可以读写执行,其他人可以读写执行。它授予所有人完全访问权限。文件权限在 PPSSPP 中似乎被破坏了。
示例
// open a file for reading only, if it doesn't exist, create it
// this is functionally equivalent to creating a new file
// to just close and do nothing with, for it is blank.
int uid = sceIoOpen("umd0:/example.file", PSP_O_RDONLY | PSP_O_CREAT, 0777);
PSPDEV 提供了 sceIoClose() 函数,它只接收一个文件句柄。关闭文件后,必须打开一个新的句柄才能继续使用该文件。此函数应该只在文件操作结束时调用。不要在多个线程中进行文件操作,其中两个线程共同管理打开和关闭文件以及 IO 操作。这样做会在文件被读取或写入时关闭文件。异步版本是 sceIoCloseAsync()。这对于关闭许多文件以继续执行程序很有用,而不是等待每个文件逐个关闭。
示例
// open a file for reading only
int uid = sceIoOpen("umd0:/example.file", PSP_O_RDONLY, 0777);
// close the file
if(uid >= 0) sceIoClose(uid);
打开文件句柄后,就可以写入文件了。使用 sceIoWrite() 处理写入文件,它接收要写入的文件句柄、要写入的 const char* (const char[]) 以及输出的字节数。该函数返回实际写入的字节数。写入的字节数少于或多于预期都是错误。如果输出一个 char*,sizeof() 函数将只返回 4。这是因为 sizeof() 将获取指针的大小,而不是实际的输出。可以使用 strlen(),它是 string.h 的一部分。但是,使用 char[] 和 sizeof() 会更容易。当使用 char[] 时,必须减去 1,否则字符串空终止符 '\0' 将被写入文件。存在 sceIoWriteAsync() 方法。
示例
// open a file for writing only, create file if not exist, truncate
int uid = sceIoOpen("umd0:/example.file", PSP_O_WRONLY | PSP_O_CREAT | PSP_O_TRUNC, 0777);
// write to the file
const char[] data = "Hello\nWorld\n!";
sceIoWrite(uid, data, sizeof(data)-1); // or strlen(data)
// close the file
if(uid >= 0) sceIoClose(uid);
打开文件句柄后,就可以从文件读取了。使用 sceIoRead() 处理从文件读取,它接收要读取的文件句柄、要读取到的 char* (char[]) 以及要读取的字节数。该函数返回实际读取的字节数。读取的字节数少于或多于预期都是错误。存在 sceIoReadAsync() 方法。
读取可以通过预先分配空间来完成。只有在无法确定文件长度时才需要这样做。如果二进制文件不存在文件头或文件是文本并且将完全读取,则可以使用 sceIoStat 结构体以及 sceIoGetstat() 函数,该函数接收 const char* (char[]) 路径和对 SceIoStat 结构体的引用。SceIOStat 结构体包含有关文件的信息。成员 st_size 以字节为单位告诉文件大小。
加载文件之前需要分配内存。不要忘记包含 stdlib。建议使用 char* 而不是 char[],因为 char* 强制需要调用 malloc 或 calloc。这允许您释放数据并丢弃指针的值以防止错误。它还通过将数据放在动态的堆内存中而不是自动的堆栈内存中,来阻止堆栈被大量字节淹没。
示例
// generate stats about the file
SceIoStat info;
sceIoGetstat("umd0:/example.file", &info);
// open a file for writing only, create file if not exist, truncate
int uid = sceIoOpen("umd0:/example.file", PSP_O_WRONLY | PSP_O_CREAT | PSP_O_TRUNC, 0777);
// allocate memory
char* data = (char*)calloc(info.st_size+1, sizeof(char));
if(data == 0) crash(0, "Memory Allocation", "Memory allocation failed!");
// read from file into data, append null terminator
sceIoRead(uid, data, info.st_size);
data[info.st_size] = '\0'; // info.st_size - 1 would be index worthy
// close the file
if(uid >= 0) sceIoClose(uid);
// free memory, null the pointer
free(data);
data = 0;
文件操作有很多可能会出现的错误。检查所有错误非常重要,这样程序就不会遇到未定义的行为。文件 IO 是一个示例,其中可能会发生多个预期的错误。创建将操作封装到范围内的函数,同时处理错误,可以创建更高效的系统。
将指针设置为 0 的主要原因,或者更确切地说,是将指针设置为 null,是为了防止程序成功使用该字符串,方法是阻止读取该字符串。释放数据只将分配的部分标记为可分配 - 它不会将所有内容都设置为 0。释放一个字符串然后调用 strlen() 将返回长度,即使它已被释放。某些错误可能在程序使用可分配的数据时发生。这可以在程序完成之后启用调试,以检查此类错误。它还可以在运行时启用立即调试,其中接收到的数据与预期的数据不符,例如从内存位置 0 读取数据。
sceIoGetstat()、sceIoOpen() 和 sceIoClose() 方法如果返回的整数小于 0,则返回错误状态。sceIoRead() 和 sceIoWrite() 方法返回读取的字节数和写入的字节数。如果读取或写入的字节数与预期不符,则表示发生了错误。
获取 SceIoStat 文件统计信息
void check_file(SceIoStat* info, const char* path) {
// open file description
int status;
if((status = sceIoGetstat(path, info)) < 0)
crash(status, "Checking File", "File not found or no access!");
}
打开文件
int open_file(const char* path, int params, int chmod) {
// open file handle
int uid = sceIoOpen(path, params, chmod);
if(uid < 0)
crash(uid, "Opening File", "File not found or no access!");
return uid;
}
关闭文件
void close_file(int uid) {
// close file handle
int status;
if((status = sceIoClose(uid)) < 0)
crash(status, "Close File", "File could not be closed!");
}
写入文件
void write_file(int uid, const char* out, int size) {
// write to file
if(sceIoWrite(uid, out, size) != size)
crash(uid, "Writing File", "File wrote incorrect number of bytes!");
}
从文件读取
void read_file(int uid, char** out, int size) {
// allocate, read into buffer, pad with \0
char* buffer = calloc(size+1, sizeof(char));
if(buffer == 0)
crash(0, "Memory Allocation", "Memory allocation failed!");
int read = sceIoRead(uid, buffer, size);
if(read != size)
crash(read, "Read File", "File size read doesn't match expected!");
buffer[size] = '\0';
*out = buffer;
}
虽然提供的函数处理错误检查,但它们充当包装器。传递给所有函数(除了 read_file)的所有数据与 sceIo*() 函数相同。使用这些函数类似于使用 PSPDEV 中提供的基本函数,但具有错误检查功能。本节旨在演示其用法。以下函数使用一个名为 test.txt 的文件,该文件包含纯文本格式数据。请参阅 工作示例 部分以了解该文件。该文件应该放在生成的 EBOOT.PBP 旁边。
要从文件读取,需要文件的路径。使用 EBOOT.PBP 的根目录,可以将两个字面量串联起来以获得指向文件的正确绝对路径。struct SceIoStat 在作用域结束后不会保留。
- 获取文件路径
- 为指针和 struct SceIoStat 保留数据
- 使用 check_file() 填充 struct SceIoStat
- 使用适当的 chmod 和 attribs 打开文件
- 使用 SceIoStat st_size 将文件读入保留的指针
- 关闭文件
- 使用数据
- 释放数据
- 将保留的指针设置为 0
void do_example1() {
const char* src = ROOT "file.txt";
// Reading file (existent) src > (char*) file_data
char* file_data;
SceIoStat info;
check_file(&info, src);
int uid = open_file(src, PSP_O_RDONLY, 0777);
read_file(uid, &file_data, (int)info.st_size);
close_file(uid);
printf("Read data:\n");
printf("%s\n", file_data);
free(file_data);
file_data = 0;
}
要写入文件,需要文件的路径。使用 EBOOT.PBP 的根目录,可以将两个字面量串联起来以获得指向文件的正确绝对路径。当 SceIoStat 离开作用域时,无需清理它。
- 获取文件路径
- 获取要写入的数据
- 使用适当的 chmod 和 attribs 打开文件
- 使用字符串长度减去 EOF 字符串终止符 '\0' 将数据写入文件
- 关闭文件
void do_example2() {
const char* new = ROOT "test2.txt";
const char output[] = "Hello World 123\nabc";
// Writing new file output[] > (non-existent) test2.txt
int uid = open_file(new, PSP_O_WRONLY | PSP_O_CREAT | PSP_O_TRUNC, 0777);
write_file(uid, output, sizeof(output)-1);
close_file(uid);
}
要复制文件,需要文件的路径。使用 EBOOT.PBP 的根目录,可以将两个字面量串联起来以获得指向文件的正确绝对路径。复制文件涉及两次打开、一次写入、一次读取和两次关闭操作。本节涉及与前面两个示例完全相同的内容。
- 获取文件路径
- 为指针和 struct SceIoStat 保留数据
- 使用 check_file() 填充 struct SceIoStat
- 使用适当的 chmod 和 attribs 打开 src 文件
- 使用 SceIoStat st_size 将文件读入保留的指针
- 关闭文件
- 使用适当的 chmod 和 attribs 打开 dest 文件
- 使用字符串长度减去 EOF 字符串终止符 '\0' 将数据写入文件
- 关闭文件
- 释放数据
- 将保留的指针设置为 0
void do_example3() {
const char* src = ROOT "test.txt";
const char* out = ROOT "diff.txt";
// Copy file test.txt > diff.txt
char* file_data;
SceIoStat info;
check_file(&info, src);
// multiple files can be open at once, but since using one variable
// closing the file would be wise before losing the data
int uid = open_file(src, PSP_O_RDONLY, 0777);
read_file(uid, &file_data, (int)info.st_size);
close_file(uid);
uid = open_file(out, PSP_O_WRONLY | PSP_O_CREAT | PSP_O_TRUNC, 0777);
write_file(uid, file_data, (int)info.st_size);
close_file(uid);
free(file_data);
file_data = 0;
}
要进行测试,只需调用创建的函数即可。如果通过指示当前操作的打印内容进行填充,则了解输出将有助于在出现错误时进行判断。
int main(int argc, char** argv)
{
// basic init
setupExitCallback();
pspDebugScreenInit();
sceDisplayWaitVblankStart();
pspDebugScreenSetXY(0, 0);
printf("Reading file\n");
do_example1();
printf("Done.\n");
printf("Writing file\n");
do_example2();
printf("Done.\n");
printf("Copying file\n");
do_example3();
printf("Done.\n");
// Sleep thread
sceKernelSleepThread();
sceKernelExitGame();
return 0;
}
您也可以将 stdio.h 中的 FILE 结构与 fopen、ftell、fseek 等一起使用。但是,存在“open”、“close”、“read”、“write”函数,这些函数内联了本文介绍的函数。它们使用 _O_* 而不是 PSP_O_*。口头上说,O_*。还存在这些函数的 Windows 版本:“_open”、“_close”、“_read”和“_write”。这些方法应该只用于支持跨平台,因为它们只是内联函数,并降低了其可扩展性。
TARGET = file_io
OBJS = main.o ../common/callback.o
INCDIR =
CFLAGS = -O2 -G0 -Wall
CXXFLAGS = $(CFLAGS) -fno-exceptions -fno-rtti
ASFLAGS = $(CFLAGS)
LIBDIR =
LIBS =
LDFLAGS =
EXTRA_TARGETS = EBOOT.PBP
PSP_EBOOT_TITLE = FileIO
PSPSDK=$(shell psp-config --pspsdk-path)
include $(PSPSDK)/lib/build.mak
- main.c : http://pastebin.com/zXu5QkiW
- test.txt : http://pastebin.com/H01X8iYJ