|
ファイル入出力の基本
このセクションでは、MFCでのファイル入出力について説明していきます。MFCでのファイル操作の基本は、CFileクラスです。CFileクラスは、低水準入出力関数のopen()、close()、seek()、read()、write()などをラッピングしたクラスだと思っていいでしょう。しかし、Windowsの独特のクセもあるので、その辺は注意が必要です。
Windowsのファイル入出力には「テキストモード」と「バイナリモード」があります。一方、UNIXではこのようなものはありません。テキストデータであっても、バイナリデータであっても、同じように入出力します。単純にメモリ上のデータをファイルに読み書きするだけです。この意味では、「バイナリモード」しかないとも言えます。
では、Windowsではなぜ2つのモードがあるのでしょうか。それは、改行コードの違いによるものです。Windowsでは、改行コードは「CR-LF(\r\n、0x0D,0x0A)」が使われます。UNIXでは「LF(\n、0x0A)」が使われます。これは文字コードには依りません。
Windowsのテキストモードでは、ファイルに出力するときに、「\n」が「\r\n」に自動的に変換されます。また、ファイルから入力するときは、「\r\n」が「\n」に自動的に変換されます。ですので、テキストモードのファイル入出力を使うときは、プログラム上では改行コードは「\n」で扱わなければいけません。
CFileクラスは通常はバイナリ入出力を扱います。テキストモードの入出力は、CFileの派生クラスの、CStdioFileクラスを使います。
次はCFileのコンストラクタですが、3種類用意されています。
// (1)何もしない、規定のコンストラクタ
CFile::CFile();
// (2)オープン済みのファイルハンドルを使って初期化
CFile::CFile(HANDLE hFile);
// (3)指定ファイルを、指定モードでオープンする
CFile::CFile(LPCTSTR lpszFileName, UINT nOpenFlags);
|
2番目のコンストラクタは、Open()関数を呼ぶ必要はありません。また、オブジェクトが破棄されてもファイルクローズはされません。
3番目のコンストラクタは、オブジェクト作成時にファイルオープン済みになるので、Open()関数を呼ぶ必要はありません。ですが、オープン時にエラーが発生した場合はどうするのでしょうか。これは、例外処理で判断します。このコンストラクタでエラーが発生すると、CFileException型の例外がスローされます。
ただ、管理人の趣味としては、コンストラクタにエラーが発生するような処理を書くのは好きではないので、規定のコンストラクタを使って、Open()関数でファイルオープンするのが自然だと思います。
次はファイルオープン時のモード指定についてです。ここはちょっと複雑なので、きちんと整理しておきましょう。
まず、Open()関数の定義を見てみましょう。nOpenFlagsに、ファイルオープン時のいろいろなフラグをORで指定します。
| virtual BOOL CFile::Open(LPCTSTR
lpszFileName, UINT nOpenFlags, CFileException* pError = NULL); |
| 説明: |
ファイルオープン |
| 引数: |
lpszFileName:オープンするファイルのパス
nOpenFlags:オープンモード指定フラグ |
| 戻り値: |
ファイルを正常にオープンしたときは0以外、エラーの場合は0 |
フラグには、「アクセス許可モード」と「共有モード」があり、それぞれ1つは指定しないといけません。
アクセス許可モードは次の中から指定します。
| アクセス許可モード |
読み取り |
書き込み |
| CFile::modeRead |
○ |
× |
| CFile::modeWrite |
× |
○ |
| CFile::modeReadWrite |
○ |
○ |
共有モードは次の種類があります。
| 共有モード |
他プロセスからの読み取りオープン |
他プロセスからの書き込みオープン |
| CFile::shareDenyNone |
○ |
○ |
| CFile::shareDenyRead |
× |
○ |
| CFile::shareDenyWrite |
○ |
× |
| CFile::shareExclusive |
× |
× |
読み取りだけの目的のファイルであれば、CFile::modeRead | CFile::shareDenyNone、読み書きをするファイルであれば、CFile::modeReadWrite
| CFile::shareDenyWriteまたは、CFile::modeReadWrite | CFile::shareExclusiveにするのがよいと思います。
このほかに、ファイル作成に関する問題があります。オープンしようとしたときに、ファイルがなかった場合どうするか、逆にファイルがあった場合に、既存の内容を破棄するのかどうかといった問題です。
このような動作を指定するために、さらにオプションのフラグが用意されています。これを指定すると、次のように動作が変わります。
| オプションフラグ |
ファイルが存在しない場合 |
ファイルが存在する場合 |
| 指定なし |
エラー |
そのまま |
| modeCreate |
新規作成 |
既存内容破棄 |
| modeCreate |
modeNoTruncate |
新規作成 |
そのまま |
modeCreate | modeNoTruncateを指定すると、「ファイルがあればそのままオープン、無ければ新規作成」という動作になります。
「読み書き用にオープンして、オープン中は他のプロセスからは読み取りも書き込みもさせない。そして、ファイルがすでに存在すればそのままオープンし、無ければ新規作成したい。」というときは、「CFile::modeReadWrite
| CFile::shareExclusive | CFile::modeCreate | CFile::modeNoTruncate」と指定すればよいことになります。
それでは、実際の使い方を見てみましょう。基本はOpen()、Close()、Seek()、Read()、Write()です。
CFile file;
struct
{
TCHAR name[32];
int age;
int height;
int weight;
} t_person = {_T("G.Ishihara"), 60, 200, 50};
int age = 0;
int err = 0;
// (1)読み書き用にオープン
if (!err)
{
if (!file.Open(_T("C:\\fileTest.dat"),
CFile::modeReadWrite | CFile::shareExclusive |
CFile::modeCreate | CFile::modeNoTruncate)) err = 1;
}
// (2)書き込み
if (!err)
{
TRY {file.Write(&t_person, sizeof t_person);}
CATCH (CFileException, eP) {err = 1;}
END_CATCH
}
// (3)シーク
if (!err)
{
TRY {file.Seek(sizeof t_person.name, CFile::begin);}
CATCH (CFileException, eP) {err = 1;}
END_CATCH
}
// (4)読み込み
if (!err)
{
if (file.Read(&age, sizeof age) != sizeof age) err = 1;
}
// (5)クローズ(明示的)
file.Close();
|
(2)書き込み
書き込みはWrite()関数を使います。この関数はなぜか戻り値がなく、エラー発生時はCFileExceptionの例外をスローします。
| virtual void CFile::Write(const
void* lpBuf, UINT nCount); |
| 説明: |
ファイルに書き込み |
| 引数: |
lpBuf:書き込むデータ
nCount:書き込むバイト数 |
| 戻り値: |
なし(エラーの場合、例外スロー) |
(3)シーク
シークはSeek()関数を使います。この関数も、エラー発生時はCFileExceptionの例外をスローします。
| virtual ULONGLONG CFile::Seek(LONGLONG
lOff, UINT nFrom); |
| 説明: |
ファイルシーク |
| 引数: |
lOff:シークするバイト数
nFrom:シークする基点。CFile::begin、CFile::current、CFile::endのどれか |
| 戻り値: |
正常の場合、ファイル先頭からの新しいオフセット値。(エラーの場合、例外スロー) |
(4)読み込み
読み込みはRead()関数を使います。
| virtual UINT CFile::Read(void*
lpBuf, UINT nCount); |
| 説明: |
ファイルから読み込み |
| 引数: |
lpBuf:読み込みデータ用バッファ
nCount:読み込むバイト数 |
| 戻り値: |
読み込んだバイト数 |
(5)クローズ(明示的)
クローズはClose()関数を使います。この関数はデストラクタでも呼び出されるので、CFileオブジェクトをローカル変数にとった場合は、必ずしも明示的に呼び出す必要はありません。
|