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配列だったもので…。そこで知識が止まってるという…。
[ ツッコむ ]
以上、1 日分です。