Win32API の ReadFile の使い方が分からない

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
アバター
sujiniku
記事: 39
登録日時: 5年前

Win32API の ReadFile の使い方が分からない

#1

投稿記事 by sujiniku » 5年前

最終目的としてRPG用のデータファイル(マップデータやアイテムデータなど)のテキストファイル(もしくはCSV)を読み取る機能を作りたいと思い、
ReadFile について練習していますが、次の事がわからず、困っています。

ReadFile はその性質上、LPVOID 型でしか、読み取ったデータを保管できないと思います。
しかし、Win32APIで文字列を扱う際には、TCHAR などの文字列型でないと、TEXTOUTなどが使用できません。
なので、TEXTOUTなどがうまく行かず、読み取り内容の表示方法および確認方法が分からずに困っています。

いったい、どうすればよいのでしょうか?


書いたコードは、とりあえずグローバル変数として

コード:

// 追加
static DWORD wReadSize;
static RECT rect;
static LPSTR strFile;
// 以上、追加
を記述しており、

wWinMAin には、

コード:

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

	HANDLE hFile;

	hFile = CreateFile(
		TEXT("test.txt"), GENERIC_READ, 0, NULL,
		OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL
	);
	if (hFile == INVALID_HANDLE_VALUE) {
		MessageBox(
			NULL, TEXT("ファイルを開けませんでした"),
			TEXT("エラー"), MB_OK			
		);
		return 1;
	}

	strFile = (LPSTR)malloc(GetFileSize(hFile, NULL));
	ReadFile(hFile, strFile, GetFileSize(hFile, NULL), &wReadSize, NULL);

	CloseHandle(hFile);
を追加し。

また、手本にした、Wisdomソフトさまのコードでは、
http://wisdom.sakura.ne.jp/system/winap ... in113.html
DrawText を使っているので、それで文字出力をするコードを、下記のように

コード:

// TODO: HDC を使用する描画コードをここに追加してください...

		GetClientRect(hWnd, &rect);


		DrawText(hdc, (LPCWSTR)strFile, wReadSize, &rect, DT_LEFT); 
			// Visual Stuido のエラーメッセージでLPCWSTRと互換性が無いと出たので. エラーコード: C2664
		
		// DrawText(hdc, strFile, wReadSize, &rect, DT_LEFT); 
			// 手本の元サイトには、こうあるが、これだとコンパイルエラー

		EndPaint(hWnd, &ps);

と記述しています。

しかし、これを実行しても、文字化けをしてしまい、意味不明の■■や漢字が大量に何十個も出てきます。


また、読み取り対象ファイル"test.txt"には

コード:

This is test of reading.
test, test, test.
と書いています。


なお、Wisdomソフトさまのコードを参考にしましたが、
http://wisdom.sakura.ne.jp/system/winap ... in113.html
上記のコードでは何箇所か、私の目的のために変更しています。

コードが手本サイトのそのままだと、私のVisual Studioの環境では実行できない事と、
最終的に私がゲームのソースコードに組み込んで使いたいため、
デスクトップウィザードで作成されるWin32テンプレートにコードを組み込んだ形に、
当掲示板で紹介したコードを変更してあります。

結城紬
記事: 42
登録日時: 6年前

Re: Win32API の ReadFile の使い方が分からない

#2

投稿記事 by 結城紬 » 5年前

sujiniku さん、こんにちは。

test.txt の文字コードは多分 ASCII ですね?
文字列型も理由の一つですが、問題は文字コードです。
DrawText(W) API に入力する文字コードは Unicode (16ビットLE)でなければいけません。
ASCII から Unicode に変換するには、例えば MultiByteToWideChar API や、ATLが使える場合は CA2CW マクロなどを使います。

アバター
みけCAT
記事: 6734
登録日時: 13年前
住所: 千葉県
連絡を取る:

Re: Win32API の ReadFile の使い方が分からない

#3

投稿記事 by みけCAT » 5年前

