2024/01/19(金) [n年前の日記]
#1 [prog] C/C++で特殊フォルダ内にiniファイルを作成して読み書きしてみた
C/C++で、Windowsの特殊フォルダ、%APPDATA% のPATHを取得して、その中に iniファイルを作成して読み書きできるのか試してみた。
環境は、Windows10 x64 22H2 + MinGW (gcc 6.3.0)。
%APPDATA% は、Windows10の場合、C:\Users\(USERNAME)\AppData\Roaming になることが多いらしい。Windows XP の場合は、また違う場所になるらしいけど…。
_上野家のホームページ - 資料室 : PC/Windows/Vista/VistaとXPのフォルダ相違VistaとXPにおけるフォルダの相違
_AppData 直下の Local, LocalLow, Roaming フォルダの違い
_AppDataフォルダ直下のLocal/LocalLow/Roamingサブフォルダの違いについて。 - 量産メモ帳
環境は、Windows10 x64 22H2 + MinGW (gcc 6.3.0)。
%APPDATA% は、Windows10の場合、C:\Users\(USERNAME)\AppData\Roaming になることが多いらしい。Windows XP の場合は、また違う場所になるらしいけど…。
_上野家のホームページ - 資料室 : PC/Windows/Vista/VistaとXPのフォルダ相違VistaとXPにおけるフォルダの相違
_AppData 直下の Local, LocalLow, Roaming フォルダの違い
_AppDataフォルダ直下のLocal/LocalLow/Roamingサブフォルダの違いについて。 - 量産メモ帳
◎ ソース :
ソースは以下のようになった。
_03_write_ini.cpp
_Makefile
make と打てば、03_write_ini.exe が生成される。この exeファイルを実行すると、Windows10の場合、以下のiniファイルを作成して読み書きする。
_03_write_ini.cpp
// ini file read and write sample. support wchar_t. // write C:\Users\(USERNAME)\AppData\Roaming\dev_ini_file_rw\dev_ini_file_rw.ini // use SHGetSpecialFolderPath() #define _WIN32_IE 0x0400 #include <shlobj.h> #include <shlwapi.h> #include <windows.h> #include <stdio.h> #include <stdlib.h> #include <wchar.h> #include <tchar.h> // target directory #define INIDIR _T("dev_ini_file_rw") // target ini file name #define INIFILENAME _T("dev_ini_file_rw.ini") // section name #define SECNAME _T("ssstarsgl_config") // global work int wait = 15; int speed = 1000; int number = 500; int fps_display = 0; // write or create ini file void writeIni(TCHAR *filepath) { TCHAR buf[1024]; wsprintf(buf, _T("%d"), wait); WritePrivateProfileString(SECNAME, _T("wait"), buf, filepath); wsprintf(buf, _T("%d"), speed); WritePrivateProfileString(SECNAME, _T("speed"), buf, filepath); wsprintf(buf, _T("%d"), number); WritePrivateProfileString(SECNAME, _T("number"), buf, filepath); wsprintf(buf, _T("%d"), fps_display); WritePrivateProfileString(SECNAME, _T("fps_display"), buf, filepath); } // read ini file void readIni(TCHAR *filepath) { wait = GetPrivateProfileInt(SECNAME, _T("wait"), -1, filepath); speed = GetPrivateProfileInt(SECNAME, _T("speed"), -1, filepath); number = GetPrivateProfileInt(SECNAME, _T("number"), -1, filepath); fps_display = GetPrivateProfileInt(SECNAME, _T("fps_display"), -1, filepath); } int main(void) { TCHAR filepath[MAX_PATH]; // target ini file path TCHAR waFolderPath[MAX_PATH]; // APPDATA folder path TCHAR waTgtFolderPath[MAX_PATH]; // target folder path // get APPDATA folder path // request shlobj.h SHGetSpecialFolderPath(NULL, waFolderPath, CSIDL_APPDATA, FALSE); // wprintf(L"APPDATA folder path: [%S]\n", waFolderPath); if (PathCombine(waTgtFolderPath, waFolderPath, INIDIR) == NULL) { printf("ERROR: Can not make target folder path.\n"); return -1; } // wprintf(L"Target folder path: [%S]\n", waTgtFolderPath); { BOOL create_dir = FALSE; if (!PathFileExists(waTgtFolderPath)) { printf("Not found target folder. create.\n"); create_dir = TRUE; } else if (!PathIsDirectory(waTgtFolderPath)) { printf("Found target folder path. but, not directory. create.\n"); create_dir = TRUE; } if (create_dir) { if (!CreateDirectory(waTgtFolderPath, NULL)) { printf("ERROR: Can not create target folder.\n"); return -1; } } } if (PathCombine(filepath, waTgtFolderPath, INIFILENAME) == NULL) { printf("ERROR: Can not make ini file path.\n"); return -1; } // %s ... ANSI // %S ... UNICODE wprintf(L"ini file path : [%S]\n", filepath); if (!PathFileExists(filepath)) { // Not found ini file printf("Not found ini file. Create.\n"); writeIni(filepath); } if (!PathFileExists(filepath)) { printf("Not found ini file.\n"); return -1; } // read ini file printf("Found ini file. Read.\n"); readIni(filepath); // dump results printf("wait=%d\n", wait); printf("speed=%d\n", speed); printf("number=%d\n", number); printf("fps_display=%d\n", fps_display); return 0; }
_Makefile
03_write_ini.exe: 03_write_ini.cpp g++ $< -o $@ -lshlwapi -Wall -O3 -static .PHONY: clean clean: rm -f *.exe rm -f *.o rm -f test.ini
make と打てば、03_write_ini.exe が生成される。この exeファイルを実行すると、Windows10の場合、以下のiniファイルを作成して読み書きする。
C:\Users\(USERNAME)\AppData\Roaming\dev_ini_file_rw\dev_ini_file_rw.ini
◎ 実行結果 :
実行した結果は以下。
最初の実行時は、フォルダもiniファイルも無いので、フォルダの作成と、iniファイルの作成(書き込み)をしている。2度目の実行時は iniファイルがあるので、iniファイルの読み込みだけをしている。
> 03_write_ini.exe Not found target folder. create. ini file path : [C:\Users\USERNAME\AppData\Roaming\dev_ini_file_rw\dev_ini_file_rw.ini] Not found ini file. Create. Found ini file. Read. wait=15 speed=1000 number=500 fps_display=0 > 03_write_ini.exe ini file path : [C:\Users\USERNAME\AppData\Roaming\dev_ini_file_rw\dev_ini_file_rw.ini] Found ini file. Read. wait=15 speed=1000 number=500 fps_display=0
最初の実行時は、フォルダもiniファイルも無いので、フォルダの作成と、iniファイルの作成(書き込み)をしている。2度目の実行時は iniファイルがあるので、iniファイルの読み込みだけをしている。
◎ 少し解説 :
特殊フォルダの取得には、SHGetSpecialFolderPath() を使うらしい。
SHGetSpecialFolderPath() に、CSIDL_* という値を渡してやることで、どの特殊フォルダを取得するのか指定できる。以下、参考ページ。
_特殊フォルダのパスを取得する
_SHGetSpecialFolderPathに設定できるCSIDL
_Windowsの特殊フォルダのパスを取得する - わびさびサンプルソース
_特殊フォルダのパス名等の取得(?)
ディレクトリのPATHと、その後に続くディレクトリ名 or ファイル名の結合には、PathCombine() を使った。利用するには shlwapi.h の include と、libshlwapi.a のリンク(-lshlwapi) が必要。
_PathCombine
ファイルやディレクトリが存在するかどうかは、PathFileExists() を使う。また、そのPATHがディレクトリかどうかは、PathIsDirectory() で調べられる。利用するためには shlwapi.h の include が必要。
_PathFileExists - Windows APIの部屋
_PathIsDirectory - 車輪のx発明 B.G's Blog
ディレクトリの作成は、CreateDirectory() を使った。これもおそらく Windows限定だろうけど…。winbase.h で定義されているけれど、windows.h の中で winbase.h を include してあるので、windows.h を include してあれば使える。
_ディレクトリ操作(Win32API)(C言語) - 超初心者向けプログラミング入門
iniファイルへの書き込みもしくは作成は、WritePrivateProfileString() を使う。また、iniファイルの読み込み(Int値の取得)は、GetPrivateProfileInt() を使う。これも Windows限定。
そんなわけで、特殊フォルダ %APPDATA% を取得して、その中にディレクトリを作って、更にその中に iniファイルを作成して読み書きできる、と分かった。
- Windows限定。
- 利用するためには、shlobj.h の include が必要。
- _WIN32_IE の値が 0x0400 以上じゃないと有効にならないようなので、#include <shlobj.h> の前に、#define _WIN32_IE 0x0400 を書いてみた。
#define _WIN32_IE 0x0400 #include <shlobj.h>
SHGetSpecialFolderPath() に、CSIDL_* という値を渡してやることで、どの特殊フォルダを取得するのか指定できる。以下、参考ページ。
_特殊フォルダのパスを取得する
_SHGetSpecialFolderPathに設定できるCSIDL
_Windowsの特殊フォルダのパスを取得する - わびさびサンプルソース
_特殊フォルダのパス名等の取得(?)
ディレクトリのPATHと、その後に続くディレクトリ名 or ファイル名の結合には、PathCombine() を使った。利用するには shlwapi.h の include と、libshlwapi.a のリンク(-lshlwapi) が必要。
_PathCombine
ファイルやディレクトリが存在するかどうかは、PathFileExists() を使う。また、そのPATHがディレクトリかどうかは、PathIsDirectory() で調べられる。利用するためには shlwapi.h の include が必要。
_PathFileExists - Windows APIの部屋
_PathIsDirectory - 車輪のx発明 B.G's Blog
ディレクトリの作成は、CreateDirectory() を使った。これもおそらく Windows限定だろうけど…。winbase.h で定義されているけれど、windows.h の中で winbase.h を include してあるので、windows.h を include してあれば使える。
_ディレクトリ操作(Win32API)(C言語) - 超初心者向けプログラミング入門
iniファイルへの書き込みもしくは作成は、WritePrivateProfileString() を使う。また、iniファイルの読み込み(Int値の取得)は、GetPrivateProfileInt() を使う。これも Windows限定。
そんなわけで、特殊フォルダ %APPDATA% を取得して、その中にディレクトリを作って、更にその中に iniファイルを作成して読み書きできる、と分かった。
◎ 文字列を何で扱うべきかよく分からない :
C言語で実験していた時は、ファイルのPATHその他の文字列を、安易(?)に char配列に入れて処理していたけれど、C++ で試したらエラーが続出して…。どうやら wchar_t だか WCHAR だかの配列にしないといかんらしい。
ただ、関連解説ページを眺めていたら、TCHAR配列にしてる事例を多く見かけたので、今回は TCHAR で書いてみた。
しかしそのことで、printf() を使ってもファイルPATHが正しく表示されなくなってしまって…。代わりに、wprintf() や wsprintf() を使わないといけないようだなと…。かつ、wprintf("%s", filepath); ではダメで、wprintf("%S", filepath); にしないと正しく表示されなかった。"%s" は ANSI用で、"%S" は UNICODE用、らしい。
TCHAR と言うのは、状況によって、char と wchar_t のどちらかを使ってくれるものらしい。ただ、「過去のプログラムとの互換性のために残してあるので、今から作るプログラムなら使うべきではない」という話も見かけた。
_TCHAR はもう使うな - エレクトロニクス・フィーバー
そう言われても、WCHAR にすると何故かコンパイルが通らなかったりするので…。もしかすると MinGW (gcc 6.3.0) が古いのだろうか…。何にせよ、今回は TCHAR にしてしまった。これならコンパイルが通った。
各文字列も、"hoge" ではなく、L"hoge" と書いてみたり、_T("hoge") と書いてみたり…。
このあたり、ちゃんと勉強して把握しておかないとマズイよな…。自分がC言語を勉強したり、使ってた頃って、文字列=char配列だったもので…。そこで知識が止まってるという…。
ただ、関連解説ページを眺めていたら、TCHAR配列にしてる事例を多く見かけたので、今回は TCHAR で書いてみた。
しかしそのことで、printf() を使ってもファイルPATHが正しく表示されなくなってしまって…。代わりに、wprintf() や wsprintf() を使わないといけないようだなと…。かつ、wprintf("%s", filepath); ではダメで、wprintf("%S", filepath); にしないと正しく表示されなかった。"%s" は ANSI用で、"%S" は UNICODE用、らしい。
TCHAR と言うのは、状況によって、char と wchar_t のどちらかを使ってくれるものらしい。ただ、「過去のプログラムとの互換性のために残してあるので、今から作るプログラムなら使うべきではない」という話も見かけた。
_TCHAR はもう使うな - エレクトロニクス・フィーバー
そう言われても、WCHAR にすると何故かコンパイルが通らなかったりするので…。もしかすると MinGW (gcc 6.3.0) が古いのだろうか…。何にせよ、今回は TCHAR にしてしまった。これならコンパイルが通った。
各文字列も、"hoge" ではなく、L"hoge" と書いてみたり、_T("hoge") と書いてみたり…。
- "hoge" ... char
- L"hoge" ... LPCSTR だか LPCWSTR になる?
- _T("hoge") ... TCHAR ?
このあたり、ちゃんと勉強して把握しておかないとマズイよな…。自分がC言語を勉強したり、使ってた頃って、文字列=char配列だったもので…。そこで知識が止まってるという…。
[ ツッコむ ]
以上です。