580 likes | 801 Views
第十章. Symbian 文件操作. 本 章 目 标. 完成本章内容之后我们将能够: 了解 Symbian OS 对文件的操作 掌握 Symbian OS 生成资源文件方法. 概述. 本章综述一组文件相关的主题,从存档系统本身到资源和位图文件。对于如此广泛的一组主题,不可能面面俱到。不过,我们的目的是提供主要的信息和一些能够有效使用该系统的实例。. 存档系统服务. 这一节描述作为全部其他 Symbian OS 基于文件服务基础的基本底层服务。. 文件名称及其操作.
E N D
第十章 Symbian文件操作
本 章 目 标 • 完成本章内容之后我们将能够: • 了解Symbian OS对文件的操作 • 掌握Symbian OS生成资源文件方法
概述 本章综述一组文件相关的主题,从存档系统本身到资源和位图文件。对于如此广泛的一组主题,不可能面面俱到。不过,我们的目的是提供主要的信息和一些能够有效使用该系统的实例。
存档系统服务 这一节描述作为全部其他Symbian OS基于文件服务基础的基本底层服务。
文件名称及其操作 • Symbian OS文件由文件规范标识,最多为256个字符。和在DOS中一样,文件规范由下列部分组成: • 设备或驱动器,例如c:。 • 路径,例如\Document\Unfiled\,其中目录名称由反斜杠(“\”)分开。 • 文件名称。 • 可选文件扩展名,由点号(“.”)与文件名分开。 • 存档系统支持最多26个驱动器,从a:到z:。在Symbian OS手机上,z:驱动器总是保留为系统ROM,c:驱动器总是内部读写驱动器。不过,在一些手机上,它可能只有有限的容量。d:以后的驱动器可以是内部的,也可以包含可移动介质。不应该假定可以写入全部驱动器:除了z:,许多手机拥有一个或多个只读驱动器。
文件名称及其操作 按照对于文件规范中对长度的总体限制, 目录名、文件名或扩展名可以是任意长度。存档系统保存这种名称的大小写,但是对于名称的全部操作与大小写无关。显然,这意味着,在同一个目录中,不能有两个或更多文件的名称只是部分字母大小写不同。除驱动器外,在其他组成部分中,文件规范可以包含通配符“?”(单个字符)和“*”(任意字符序列)。
文件名称及其操作 尽管大多数Symbian OS应用程序不这么做,但可以在文件的规范中包括文件扩展名。Symbian OS应用程序不依赖扩展名确定文件类型。相反,它们使用一个或多个存储在文件内的UID保证文件类型匹配应用程序。 文件名称使用TParse类和它的成员函数来构造和操作。例如,为了设置TParse实例包含文件规范C:\Documents\Oandx\Oandx.dat,可以使用: _LIT(KFileSpec,"C:\\Documents\\Oandx\\Oandx.dat"); TParse fileSpec; fileSpec.Set(KFileSpec,NULL,NULL);
文件名称及其操作 在这段代码之后,可以调用TParse的getter函数,确定该文件规范的不同组成部分。例如,filespec.Drive()包含字符串“C:”,fileSpec.Path()包含“\Documents\Oandx\”。 Set()函数使用3个文本参数,从上面代码已经看到,第一个参数是TDesC引用,包含要解析的文件规范。第二个和第三个参数是指向其他两个TDesC描述符的指针,其中一项或两项可以是NULL。第二个参数(相关的文件规范)如果存在,它指向的文件规范用于提供第一个文什规范中缺少的组成部分。如果使用了第三个参数,它应该指向默认文件规范,从中获取第一个和第二个参数未提供的组成部分。路径、文件名或扩展名可以包含通配符“?”或“*”,分别表示任意单个字符或任意字符序列。
文件名称及其操作 TParse拥有TFileName的实例,TFileName是一个TBuf<256>。这是一个庞大对象,如果可能,应该避免它的使用。尽可能创建包含文件规范的自定义更小缓冲,并使用TParsePtr(引用可修改缓冲)或TParsePtrC(引用常量缓冲)。
文件服务器会话 Symbian OS文件服务器提供使用户程序能够操作驱动器、目录和文件以及在文件中读写数据的基本服务。 与全部服务器一样,文件服务器使用墓于会话的通信,将客户端操作转换为发送给服务器的消息。请求的函数在服务器中执行,然后将结果传回客户端。因此,为了使用文件服务器,首先需要已连接的文件服务器会话,它由RFs类的实例表示。 使用文件服务器的通用模式如下(忽略错误处理): RFs session; session. Connect(); … session. Close();
文件服务器会话 在连接和关闭RFs之间,可以使用它打开任意数量的文件或目录,或执行其他与文件相关的操作。如果愿意,你可以在应用程序的生存期保持文件服务器会话打开。更可取的方式是,在关闭会话之前,确保正确关闭基于文件的所有打开资源。在任何情况下,当关闭会话时服务器将清除所有与会话关联的服务器端资源。 事实上,在GUI应用程序中,不需要打开文件服务器会话,由于控件环境已经有一个打开的RFs,可以使用iCoeEnv->FsSession()访问它。打开文件服务器会话是一种开销较高的操作,因此只要有可能,应该使用现有的会话,而不是创建自己的会话。
文件服务器会话 • RFs类提供与文件系统相关的许多有用操作,包括: • 创建、移除和重命名目录,分别使用MkDir()、MkDirAll()、RmDir()和Rename()。 • 使用Delete()和Rename()删除或重命名文件。 • 通过Att()、SetAtt()、Modified()和SetModified()读出和更改目录、文件属性。 • 使用NotifyChange()(和NotifyChangeCancel())通知更改。 • 使用Drive()、SetDriveName()、Volume()和SetVolumeLabel()操作驱动器和卷。 • 通过使用ReadFileSection(),在不打开文件情况下查看文件数据。
文件服务器会话 • 使用AddFileSystem()、MountFileSystem()、DismountFileSystem()和RemoveFileSystem()添加和移除文件系统。 • Symbian OS SDK中说明了这些函数及其用法。 • 大多数与RFs相关的函数无状态,换句话说,函数调用的结果不依赖以前调用的任何函数。这就是为什么通常可以为了自己的目的使用该控件环境的RFs。然而,RFs的确有一个状态项:它的当前目录。当打开RFs时,它的当前目录通常设置为c:\,但是,可以使用SetSessionPath()更改一个打开RFs使用的当前目录,或者使用SetDefaultPath()更改初始全部未来RFs对象的当前目录。
文件服务器会话 当前日录包括驱动器以及目录名。因此,与DOS不同,没有每个驱动器一个当前目录的概念。 如果操作当前目录或依赖当前目录,确保使用自己的RFs,而不使用共享RFs。在这种情况下,必须处理与会话关联的错误。如何处理它们依赖于会话如何存储。如果它在一个类中声明为成员数据,使用包含如下所示代码行的类定义: RFs iFs; 然后,只需使用如㈠圮码连接文件会话(从允许退出的函数): User::LeaveIfError(iFs,Connect()); 同时在类析构函数中按下面代码关闭会话: iFs.Close ()
文件服务器会话 另一种方案是,在栈上声明文件服务器会话,此时需要更加小心。需要将该会话放在清除栈上,最好的方法是使用清除栈的CleanupClosePushL(),如下所示: RFs myFs; User::LeaveIfError (myFs.Connect()); CleanupStack::CleanupClosePushL(myFs); … //可能退出的文件会话操作 … CleanupStack::PopAndDestroy (); 记住,连接文件服务器会话是开销大、耗时的操作,因此除非有充足的理由不共享控件环境中存在的会话,否则均应该这么做。
目 录 目录包含文件和其他目录,每一个目录由一个目录项表示。通过RDir类允许打开目录并读取它包含的项。 有几种不同的方式读取目录的内容。最直接了当、但不一定是最有效的方式如卜所示。 void ReadDirContentsL(RFs& aFs, const TDesC& aDirName) { RDir myDir; TInt err; User::LeaveIfError(myDir. Open(aFs,KDirName, KEntryAttNormal | KentryAttDir)); TEntry currentEntry;
目 录 FOREVER { err=myDir.Read(currentEntry); if (err) { break; //无更多项或者其他错误 } // 处理该项 } myDir. Close () if (err != KErrEof) // EOF表示无更多项可读 { User::LeaveIfError (err); } }
目 录 每次调用Read()读取一项,在内存使用需要最小化的情况下有用。然而,多次调用服务器函数效率很低,因此RDir也提供了Read()的重载,在单次调用中将全部项读入一个TEntryArray。与许多其他涉及与服务器通信的函数一样,另外两个重载提供从活动对象使用的两种类型Read()的异步版本。 除了Rdir的Read()函数外,RFs还提供一组GetDir()函数,为了在单次调用中将日录的内容读入CDir。从下列代码可见,这创建与前一个示例类似的项列表,GetDir()也允许定义结果列表的排列顺序。
目 录 void ReadDirContentsL(RFs& aFs, const TDesC& aDirName) { CDir* dirList; User :: LeaveIfError (aFs. GetDir (aDirName, KEntryAttNormal, ESortByName, dirList)); //处理该项 delete dirList; }
目 录 可以更改目录项的属性,包括隐藏、系统、只读和存档位。有真正明确意义的惟一位是“只读”。如果文件定义为只读,那么将不能写入或删除它。为了与VFAT严格匹配,也支持其他属性,但是它们在Symbian OS中并不重要,因此最好不要使用它们。 Symbian OS按UTC维护目录项时间为准,而不是按本地时间,因此没有时区更改导致混淆的风险。
文 件 在文件服务器会话内,单个文件由RFile对象表示,RFile对象提供打开和操作文件的方式。例如,为了打开一个文件并添加数据,可以使用: TInt WriteToFileL(RFs& aFs, const TDesC& aFileName, const TDesC8& aData) { RFile file; TInt err = file.Open(aFs,aFileName,EFileWrite); if(err = = KErrNotFound)//文件不存在,因此创建它 { err = file .Create(aFs,aFileName,EFileWrite); }
文 件 User :: LeaveIfError (err); CleanupStack :: CleanupClosePushL (file); User :: LeaveIfError (file. Seek {ESeekEnd, 0) ); User :: LeaveIfError (file .Write (aData)); CleanupStack::PopAndDestroy(); // 关闭文件 }
文 件 • 这个示例尝试打开一个现有文件,但是如果文件不存在,则创建该文件。除了Open()和Create(),RFile还提供了其他两种打开文件的方式: • Replace():删除现有文件并创建新文件。 • Temp():打开临时文件并给它分配名称。 • 在这个示例中,文件按二进制文件打开,具有非共享的写入权限;但是文件也可以按文本文件打开,支持其他多种访问模式,包括共享写入和独占或共享读取。通常在打开文件时定义访问模式,不过,可以在文件打开时使用ChangeMode()更改它。如果正在使用共享写入权限,可以使用Lock()声明对某个文件区域的临时独占权限,以后再使用UnLock()解除锁定。
文 件 • 当第一次打开文件时,当前读写位置设置为文件的开始。可以使用Seek()移动到不同的位置,示例使用它移动到文件的结尾,准备添加更多数据。RFile提供多种重载的Write()函数,全部从TDesC8写入8位数据。其中一半是同步函数,例如上面示例中使用的函数:其他是异步函数,用于从活动对象调用。 • 存在相应的多种Read()重载函数,也提供同步和异步版本,均是将8位数据读入TDes8。 • 除非读写相当少量的信息,否则应该总是使用异步函数,以保证应用程序在可能的长时间读写序列过程中保持响应。
文 件 从以上简短讨论可见,RFile只提供最基本的读取和写入函数,只操作8位数据。因此RFile不是很适合读取或写入在SymbianOS应用程序中可能出现的丰富数据类型。
使用.ini文件 除了应用程序的文档文件,应用程序架构还提供对应用程序打开、读取和修改辅助文件的支持。按照约定,该文件的名称与应用程序相同,但是具有扩展名.ini;因此,这样的文件称做.ini文件。 .ini文件的意图是,存储独立于应用程序正在处理的文档数据的全局设置和首选项。于是,原则上应用程序可以打开不同的文档,而不影响现有的首选项设置。 实际上,运行在手机上的应用程序趋向只使用单个文档,所以并不真正需要使用.ini文件。基于这一事实,Series 60 UI禁用应用程序结构对.ini文件的支持,但是如果需要,通过用调用CEikApplication的OpenIniFileLC()的版本替换CAknApplication的OpenIniFileLC(),可以轻易恢复。例如:
使用.ini文件 CDictionaryStore*CMyAppApplication::OpenIniFileLC(RFs& aFs) const { return CEikApplication::OpenIniFileLC(aFs); } 除了为独占读取/写入访问打开应用程序的.ini文件(并将它推入清除栈),如果该文件不存在,这个函数将创建该.ini文件,并替换损坏的文件。应用程序架构调用该函数,例如记录最后打开的文件,应用程序代码也可以调用它。
使用.ini文件 .ini文件由派生于CDictionaryStore基类的CDictionaryFileStore的实例表示。这些类的命名略微令人费解, 由于它们没有和流词典关联。同时由于它们不是派生于CStreamStore,所以它们不表示流存储器。尽管这样,使用上也有相似之处:词典存储器包含与UID关联的流,通过RDictionaryReadStream和RDictionaryWriteStream类的方式读取和写入流,使用的方式与流存储器相同。 然而,一个重要的差别是,词典存储器从不包含多个简单流列表,与流存储器中可能的复杂网络不同。此外,你可以直接使用UID访问流,如图6.4所示,而不是通过流词典。
使用.ini文件 图6.4 词典存储器
使用.ini文件 下列简单的示例,设计用作应用程序的AppUi的成员函数,演示从应用程序的.ini文件写入和读取。 void WriteToIniFileL(RFs& aFs) { CDictionaryStore* iniFile = Application()- >OpenIniFileLC(aFs); RDictionaryWriteStream stream; stream. AssignLC(*iniFile, TUid::Uid(0xl01ffac5)); // 根据UID直接访问 TUintl6 i = 0x3456; stream << i; stream. CommitL (); iniFile->CommitL (); CleanupStack::PopAndDestroy(2); // 流和iniFile }
使用.ini文件 void ReadIniFileL(RFs& aFs) { CDictionaryStore* iniFile = Application()- >OpenIniFileLC(aFs); RDictionaryReadStream readStream; readStream. OpenLC(*iniFile, TUid::Uid(0xl01ffac5)); // 根据UID直接访问 TIntl6 i; readStream >> i; CleanupStack::PopAndDestroy(2); // readStream和iniFile }
资源文件 Symbian OS将资源文件和位图分开,使用不同的工具生成它们。资源编译器和位图转换器都生成二进制数据文件,与应用程序的可执行文件平行交付。另一方面,Windows使用支持图标和图形以及基于文本资源的资源编译器,资源编译器直接将资源编译到应用程序可执行文件中。 与Windows开发人员不同,Symbian OS开发人员的目标是广泛的硬件平台。资源文件和位图格式独立于目标平台,但是对于每种平台,可执行文件可能需要不同的格式。将资源和位图彼此分开并和应用程序的可执行文件分开引入一个抽象层,该抽象层减少第三方开发人员在不同的硬件平台之间移值应用程序所需的努力。另外,它为本地化提供优秀的支持。将文本与图形和可执行文件分开有助于翻译过程,并允许多语言应用程序作为一个可执行文件与许多语言特定的资源文件一起提供。
资源文件 资源编译器从简单文本源文件或资源脚本(按约定扩展名为.rss)生成Symbian OS资源文件。处理的第一个阶段使用标准C预处理器,因此,资源脚本与C程序词法约定相同,包括源文件注释和C预处理器指令。 编译过程的输出是一个二进制资源文件,名称与源文件相同,但是扩展名为.rsc,并生成一个扩展名为.rsg的头文件。.rsg文件包含定义的符号常量列表,资源文件中的每项资源有一个对应的符号常量。在应用程序源文件中使用#include包含这个文件,提供对资源的访问。 1. 源文件语法 内置资源数据类型请参见表6.1。
资源文件 表6.1 内置资源数据类型
资源文件 这些类型用于定义资源的数据成员,如下所示。 资源文件可以包含表6.2所列的语句类型。 表6.2 资源文件可包含的语句类型
资源文件 2. STRUCT语句 STRUCT语句的形式如下: STRUCT struct-name[BYTE|WORD]{struct-member-list} struct_name定义结构的名称。名称必须以字母开头,并使用大写字符。它可以包含字母、数字和下划线,但是不能使用空格。可选的BYTE或WORD关键字是为了用于有可变长度的结构。它们没有影响,除非该结构用作另一个结构的成员,此时,它们导致结构的数据分别在前面冠以一个长度BYTE或长度WORD。
资源文件 struct_member_list是成员初始值列表,以分号隔 离,并且包围在大括弧{}内。成员可以是内置类型之 一,前面定义的结构或数组。一般为成员提供默认值, 常常是数字零或空字符串。下列示例摘自eikon.rh,演 示STRUCT语句的许多功能。注意,数组成员没有定义它 的元素的数量或类型。 STRUCT DIALOG { LONG flags=0; LTEXT title=""; LLINK pages=0; LLINK buttons=0; STRUCT items[]; //一个数组 LLINK form=0; }
资源文件 如在这种情况下,STRUCT语句按约定放在扩展名为.rh的单独文件中(资源头文件),并使用#included包含在资源脚本中。在所安装的SDK的\epoc32\include目录中,查看不同.rh文件的内容会有所帮助。 3. RESOURCE语句 RESOURCE语句可能是最重要的、当然也是最频繁使用的语句。该语句的形式如下: RESOURCE struct_name[id]{member_initializer_list}
资源文件 struct_name表示前面遇到的STRUCT语句。在大多数应用程序资源脚本中,这意味着在一个通过#included包含的.rh文件中定义的结构。id是可选的符号资源ID。如果定义了id,它必须是小写的,并且以字母开头。另外,它可以包含字母、数字和下划线,但是不能包含空格。member_initializer_list由成员初始值的列表组成,由分号隔开并包围在大括弧{}内。 下列示例摘自杀三子程序的资源脚本,演示使用前面介绍的DIALOG结构构建的资源。
资源文件 RESOURCE DIALOG r_oandx_first_move_dialog { title="First player"; flags=EAknDialogSelectionList; buttons=R AVKON SOFTKEYS_OK_CANCEL; items= { DLG_LINE { type=EAknCtSingleListBox; id=EOandXPlayerListControl; control=LISTBOX { flags=EAknListBoxSelectionList; array_id=r_oandx_player_list; }; } }; }
资源文件 在这种情况下,items数组只包含一个项,它本身是一个DLG_LINE结构。注意,资源不一定按相应结构中定义的顺序声明初始值。这个示例中的资源不提供DIALOG结构的pages和form初始值,因此它们的值将使用结构中定义的默认值。 RESOURCE语句的标点原则尽管第一眼看起来不明显,但实际上相当简单: 1. 任何类型的赋值语句以分号终止。 2. 列表中的项,除了最后一项,以逗号终止。 3. 其他情况下,不需要标点。
资源文件 例如: RESOURCE ARRAY r_oandx player_list { items= { LBUF { txt="\tNoughts first";//原则1 }, //原则2 LBUF { txt="\tCrosses first"; //原则1 } //原则2 }; //原则1 } //原则3
资源文件 4. ENUM语句 为了保证资源脚本和C++程序对符号常量使用相同的值,资源编译器支持常量的enum(和#define)定义,使用与C++类似的语法。按约定,这些定义包含在.hrh文件中。扩展名.hrh意在表示该文件适合于作为.h文件包含在C++源文件中,或作为.rh文件包含在资源脚本中。 5. NAME语句 资源脚本必须包含一条NAME语句,该语句必须出现在第一条RESOURCE语句之前。NAME关键字后面必须跟随一个大写名称,最多包含4个字符,例如: NAME OANX
资源文件 应用程序通过在生成的.rsg头文件中发布的符号ID识别每个资源。ID的前导20位从NAME语句中提供的名称生成,从而识别包含该资源的资源文件。ID的其余12位识别文件内的单个资源——这意味着资源文件限制为包含不超过4095个资源。 然而,深一层的影响是,应用程序可以从多个资源文件访问资源,只要应用程序访问的资源没有同样名称。只要不使用系统资源文件所用的名称,通常能够保证应用程序满足这个条件。例如,应该避免以A(在Series 60中)和Q(在UIQ中)开头的名称。如果也避免名称EIK、CONE、BAFL和其他Symbian OS组件名称,应该可以安全使用。
资源文件 6. 资源字符串本地化 在本书中使用的几个示例应用程序将它们的文本字符串留在资源脚本文件中。那是因为它们针对开发人员,不打算将文本字符串翻译为其他语言。将文本保存在它的资源内能够帮助程序员理解资源的目的和结构。 在真实的应用程序中,有不同的考虑,特别是如果打算将应用程序翻译为一种或更多种不同语言之时。翻译者不一定是程序员,并且可能发现经过翻译处理后,保持资源脚本的常规结构很困难。因此, 良好做法是在单独的文件中存储全部资源文本字符串。 要做的全部工作是用符号标识符替换资源脚本中的每个文本字符串,在单独的文件中关联该标识符和原始字符串。
资源文件 例如,不使用包含如下内容的资源脚本: RESOURCE_MENU_PANE r_oandx_menu { items= { MENU_ITEM { command=EOandXNewGame; txt="New game"; } }; }
资源文件 将资源修改为如下内容: RESOURCE MENU_PANE r_oandx_menu { items= { MENU_ITEM { command=EOandXNewGame; txt=text_new_game_menu; } }; }
资源文件 并且在单独的文本本地化文件中,将使用 rls_string关键字关联符号ID和字符串: rls_string text_new_game_menu "New game" 显然,需要在资源脚本中通过#included包含该文本本地化文件。 可以在文本本地化文件中包括C或C++风格的注释,通知翻译者(和程序员自己)每个文本项出现的上下文,并提供约束方面的信息,例如允许的字符串最大长度。事实上,这样做有双重优势:翻译者只看文本和注释,程序员只看资源脚本,不必查看可能伪装资源结构的注释。
资源文件 关于文本本地化文件使用的文件扩展名,没有特别的约定。在Symbian中,开发人员倾向于使用.rls扩展名(代表“资源可本地化字符串”),但是Series 60建议使用.loc扩展名。
资源文件 7. 编译资源文件 资源编译器通常作为应用程序生成过程的一部分被调用,从IDE内或从命令行执行。如前所述,除了创建二进制资源文件,资源编译器也生成一个头文件,扩展名为.rsg,包含该文件中包含的每个资源的符号ID。需要在应用程序的源文件中通过#included包含这个文件,这就是为什么生成Symbian OS程序时,资源编译器在运行C++编译器之前运行的原因。.rsg文件总是生成到\epoc32\include,但是如果生成的文件与\epoc32\include中已存在的文件相同,则不更新现有的文件。这意味着,如果更改资源文件而不更改资源ID(例如改变文本字符串),并非必须重新编译应用程序。