結城紬 さんが書きました:
5年前
DrawText(W) API に入力する文字コードは Unicode (16ビットLE)でなければいけません。
DrawTextW APIだと入力がUnicodeでないといけないので、
Shift_JISの文字列をそのまま使いたければDrawTextA APIを使う、という方法もあります。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

アバター
sujiniku
記事: 39
登録日時: 5年前

Re: Win32API の ReadFile の使い方が分からない

#4

投稿記事 by sujiniku » 5年前

とりあえずWindowsアクセサリのメモ帳で、「名前をつけて保存」を使って、 test.txt を Unicode に変換して試したところ、

コード:

This is test of reading.
test, test, test.■■te■■■■B5(文字化け)■g■g部f■f (文字化け)gcg ■gc
のように、末尾に十数個くらいの■と、謎の文字列が表示されます。

とりあえずテキスト本文の英文(This is test of reading.)は読めるようになりましたが、
まだ何か、オカシイようです。

アバター
みけCAT
記事: 6734
登録日時: 13年前
住所: 千葉県
連絡を取る:

Re: Win32API の ReadFile の使い方が分からない

#5

投稿記事 by みけCAT » 5年前

ReadFile関数で読み込んだサイズは「バイト数」で返されるのに対し、
DrawText関数で指定するサイズは「文字数」です。
したがって、1文字を複数バイトで表す場合、文字数が実際より長いと勘違いしてしまうようです。
DrawText関数に渡しているwReadSizeを、wReadSize / sizeof(TCHAR)としてみてください。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

アバター
sujiniku
記事: 39
登録日時: 5年前

Re: Win32API の ReadFile の使い方が分からない

#6

投稿記事 by sujiniku » 5年前

DrawTextで正しく表示されました。DrawText の件は解決しました(※ 下記に追加の質問があります)。
ありがとうございます。

追加の質問なのですが、
ゲームでTextOutも使いたいので(私は普段、DrawTextよりもTextOutのほうを使っている)、
TextOutでも同様にテキストファイルtest.txtから読み取った文字列を画面表示してみたいと思い、

テストのため WM_PAINT のTODO以下を次の書き換え、

TextOutのテストコード

コード:

// TODO: HDC を使用する描画コードをここに追加してください...

		TCHAR buf[100];
		_stprintf_s(buf, 100, TEXT("%s"), (wchar_t*)strFile);

   		TextOut(hdc, 10, 30, buf, lstrlen(buf));

		EndPaint(hWnd, &ps);
コンパイルして試してみましたが、

TextOutの結果

コード:

This is test of reading.■test, test, test.■■te■B
のように、改行されずに、表示されてしまいます。

改行がされずにその位置で文字化けをしているという事から、
たぶん、少なくとも文字コードの設定ミスを含んでしまってるのだろうとは思いますが・・・。

なお、_stprintf_sで strFile を (wchar_t*)で変換してる理由は、
Visual Studioのエラー報告で

コード:

C4477 `swprintf_s` : 書式文字列 `%s` には 型 `wchar_t *` の引数が必要ですが、可変個引数
	 1 は型 `LPSTR` です
が、表示されたからです。

結城紬
記事: 42
登録日時: 6年前

Re: Win32API の ReadFile の使い方が分からない

#7

投稿記事 by 結城紬 » 5年前

TextOut API は改行文字は使えません。あきらめましょう。

アバター
sujiniku
記事: 39
登録日時: 5年前

Re: Win32API の ReadFile の使い方が分からない

#8

投稿記事 by sujiniku » 5年前

TextOut API は改行文字は使えません。あきらめましょう。
質問があります。
今後の、ファイル操作のプログラムを作る上で、私の取るべき方向性を、皆様に教えていただきたいと思います。

私の都合ですが、最終的にゲームに適用するため、CSVファイルのように、データを1行ずつ解釈できるようにしたいと思っています。

コード:

