2024/01/14(日) [n年前の日記]
#1 [prog] C言語でiniファイルの読み書きをしたい
C/C++ と OpenGL を使って、Windows用のスクリーンセーバを作りたい。しかし、スクリーンセーバの設定パラメータを、どこにどうやって保存しようか…。
一般的なスクリーンセーバは、えてしてWindowsのレジストリに設定を記録するらしいけど、レジストリの読み込みはともかく、書き込みなんて怖くてやりたくない。ここは一つ、iniファイルの読み書きで勘弁してもらえないだろうか。
でも、C/C++ でiniファイルの読み書きってどうやったらいいんだろう。ということでそのあたりを調べて実験してみた。
環境は、Windows10 x64 22H2 + MinGW (gcc 6.3.0)。
一般的なスクリーンセーバは、えてしてWindowsのレジストリに設定を記録するらしいけど、レジストリの読み込みはともかく、書き込みなんて怖くてやりたくない。ここは一つ、iniファイルの読み書きで勘弁してもらえないだろうか。
でも、C/C++ でiniファイルの読み書きってどうやったらいいんだろう。ということでそのあたりを調べて実験してみた。
環境は、Windows10 x64 22H2 + MinGW (gcc 6.3.0)。
◎ WritePrivateProfileStringを使う :
ググったところ、iniファイルの読み書きについては、Windows なら WritePrivateProfileString()、GetPrivateProfileString() といった関数が使えるらしい。
_INIファイル(Win32API)(C言語) - 超初心者向けプログラミング入門
_iniファイルを読み込む - わびさびサンプルソース
Windowsに特化してるあたりがなんだかちょっと気になるけれど、これがもし Linux なら、ini ファイルではなく .xxxxrc 等のファイルに設定を保存しそうではあるし、iniファイルにアクセスする時点で、そのプログラムは Windows上でしか動かさないやろ、という気もするのでこの際使ってしまうことにした。
そんな感じで、こうなった。実行ファイルと同じ場所(ディレクトリ)に test.ini が無かったら作成して、その test.ini の内容を読み込んで printf() で出力するだけのプログラム。
_01_write_ini.c
_Makefile
make でビルドして 01_write_ini.exe を作る。
DOS窓(cmd.exe)上で 01_write_ini.exe を実行すると、以下のような出力をして、test.ini という iniファイルも作成された。
test.ini の内容は以下のような感じ。
_test.ini
再度実行すると、test.ini が存在するので、test.ini を読み込んで内容の表示だけをする。
_INIファイル(Win32API)(C言語) - 超初心者向けプログラミング入門
_iniファイルを読み込む - わびさびサンプルソース
Windowsに特化してるあたりがなんだかちょっと気になるけれど、これがもし Linux なら、ini ファイルではなく .xxxxrc 等のファイルに設定を保存しそうではあるし、iniファイルにアクセスする時点で、そのプログラムは Windows上でしか動かさないやろ、という気もするのでこの際使ってしまうことにした。
そんな感じで、こうなった。実行ファイルと同じ場所(ディレクトリ)に test.ini が無かったら作成して、その test.ini の内容を読み込んで printf() で出力するだけのプログラム。
_01_write_ini.c
// ini file read and write sample #include <stdio.h> #include <stdlib.h> #include <shlwapi.h> #include <windows.h> #define INIFILENAME "test.ini" #define SECNAME "ssstarsgl_config" // global work int wait = 15; int speed = 1000; int number = 500; int fps_disp_enable = 0; int main(void) { char cdir[MAX_PATH]; char filepath[MAX_PATH]; // get current directory GetCurrentDirectory(MAX_PATH, cdir); // create save file path PathCombine(filepath, cdir, INIFILENAME); printf("Current Directory : %s\n", cdir); printf("ini file path : %s\n\n", filepath); if (!PathFileExists(filepath)) { // Not found ini file printf("Not found %s\n", filepath); printf("Create %s\n\n", filepath); char buf[256]; // create/write ini file sprintf(buf, "%d", wait); WritePrivateProfileString(SECNAME, "wait", buf, filepath); sprintf(buf, "%d", speed); WritePrivateProfileString(SECNAME, "speed", buf, filepath); sprintf(buf, "%d", number); WritePrivateProfileString(SECNAME, "number", buf, filepath); sprintf(buf, "%d", fps_disp_enable); WritePrivateProfileString(SECNAME, "fps_disp_enable", buf, filepath); } if (!PathFileExists(filepath)) { printf("Not found %s\n\n", filepath); return -1; } else { printf("Found %s\n", filepath); // read ini file printf("Read ini file\n"); wait = GetPrivateProfileInt(SECNAME, "wait", -1, filepath); speed = GetPrivateProfileInt(SECNAME, "speed", -1, filepath); number = GetPrivateProfileInt(SECNAME, "number", -1, filepath); fps_disp_enable = GetPrivateProfileInt(SECNAME, "fps_disp_enable", -1, filepath); // dump results printf("wait = %d\n", wait); printf("speed = %d\n", speed); printf("number = %d\n", number); printf("fps_disp_enable = %d\n", fps_disp_enable); } return 0; }
_Makefile
01_write_ini.exe: 01_write_ini.c gcc $< -o $@ -lshlwapi -Wall -O3 .PHONY: clean clean: rm -f *.exe rm -f *.o rm -f test.ini
make でビルドして 01_write_ini.exe を作る。
DOS窓(cmd.exe)上で 01_write_ini.exe を実行すると、以下のような出力をして、test.ini という iniファイルも作成された。
> 01_write_ini.exe Current Directory : D:\home\prg\c_lang\gcc\ini_read_write\take1 ini file path : D:\home\prg\c_lang\gcc\ini_read_write\take1\test.ini Not found D:\home\prg\c_lang\gcc\ini_read_write\take1\test.ini Create D:\home\prg\c_lang\gcc\ini_read_write\take1\test.ini Found D:\home\prg\c_lang\gcc\ini_read_write\take1\test.ini Read ini file wait = 15 speed = 1000 number = 500 fps_disp_enable = 0
test.ini の内容は以下のような感じ。
_test.ini
[ssstarsgl_config] wait=15 speed=1000 number=500 fps_disp_enable=0
再度実行すると、test.ini が存在するので、test.ini を読み込んで内容の表示だけをする。
> 01_write_ini.exe Current Directory : D:\home\prg\c_lang\gcc\ini_read_write\take1 ini file path : D:\home\prg\c_lang\gcc\ini_read_write\take1\test.ini Found D:\home\prg\c_lang\gcc\ini_read_write\take1\test.ini Read ini file wait = 15 speed = 1000 number = 500 fps_disp_enable = 0
◎ 少し解説 :
分かった範囲で、少し解説。
_Win32APIでパス名とファイル名を連結する - プログラムを書こう!
_PathCombine
ファイルの存在チェックは、PathFileExists() が使える。
_PathFileExists - Windows APIの部屋
注意点。PathCombine() や PathFileExists() を使うには、#include <shlwapi.h> が必要らしい。また、gcc/g++ に -lshlwapi を渡してライブラリをリンクしないといけない。
iniファイルへの書き込みは、WritePrivateProfileString() を使う。もし、指定したiniファイルが存在しなかったら、自動でiniファイルを作成して書き込んでくれるらしい。
iniファイルからの読み込みは、GetPrivateProfileString() を使って文字列として読み込むのが一般的らしいけど、今回は Int値を読み込めれば十分なので、Int値を返してくる GetPrivateProfileInt() を使って済ませることにした。この GetPrivateProfileInt()、もし、指定したキー名が無かったときは、デフォルト値を返すらしい。
とりあえず、これで iniファイルの読み書きはできそう。
ただ、このプログラム、英数字のみのPATHにしか対応できない気もする…。PATHを格納するバッファを char で用意してるので、日本語文字列を含むディレクトリやファイル名には対応できないのではないかな…。いや、日本語文字列でフォルダを作ってその中で試したら、一応動いているようではあるけれど…。
- 実行している exeファイルの場所(ディレクトリ)を取得するには、GetCurrentDirectory() を使う。
- ディレクトリのPATHに、ファイル名を結合したい時は、PathCombine() を使う。
_Win32APIでパス名とファイル名を連結する - プログラムを書こう!
_PathCombine
char filepath[MAX_PATH]; // get current directory GetCurrentDirectory(MAX_PATH, cdir); // create save file path PathCombine(filepath, cdir, INIFILENAME); printf("Current Directory : %s\n", cdir); printf("ini file path : %s\n\n", filepath);
ファイルの存在チェックは、PathFileExists() が使える。
_PathFileExists - Windows APIの部屋
if (!PathFileExists(filepath)) { // Not found ini file // ... } else { // Found ini file // ... }
注意点。PathCombine() や PathFileExists() を使うには、#include <shlwapi.h> が必要らしい。また、gcc/g++ に -lshlwapi を渡してライブラリをリンクしないといけない。
iniファイルへの書き込みは、WritePrivateProfileString() を使う。もし、指定したiniファイルが存在しなかったら、自動でiniファイルを作成して書き込んでくれるらしい。
WritePrivateProfileString("セクション名", "キー名", "キーの文字列", "iniファイルのPATH");
// create and write ini file char buf[256]; sprintf(buf, "%d", wait); WritePrivateProfileString(SECNAME, "wait", buf, filepath); sprintf(buf, "%d", speed); WritePrivateProfileString(SECNAME, "speed", buf, filepath); sprintf(buf, "%d", number); WritePrivateProfileString(SECNAME, "number", buf, filepath); sprintf(buf, "%d", fps_disp_enable); WritePrivateProfileString(SECNAME, "fps_disp_enable", buf, filepath);
iniファイルからの読み込みは、GetPrivateProfileString() を使って文字列として読み込むのが一般的らしいけど、今回は Int値を読み込めれば十分なので、Int値を返してくる GetPrivateProfileInt() を使って済ませることにした。この GetPrivateProfileInt()、もし、指定したキー名が無かったときは、デフォルト値を返すらしい。
int GetPrivateProfileInt("セクション名", "キー名", デフォルト値, "iniファイルPATH")
// read key value from ini file wait = GetPrivateProfileInt(SECNAME, "wait", -1, filepath); speed = GetPrivateProfileInt(SECNAME, "speed", -1, filepath); number = GetPrivateProfileInt(SECNAME, "number", -1, filepath); fps_disp_enable = GetPrivateProfileInt(SECNAME, "fps_disp_enable", -1, filepath);
とりあえず、これで iniファイルの読み書きはできそう。
ただ、このプログラム、英数字のみのPATHにしか対応できない気もする…。PATHを格納するバッファを char で用意してるので、日本語文字列を含むディレクトリやファイル名には対応できないのではないかな…。いや、日本語文字列でフォルダを作ってその中で試したら、一応動いているようではあるけれど…。
[ ツッコむ ]
以上です。