MFCプログラミング(1)
CFileまたはCStdioFileクラスを使用してファイル出力

2021年4月10日

どもです。
突然ですが、仕事でMFCを使用することになりました。
MFCはこれまで触ったことがなかったので、色々とお勉強をしながら開発を進めています。
その中で、少し(あるいは少しどころではなく)ハマったことを書いていきます。

さっそく今回は、「ファイルへの出力」でハマった内容について書きます。

1.背景(簡単に)

入力内容をCSV形式でファイルに出力する、という機能を実装する際にハマりました。
CString型で出力したいデータを作成してファイルに書き出す、という単純な処理なので油断していました。

2.内容

CString型で出力するデータを作成しているので、最初は「CStdioFile」クラスの「WriteString」メソッドを使用して出力しようとしました。
しかし、実際に出力したファイルをテキストでディタなどで開いてみると、「余計な改行」が付与されて出力されました。
調べてみると、CString型のデータの中には含まれていない「ラインフィード(0x0A)」が付与されていました。
この「ラインフィード(0x0A)」ですが、どうやら「CStdioFile」クラスが自動で付与するものらしいです。
MSDNによれば、「CStdioFile」クラスは、

「ラインフィードをテキストモードでCStdioFileオブジェクトに書き込むと、バイトペア(0x0D 0x0A)がファイルに送信される」
MSDN

という仕様とのことです。
そのため、出力したいCString型の末尾に付与した改行コード(0x0D 0x0A)の「0x0A」が「0x0D 0x0A」に置換され、結果として「0x0D 0x0D 0x0A」が出力されていました。
この1つ目の「0x0D」が、出力ファイルでは「余計な改行」と見えていたのです。

3.対応

3.1.策1:改行コードを変更する

出力する文字列の末尾に付加する改行コードを「0x0D 0x0A」から「0x0A(のみ)」に変更する、という方法です。
実装、及び使用するクラスとか色々考えると、最も適切な方法です。

ところが、実装している機能では、行の末尾以外にも改行コードがあり、かつ行末の改行コードと区別するために、途中の改行コードを「0x0A」としています。
採用した場合、途中の改行コードと末尾の改行コードの区別ができなくなります。
そのため、この方法は採用できません。
(CSVの仕様を考えると色々ダメですが、そこはツッコんではいけません。)

3.2.策2:CFileクラスを使用する

ファイル出力に使用するクラスを、CFileクラスに変更する、という方法です。
CFileクラスには、CString型をそのまま出力するメソッドは用意されていません。
用意されているのは、

virtual void Write(
const void* lpBuf,
UINT nCount);

というメソッドです。(MSDN)

このメソッドを使用して、ファイルにデータを出力します。
そのため、CString型から文字列情報を取得、さらにそのサイズ(バイト)を計算しなければなりません。
(※間違っても、出力するCString型のポインタをconst void*にキャストする、ということはしてはいけません。)
具体的には、下記のようなコードになるかと思います。

CString DataToWrite = CreateDataToWrite(DataSrc);
void* BufferToData = (void*)(DataToWrite.GetBuffer());
UINT Count = DataToWrite.GetLength() * sizeof(TCHAR);

CFile File;
File.Open(CsvFilePath, CFile::modeWrite | CFile::modeCreate);
File.Write(BufferToData, Count);
File.Close();

※DataSrcとCsvFilePathは適宜設定します。

4.まとめ

今回は、MFCで文字列をファイルに出力しようとした際にハマった内容について書きました。
CFile、CStdioFile、あるいはそれ以外について、きちんと仕様を理解していれば、ハマらない内容でした…。