名前,最大HP,素早さ,ゴールド,経験値
スライム,2,2,1,2
コボルト,12,6,10,5
こういう感じで、ゲーム用データベース的な感じのCSVファイルを作ろうと思っています。

なのでファイルを1行ずつ読み取る機能、もしくは、いっぺんにファイル全体を読み取った後に1行ごとに文字列を分解する機能が必要あります。

ゲームエンジンのようなものの開発をするにあたり、ウィンドウズでファイルを1行ずつ読み取りをするには、fgetsを使うべきでしょうか、それともReadFileを使うべきでしょうか?

どちらを使うにせよWindows用のファイル読み取りの機能の実装には、手間が掛かりそうなので、
あらかじめ、fgetsとReadFileのどちらで書くべきかを聞いておきたいと思います。(後から修正するのは、とても大変なので)


C言語のfgetsを使ったほうが、古くからある関数だから、学びやすくて、よろしいのでしょうか?

それとも、
Yahoo知恵袋『ReadFile()のような関数を用いて1行単位で文字列を取得する方法 』
https://detail.chiebukuro.yahoo.co.jp/q ... 1469253434
のように、ReadFileで実装したほうが良いのでしょうか? Windowsのファイル読み取りはReadFileに一元化したほうが、よろしいのでしょうか?

結城紬
記事: 42
登録日時: 6年前

Re: Win32API の ReadFile の使い方が分からない

#9

投稿記事 by 結城紬 » 5年前

方向性という意味で言えば、そもそも論としては、平成ももうすぐ終わろうかという2018年に、Windows API を直接使ってゲームエンジンを作ろうというのがそもそもありえない話です。
RPGを作りたいのであればウディタとかRPGツクールとか専用のツールがありますし、汎用ツールならUnity、百歩譲って DxLib でしょう。
CSVを読み込むのだってライブラリを使えば済む話です。
それでもあえてやるというのであれば、ただのお勉強ということになりますが、それならどちらがやりやすいかとか、学びやすいかと気にするのは道理に合わない話です。学ぶ必要があると思ったほうをやればいいのです。

アバター
sujiniku
記事: 39
登録日時: 5年前

Re: Win32API の ReadFile の使い方が分からない

#10

投稿記事 by sujiniku » 5年前

吉里吉里zがたしかWin32APIを使ってたような記憶があったので、
吉里吉里zのソースコードを見てみたら、CreateFileとwfopenの両方を使っていますね。
基本はCreateFileでファイルオープンをするけど、fgetsを呼び出す際にはwfopenを使っているようです。

私も、このやり方を真似ようと思います。

アバター
sujiniku
記事: 39
登録日時: 5年前

Re: Win32API の ReadFile の使い方が分からない

#11

投稿記事 by sujiniku » 5年前

とりあえずtest.txtから1行だけ読み取って画面に表示するプログラムを作るため、fgetsを使おうと思い、下記のコードを書きましたが、文字化けしていまいます。

コード:

// TODO: HDC を使用する描画コードをここに追加してください...

		FILE *fp2;
		char str1[100];
		char str2[100];

		fopen_s(&fp2, "test.txt", "r");

		fgets(str1, 100 - 1, fp2);
		fgets(str2, 100 - 1, fp2);

		wchar_t wbuf2[100];
		mbstowcs(wbuf2 , str1 ,100);

		TextOut(hdc, 30, 80, wbuf2, lstrlen(wbuf2));
		fclose(fp2);
のコードを書きましたが、文字化けをしてしまいます。
文字化けの結果、「ybT」みたいな文字が表示されます。

なお、

コード:

#pragma warning(disable:4996) 
を使用しています。

どうすればいいか、検討もつきません。教えてください。

かずま

Re: Win32API の ReadFile の使い方が分からない

#12

投稿記事 by かずま » 5年前

mbstowcs を使うためには、setlocale が必要です。

コード:

#include <windows.h>
#include <stdio.h>   // fopen_s, fclose, fgets
#include <stdlib.h>  // mbstowcs
#include <string.h>  // strchr
#include <locale.h>  // setlocale

