ファイルの入出力

編集

fopenの使用許可を与える方法

編集

Win32APIには、CreateFile関数などのファイル入出力の専用コマンドが用意されている。

しかし、Win32APIでも標準C言語のfopen関数などのC言語規格の関数も使える。


しかしWindowsAPIやVisual C++ の標準設定ではfopen関数などの古いC言語の関数が禁じられている場合があるので、

その場合、使用を許可するために、下記のように#pragma warning(disable:4996)を下記のような位置に加える必要がある。


// Windowsでfopenが使えない場合
#include "stdafx.h"
#include <stdio.h>

#pragma warning(disable:4996)

のように。

初心者にとっては、fopenなどC言語規格の関数を使うほうが、書店などに標準C言語の入門書も多いので、勉強が簡単だろう。


ウィザードで自動作成したコードに、ファイル入出力のコードを追記するのが、簡単である。

(※ 白紙状態のコードから作る方法では、当ページの巻末の参考文献にあるネット上にある手本のコードが、そのコードのバージョンが古いなどの理由で、現在のWindowsでは使えないので、自分で調べなおす必要が生じる。2018年9月1日に確認。)


CreateFile

編集

さて、CreateFile関数を使う場合、たとえば、「test.txt」というファイルを作成したい場合なら、

// TODO: ここにコードを挿入してください。

の下に、下記のように書き加える。

// TODO: ここにコードを挿入してください。

	HANDLE hFile;

	hFile = CreateFile(
		TEXT("test.txt"), GENERIC_READ, 0, NULL,
		CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL
	);

	if (hFile == INVALID_HANDLE_VALUE) {
		MessageBox(
			NULL, TEXT("ファイルを作成できませんでした。"),
			TEXT("エラー"), MB_OK
		);
		return 1;
	}

	CloseHandle(hFile);

ウィザードの自動作成したコードに上記コードを追加したコードを、コンパイルする。

成功すれば、特にメッセージは表示されないが、パソコンでソースコードのあるフォルダに、「test.txt」というファイルが加わっているのが分かる。

