mieki256's diary



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サブフォルダの違いについて。 - 量産メモ帳

ソース :

ソースは以下のようになった。

_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

実行結果 :

実行した結果は以下。
> 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() を使うらしい。
  • 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") と書いてみたり…。
  • "hoge" ... char
  • L"hoge" ... LPCSTR だか LPCWSTR になる?
  • _T("hoge") ... TCHAR ?
何にせよ、_T("xxxx") としておけば、状況に合わせて char[] か wchar_t[] にしてくれるのだろう。たぶん。よく分からんけど。

このあたり、ちゃんと勉強して把握しておかないとマズイよな…。自分がC言語を勉強したり、使ってた頃って、文字列=char配列だったもので…。そこで知識が止まってるという…。

以上、1 日分です。

過去ログ表示

Prev - 2024/01 - Next
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project