wchar_t wbuf2[100];
size_t n2;

void onCreate(HWND hwnd)
{
	FILE *fp2;
	if (fopen_s(&fp2, "test.txt", "r")) return;
	char str1[100];
	fgets(str1, 100, fp2);
	// char str2[100];
	// fgets(str2, 100, fp2);
	fclose(fp2);
	char *p = strchr(str1, '\n');
	if (p) *p = '\0';  // erase '\n'
	n2 = mbstowcs(wbuf2 , str1 ,100);
}

void onPaint(HWND hwnd)
{
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hwnd, &ps);
	TextOut(hdc, 30, 80, wbuf2, n2);
    EndPaint(hwnd, &ps);
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
    switch (msg) {
	case WM_CREATE:  onCreate(hwnd); break;
    case WM_PAINT:   onPaint(hwnd); break;
    case WM_DESTROY: PostQuitMessage(0); break;
    default: return DefWindowProc(hwnd, msg, wp, lp);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE h, HINSTANCE p, LPSTR cl, int cs)
{
    setlocale(LC_ALL, "");  // for mbstowcs 
    WNDCLASS wcl = { 0, WindowProc, 0, 0, h, NULL,
		LoadCursor(NULL, IDC_ARROW), (HBRUSH)(COLOR_WINDOW+1), NULL, L"wc" };
    if (!RegisterClass(&wcl)) return FALSE;
    if (!CreateWindowEx(WS_EX_COMPOSITED, L"wc", L"Title",
			WS_OVERLAPPEDWINDOW | WS_VISIBLE,
			80, 60, 640, 480, NULL, NULL, h, NULL)) return FALSE;
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0) > 0) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}

アバター
sujiniku
記事: 39
登録日時: 5年前

Re: Win32API の ReadFile の使い方が分からない

#13

投稿記事 by sujiniku » 5年前

私の環境の場合、すでに作ったファイルがwWinMainを使ってますので、
それに合わせた書き方をしたら、
画面に文字が表示されません。(コンパイル自体は出来る。)

wWinmainの箇所のコードでは、setlocaleの記述位置を、

コード:

// TODO: ここにコードを挿入してください。
		setlocale(LC_ALL, "");  // for mbstowcs
のように書いています。

wWinmain全体を書くと、

コード:

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
	_In_opt_ HINSTANCE hPrevInstance,
	_In_ LPWSTR    lpCmdLine,
	_In_ int       nCmdShow)
{
	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(lpCmdLine);

	// TODO: ここにコードを挿入してください。
		setlocale(LC_ALL, "");  // for mbstowcs
	
	// グローバル文字列を初期化しています。
	LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
	LoadStringW(hInstance, IDC_WIN32PRACTICE, szWindowClass, MAX_LOADSTRING);
	MyRegisterClass(hInstance);

	// アプリケーションの初期化を実行します:
	if (!InitInstance(hInstance, nCmdShow))
	{
		return FALSE;
	}

	HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WIN32PRACTICE));

	MSG msg;

	// メイン メッセージ ループ:
	while (GetMessage(&msg, nullptr, 0, 0))
	{
		if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	return (int)msg.wParam;
}
のようにしています。

WndProcは

コード:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_CREATE:  onCreate(hWnd); break;

	case WM_COMMAND:
	{
		int wmId = LOWORD(wParam);
		// 選択されたメニューの解析:
		switch (wmId)
		{
		case IDM_ABOUT:
			DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
			break;
		case IDM_EXIT:
			DestroyWindow(hWnd);
			break;
		default:
			return DefWindowProc(hWnd, message, wParam, lParam);
		}
	}
	break;
	case WM_PAINT:   onPaint(hWnd); break;

	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}
のように書いています。

なお小文字のhwndだとコンパイルエラーになるので、2文字目が大文字のhWndに変えてます。

onCreateやonPaintは、そのまま書いています。