( たとえばファイル名がWindowsProject1 なら WindowsProject1.cpp などがソースコードであり、これのあるフォルダに、一緒に「test.txt」というファイルが加わっている。

この「test.txt」は拡張子のとおり、通常のテキストファイルであるので、test.txt のアイコンをダブルクリックするなどして開くと、文字を書き足したり保存することもできる。


上述のコードのように、Win32API用の、無印C言語のファイル入出力関数ではない(つまりfopenなどではない)、ファイル入出力の関数がある。


C言語のfopenなども、stdio.h をインクルードすればWin32APIでも使用可能であるが、しかし、TCHAR型の扱いの際に、特殊な操作が必要になってしまう。

なので、Win32APIでは、なるべくCreateFileなどのウィンドウズAPI用のファイル関数を使うほうが簡単である。


なお、Win32 API にかぎらず、

一般に、ファイルの入出力のプログラミングでは、

まず、ファイルを操作する際、

ファイル操作用の変数を用意する。
ファイルを読み書きする前に、ファイルを開く必要がある。
読み書きの作業が終わったら、安全のためファイルを閉じる。閉じられた状態のファイルは、そのままでは読み書きできない。

などの特徴がある。


また、ファイルを作成したりオープンした直後に、本当にその作業が成功したかどうかの確認のためのコードを入れるのが一般的である。


なぜなら、ファイル入出力の関数は、パソコンのハードディスクを書き換えるので、エラーが起きた際、大きな被害に発展しかねない。


なので、そのような誤作動にそなえて、ファイルのオープンなどが上手くいったかを確認する必要がある。

そして、もしファイルのオープンに失敗した場合、終了する処理をコードに書くのが一般的である。(上記のコードでは、 return 1 で関数を終了させている。)


ファイルのオープンとクローズ

編集

CreateFile() 関数の機能は、その名とは違い、この機能はファイルを作成またはオープン(開く)する機能である。

やや不正確な説明をするが、わかりやすく言うと CreateFile() 関数が作成しているものは、ファイル操作用ハンドルに必要な設定情報である。より正確に言うと、ハンドル(上記のコードでは hFile )がファイルを操作できるようにするために必要な諸設定を、 CreateFile() 関数は作成している。

設定情報を作成しただけでは、どのハンドルでファイルを扱うかは指定されてないので、なので

	hFile = CreateFile(
		TEXT("test.txt"), GENERIC_READ, 0, NULL,
		CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL
	);

のように、ハンドル (上記の例では hFile )を指定する必要がある。

C言語の入門書では、「変数は、入れ物(いれもの)である」と習ったのを思い出そう。ハンドル変数( hFile )に、ファイル操作用の設定情報( createFile で作成された設定情報)を入れる必要がある。


そして、このファイル操作用ハンドルの設定の作成をする際に、目的のファイルのオープンもされる仕様になっている。(CreateFileの第一引数で、"test.txt" のように、対象のファイルを指定しているので、どのファイルを開く必要があるかを、この関数を使う関数の実行時点でOSが既に知っている。)


すでに存在するファイルに読み書きをしたい場合にも、 CreateFile() を使って対象のファイルをオープンする必要がある。


CreateFile() は、引数での指定によって、ファイル作成やファイルオープンなどの動作を指定できる。

CreateFile() によって作成されたファイルハンドルは、自動的に開かれる。

なので、目的のファイル処理が終わったら、

CloseHandle() でファイルハンドルを閉じる必要がある。


関数の内容

編集
CreateFile(
		TEXT("test.txt"), GENERIC_READ, 0, NULL,
		CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL
	);

とあるが、いくつもある引数の意味は、それぞれ

CreateFile(
		操作対象のファイル名, アクセスモード , 共有モード, セキュリティ記述子,
		作成方法, ファイル属性, テンプレートファイルのハンドル
	);

を設定している。

正確な情報は、マイクロソフトmsdn [1] を参照せよ。

アクセスモード

編集

読み取りモードでアクセスする場合には、

アクセスモードを

GENERIC_READ

にする。

書き込みモードでアクセスする場合には、

アクセスモードを

GENERIC_WRITE

にする。

共有モードとは?

編集

「共有モード」とは何かというと、上記のプログラムがファイルを開いている間、後続のアクセス関数がアクセスできるかどうかを指定することです。

共有モードを「0」にすると、ファイルハンドルを閉じてからでないと、ほかのプログラムがアクセスできません。

FILE_SHARE_READ に設定すると、ハンドルを閉じなくても、後続アクセスは読み込みだけ可能になります。

同様に、 FILE_SHARE_WRITE に設定すると、ハンドルを閉じなくても、後続アクセスは書き込みだけ可能になります。

セキュリティ記述子

編集

難しいので、説明を省略。詳しくは マイクロソフトmsdn [2] を参照せよ。

NULL にすれば、特に指定しないことになります。


「作成方法」とは

編集

名前こそ「作成方法」ですが(マイクロソフトMSDNがそう呼んでいる)、これは、ファイルが存在するとき(もしくは存在しないとき)、どう処理するかの指定です。

CREATE_NEW なら、

対象のファイルと同名のファイルが存在すれば、この関数CreateFile()は失敗する。(何も作成しない。)
対象のファイルと同名のファイルが存在すれば、この関数CreateFile()が実行される。(つまり、ファイルが作成される。)

という意味です。

他にも指定子はいろいろとありますが、すべて説明すると長くなってしまうので、詳しくは MSDN などを参考にしてください。

ファイル属性

編集

ファイルの属性を、「読み込み専用」とか、いろいろと設定できるのですが、初心者にはめんどくさいので、何も設定しないでおきましょう。

FILE_ATTRIBUTE_NORMAL を指定すれば、何も設定しないことになります。

テンプレートファイルのハンドル

編集

難しいので説明を省略。

よく分からなければ、 NULL を指定しとけばいい。

詳しくは、 マイクロソフト MSDN を参照せよ。

ファイルの読み書き

編集

総論

編集

まず、Win32APIには、ReadFile関数などのファイル入出力の専用コマンドが用意されている。

しかし、Win32APIでも標準C言語のfgets関数などのC言語規格の関数も使える。しかし、ワイド文字を扱う場合、fgetwsになることに注意しよう。


初心者にとっては、fgetwsなどC言語の関数を使うほうが簡単だろう。


さて、Win32 API でテキストファイルなどの読み書きをする際、文字コードに気をつけなければならない。

文字コードは、マイクロソフトの各ソフトウェアの標準設定に合わせておくのが、簡単である。

アクセサリ「メモ帳」などテキストエディタの文字コードは、WindowsではANSIが標準の文字コードなので、メモ帳で作成したテキストファイルの文字コードをANSIに合わせておくのが簡単である。テキストファイルの文字コードを「ANSI」に設定するには、「名前をつけて保存」の際に「文字コード」の欄を「ANSI」にすれば良い。

なお、マイクロソフト社の言う「ANSI」とは、けっして本物のANSI(いわゆるASCII(アスキー)コード)ではなく、マクロソフト社コード番号 CP932 という、ASCIIを拡張して日本JIS漢字規格など各国の規格などを追加したマイクロソフト社独自の文字コードのことである。日本では Shift-JIS として知られる。


Visual Studio の文字コードは当面のあいだ、いじらないのが安全であるが、もし、すでにいじってしまった場合、UnicodeがVisual Studioの標準設定なので、Unicodeにすればいい。

また、各アプリの文字コードと、Visual Stuido の文字コードとは、上述のように、別々のコードの場合があるので、混同しないように気をつけること。

※ ただし、2018年12月、将来的にWindows 10 ではWindowsのテキストエディタ「メモ帳」の文字コードが、従来のANSIからUTF-8に文字コードが統一される見通しになった[1]。おそらく Visual Studio の仕様も将来、この仕様変更に合わせて標準設定の文字コードがANSIからUTF-8に変更されるので、プログラマーは気をつける必要がある。


読み取らせたいテキストファイルの文字列型をどうすればいいか分からない場合、


読み取ったファイルの文章表示

編集
方法
編集

日本語の場合、ワイド文字型 wchar_t を使わないと、TextOutするのが困難になる。 char型でも不可能ではないかもしれないが、かなり面倒くさいので、ワイド文字型で説明する。

例として

読み取りたいファイル
これはテストです。
this is a test.

としよう。

まず、このテキストファイルの文字コードを「ANSI」に設定する。「名前をつけて保存」で、「文字コード」の欄を「ANSI」にすれば良い。


いっぽう、Win32APIのプログラミングでは、まず、読み取った文字列を格納するための変数を、グローバル変数で、

wchar_t str1[100];

のように wchar_t 型の文字配列を宣言する。


グローバル変数にする理由は、ファイル読み取りの関数と、画面表示などの別の関数で、読み取り結果を共有するので、グローバル変数で宣言する必要があるからである。


そして次に、プログラム中のファイル読み取りを実行したい箇所で、

 // TODO: ここにコードを挿入してください。
	setlocale(LC_ALL, "");  // ロケールの設定。空白で良い。
	FILE *fp;
	
	fopen_s(&fp, "test.txt", "r");

	fgetws(str1, 100, fp); // fgetsではなく fgetws
	
	fclose(fp);

        // 行末に改行文字がつくので、それを除去
	wchar_t *p = wcschr(str1, '\n');
	if (p) *p = '\0';  // '\n' を除去


などとして、str1 に読み取ったワイド文字を入れる。


wcschr とは、 msdn に書いてあるが、ワイド型の文字列中で、ある文字(引数で指定する)が初めて出現する場所を見つけて、その位置を返す関数である。

標準C言語に、strchr という、文字を検索して位置を返す関数がある。wcschr は strchr のワイド文字版である。


上記のプログラム例では、改行文字\nを見つけて、それをヌル文字\0に置き変えている。ファイル検索の処理プログラムでよく使う定石なテクニックである。


あとは、例えば読み取った文字列を表示したいなら、単に WM_PAINT で

 // TODO: HDC を使用する描画コードをここに追加してください...
            
	TextOut(hdc, 30, 80, str1, lstrlen(str1));

のように、すれば済む。


実行結果

座標(30,80)の位置に

「これはテストです。」

(カギ括弧なしの状態で)と表示される。


背景事情
編集

なぜ、このような wchar_t で宣言するという仕組みになってるかというと、

もちろん第一の理由は「マイクロソフト社がこういう風に作ってしまったから」であるが(マイクロソフト社の独自規格の問題なので)、

別の理由もある。


理由1

「Windowsではシステム内部の(ユーザーからは直接操作できない部分の)文字コードでは(自称)『Unicode』が使われており、

一方、Windowsのシステム外部の(ユーザーの操作する)メモ長などのOS付属アクセサリやMSエクセルなどオフィスソフトの標準の文字コードでは(自称)『ANSI』コードが標準として使われているから」

という事情がある。(マイクロソフト社の独自規格は、時代によって変更される可能性があるので(マイクロソフト社の都合により)、変更されても対応できるように、技術的な背景事情を把握しておく必要がある。)


そして、TextOutなどWin32APIのいくつかの関数では、Windowsのシステム内部では出力文字の管理は Unicode で行っているようである。

しかし、Windowsにおけるchar型は、どうやらASCIIコードや自称「ANSI」(Shift-JIS)を前提としてコンパイラ内部で処理しているようである。

このため、Windowsシステム内部の「Unicode」とWindowsシステム外部の「ANSI」という、これら2種類の文字コードの調整のため wchar_型 で宣言する手間が生じるようである。


理由2

もうひとつの理由として、英数字以外の文字の、バイト数の扱いがある。

本来、char型が1バイト型だとしたら、日本語文字などの2バイト文字は入らないハズであるが、しかし過去の経緯により、コマンドプロンプト画面用プログラムではchar型にも漢字を格納できてしまうし、printfなどで日本語もコマンドプロンプト画面に表示できてしまう。


しかし、TextOutなどWin32API専用の関数では、より厳密に型の管理をしているようであり、コマンドプロンプト画面とは違うようである。

TextOutなどWin32API専用の関数では、char型でなく wchar_t で宣言しないと、日本語を格納しづらい。

結論

上述のような様々な理由が合わさり、win32APIでは、日本語文字の格納は、wchar_t 型で行うのが望ましいだろう。


備考

もし、すでに char 型やfprintf関数などで外部ファイルの文字列を読み書きするソースコードのある場合、わざわざ宣言時の型 wchar_t 型 や TCHAR型などで読み取るように書き換えなくても、

WideCharToMultiByte()  関数 などの、文字列のための型変換の関数があるので、この関数による関数を、適切な箇所に追加すればいい。

なお WideCharToMultiByte()  関数 の場合、これを使えば文字列の型をワイド型からマルチバイトに変換できる。windows7以降(Win7も含む)のWindowsでは、普通のテキストファイルを標準C言語にある関数(fopen や fgets など)や文字列型(char 型)で外部ファイルとの読み書きをする場合、Windows内部では読み書き後の文字列データはワイド文字列として処理されている場合があるので、その場合には、この関数だけでマルチバイト文字に変換できる。

  1. ^ インプレス(企業名)、窓の杜(サイト名)、『「メモ帳」に多数の改善、BOMなしUTF-8がデフォルト保存形式に ~「Windows 10 19H1」』