YuO
記事: 947
登録日時: 13年前
住所: 東京都世田谷区

Re: Win32API の ReadFile の使い方が分からない

#14

投稿記事 by YuO » 5年前

  • TextOutの行にブレークポイントを設定する (VS2017では[デバッグ] - [ブレークポイントの設定/解除]。通常はショートカットF9を利用)
  • デバッグ実行する ([デバッグ] - [デバッグの開始]。ショートカットF5)
  • ブレークされた時点での,str1およびbuf2をローカルペインとかウオッチ系の機能を使って調べる
をやった場合のstr1およびbuf2はどうなっていますか。
  • str1が望んだ値でないならば,正しいtest.txtが開かれていないか,test.txtの中身が正しくない
  • buf2が望んだ値でないならば,mbstowcsが正しく動作していない→mbstowcsの戻り値も確認
  • str1とbuf2が共に望んだ値ならば,TextOutの使い方が正しくない(?)→固定の文字列で試す,戻り値を確認する
オフトピック
「望んだ結果が得られない」のであれば,どこかに望んだ通りの動作になっていない部分があるはずです。
その場所を知るための基本的な方法が,上記の方法(怪しい場所にブレークポイントを設定して実行,変数等を確認する)です。
ステップ実行と合わせて,IDEでの開発の基本知識になります。

アバター
sujiniku
記事: 39
登録日時: 5年前

Re: Win32API の ReadFile の使い方が分からない

#15

投稿記事 by sujiniku » 5年前

ブレークポイントの設定後に、
Visual Studio の左下にあるウィンドウのタブ「自動」、「ローカル」、「ウォッチ」のどれに切り変えて表示される変数の一覧を見ても、
そもそも str1 と str2 がないです。
一方、n2やwbuf2は一覧中に存在しています。

なお
n2 の値は 4294967295 で種類は unsigned int
wbuf2 の値は 0x00c0a2d8 L"" で種類は wchar_t[100]
です

かずま

Re: Win32API の ReadFile の使い方が分からない

#16

投稿記事 by かずま » 5年前

ブレークポイントはどこに設定したのですか?

str1 が onCreate のローカル変数だったら、
その中で止めないと、str1 は存在しません。

n2 = mbstowcs(wbuf2 , str1 ,100); の行に
ブレークポイントを設定するとどうなりますか?

アバター
sujiniku
記事: 39
登録日時: 5年前

Re: Win32API の ReadFile の使い方が分からない

#17

投稿記事 by sujiniku » 5年前

n2 = mbstowcs(wbuf2 , str1 ,100); の行に
ブレークポイントを設定するとどうなりますか?
str1の値は

0x0021f3fc <文字列中に無効な文字があります。>

と表示されます。

かずま

Re: Win32API の ReadFile の使い方が分からない

#18

投稿記事 by かずま » 5年前

sujiniku さんが書きました:
5年前
str1の値は

0x0021f3fc <文字列中に無効な文字があります。>

と表示されます。
if (fopen_s(&fp2, "test.txt", "r")) return; だとすると
test.txt の文字コードを Unicode ではなく、
Shift-JIS(ANSI) に戻してください。

なぜだか分かりますか?

アバター
sujiniku
記事: 39
登録日時: 5年前

Re: Win32API の ReadFile の使い方が分からない

#19

投稿記事 by sujiniku » 5年前

ANSIにするとtest.txtの最初の1行だけ表示されますね。期待どおりの動作です。ご指導、ありがとうございます。
Shift-JIS(ANSI) に戻してください。

なぜだか分かりますか?
たぶん

コード:

char str1[100];
で宣言してるから、ANSI以外は入らないのだろうと解釈しました。

どうしてもUnicodeをファイル読み取りしたい場合、
たぶん、fgetsではなくfgetwsなどで読み取る必要があるのだろうと思いますが、
私の現在の能力を越えてます。

返信

“C言語何でも質問掲示板” へ戻る