イネマルのプログラミング備忘録

趣味プログラマのメモ

WSH JScript JSONファイルを使用する

はじめに

WSH JScript で、設定ファイルに対応する際に使用した方法です。
WSH(VBScript, JScript) から設定ファイルを扱う場合、JSON を使用すると楽できます。

注意

文字コードは、SJIS を使用しています。
それ以外の文字コードでは、動作検証していません。

また、Chakraスクリプトエンジンとして利用する場合は、下記を参照。
WSH JScript で、JSONファイルをパースする(Chakra を利用する場合) - イネマルのプログラミング備忘録

実装

{
  "Date": {
    "Year": 2018,
    "Date": "03/21",
    "Time": "12:34"
  },
  "Users": [
    {
      "Country": "Austria",
      "Name": "Hans Meier",
      "Age": 18
    },
    {
      "Country": "Canada",
      "Name": "Joe Blow",
      "Age": 19
    },
    {
      "Country": "Japan",
      "Name": "山田 太郎",
      "Age": 20
    }
  ]
}

  • JSONParser.bat
@if(0)==(0) echo off
title %~n0
%windir%\System32\cscript.exe //nologo //E:JScript "%~f0" %*
echo;&echo;&pause&goto:EOF
@end

/**
 * @brief  エントリーポイント用即時関数
 */
WSH.Quit(function ()
{
    // 初期化
    var jsonFilePath = ".\\sample.json";
    var savePath = ".\\output.json";
    
    var parser = GetJSONParser(jsonFilePath);
    var fso = parser.GetFileSystem();
    if (!fso.FileExists(jsonFilePath)) {
        WSH.Echo("ファイルが見つかりません。");
        return -1;
    }
    
    // 解析
    try {
        var conf = parser.Parse(jsonFilePath);
    }
    catch(e){
        WSH.Echo("解析に失敗しました。\n" + e);
        return -2;
    }
    
    // 読み込み
    WSH.Echo(conf.Date.Year);
    WSH.Echo(conf.Date.Date);
    WSH.Echo(conf.Date.Time);
    
    for (var idx = 0; idx < conf.Users.length; idx++) {
        WSH.Echo("----------------");
        var user = conf.Users[idx];
        WSH.Echo(user.Country);
        WSH.Echo(user.Name);
        WSH.Echo(user.Age);
    }
    
    // 保存
    // ユーザー情報だけ、保存
    parser.Save(savePath, conf.Users);
    
    return 0;
}());

/*
 * @brief  JSONParser 取得
 * @return JSONParser オブジェクト
 * @note
 *     JSON Object (Windows Scripting - JScript)
 *     https://msdn.microsoft.com/en-us/library/cc836458(v=vs.84).aspx
 */
function GetJSONParser() {
    return new((function () {
        // コンストラクタ
        var JSONParser = function () {
            this.fso = WSH.CreateObject("Scripting.FileSystemObject");
            var htmlfile = WSH.CreateObject("htmlfile");
            htmlfile.write('<meta http-equiv="x-ua-compatible" content="IE=9"/>');
            htmlfile.close(this.parser = htmlfile.parentWindow.JSON);
        }
        // アクセサ
        var p = JSONParser.prototype;
        p.GetFileSystem = function () {return this.fso;}
        
        /**
        *  @brief  解析
        *  @param[in]  path jsonファイルのパス
        *  @return 解析結果
        */
        p.Parse = function (path) {
            var jsonFile = this.fso.OpenTextFile(path, 1, true);
            var jsonText = jsonFile.ReadAll();
            jsonFile.Close();
            return this.parser.parse(jsonText);
        }
        /**
        *  @brief  保存
        *  @param[in]  path 保存先のパス
        *  @param[in]  obj 保存するデータ
        */
        p.Save = function (path, obj) {
            var jsonFile = this.fso.OpenTextFile(path, 2, true, -2);
            jsonFile.Write(this.parser.stringify(obj));
            jsonFile.Close();
        }
        return JSONParser;
    })())();
}

あとがき

ちなみに、保存機能を考慮しなければ、テキストを直接 eval に流すだけでも対応できます。
JScript で、JSON オブジェクトを取得する方法は、下記から。 stackoverflow.com

C++ Windowsで ini ファイルを読み込む

はじめに

Windows で、ini ファイルの読み込みと言えば、GetPrivateProfileStringです。
時間をかけずに、手っ取り早く設定ファイル対応を行うときに使えるかもしれない方法です。

注意

GetPrivateProfileString は、下記に引用した通り
16ビット互換 のために残されているAPIなので、非推奨と考えた方がよさそうです。

MSDN より引用
注意 この関数は、16 ビット Windows ベースのアプリケーションとの互換性を保つ目的でのみ提供されています。Win32 ベースのアプリケーションでは、初期化情報をレジストリに格納してください。

実装

  • config.ini
; 設定ファイル
[System]
WindowText = ウィンドウタイトル
WindowWidth = 1280
WindowHeight = 720
  • main.cpp
#include <Windows.h>  // GetPrivateProfileString
#include <iostream>      // cout
#include <array>     // array
#include <string>        // string

std::string GetConfigString(const std::string& filePath, const char* pSectionName, const char* pKeyName)
{
    if (filePath.empty()) {
        return "";
    }
    std::array<char, MAX_PATH> buf = {};
    GetPrivateProfileStringA(pSectionName, pKeyName, "", &buf.front(), static_cast<DWORD>(buf.size()), filePath.c_str());
    return &buf.front();
}

int main()
{
    std::string filePath = ".\\config.ini";
    auto WindowText = GetConfigString(filePath, "System", "WindowText");
    auto WindowWidth = GetConfigString(filePath, "System", "WindowWidth");
    auto WindowHeight = GetConfigString(filePath, "System", "WindowHeight");

    std::cout << WindowText << std::endl;
    std::cout << WindowWidth << std::endl;
    std::cout << WindowHeight << std::endl;

    system("pause");
    return 0;
}

あとがき

APIの引数的に、情報取得毎にファイルを読み込みに行く疑惑があるので、
もしかすると、ファイルサイズの影響を受けて、遅くなるかもしれません。
そもそも、互換目的で残されているだけっぽいので、代替機能を用意するなり、適宜対応した方が良さそう。

C++ ビルド時の西暦を表示

メモ

定義済みマクロ の、__DATE__ は文字列として扱えるので、
西暦部分の先頭アドレスがあれば事足りる。

実装

#include <iostream>       // cout

constexpr const char* YEAR = (__DATE__ + 7);

int main()
{
    std::cout << "ビルド年:" << YEAR << std::endl;
    return 0;
}

あとがき

西暦のみの定義済みマクロが無いのか調べた結果、無さそうという結論。
よく考えたら、__DATE__から取り出せるよねと言う話。

C++ バージョン番号を比較する方法

はじめに

複数の値から形成されているバージョン番号などの値を比較する際、
一つずつ比較するのは、手間ですが、std::lexicographical_compare を使用すると、一行で書けます。
なお、std::tuple でより短くスマートに実装できるので、追記要参照。

実装

#include <algorithm>  // lexicographical_compare
#include <iostream>      // cout
#include <cstdint>       // int32_t
#include <array>     // array

// バージョン比較用共用体
union CompVersion {
    struct {
        int32_t minor;
        int32_t major;
        int32_t revision;
        int32_t build;
    };
    std::array<int32_t, 4> data;
};

int main()
{
    // 適当なバージョンデータ
    CompVersion appA = { 1, 0, 100, 2 };
    CompVersion appB = { 2, 0, 100, 3 };

    // 比較(appA < appB)
    bool result = std::lexicographical_compare(appA.data.begin(), appA.data.end(), appB.data.begin(), appB.data.end());

    // 結果
    if (result) {
        std::cout << "appAの方が古い" << std::endl;
    }
    else {
        std::cout << "appBの方が古い" << std::endl;
    }

    system("pause");
    return 0;
}

あとがき

ショートコーディングとしては、かなり有用なので覚書。
元ネタは、下記から。
stackoverflow.com

追記

この記事を公開直後に、バンビちゃんさんに高速カウンターをいただきました。
std::tuple を使用すると、もっとスマートに実装できるとのこと。
tuple便利!

Windows C++ 実行ファイルからバージョン情報を取得する

メモ

バージョンリソースが含まれるファイルからバージョン情報を取得する方法。
Windows API の GetFileVersionInfo を使って取得する。

実装

#include <windows.h>
#include <iostream>
#include <vector>
#include <string>

#pragma comment(lib, "version.lib")

// バージョン取得用の構造体
struct Version final {
    WORD minor;
    WORD major;
    WORD revision;
    WORD build;
};

// バージョン取得関数
bool GetFileVersion(const std::string& path, Version& version)
{
    DWORD infoHandle = 0;
    const auto size = GetFileVersionInfoSizeA(path.c_str(), &infoHandle);
    std::vector<char> buffer(size);
    if (buffer.empty()) {
        return false;
    }
    else if (GetFileVersionInfoA(path.c_str(), infoHandle, size, buffer.data()) == 0) {
        return false;
    }
    VS_FIXEDFILEINFO* pInfo;
    UINT verLen;
    if (VerQueryValueA(buffer.data(), "\\", reinterpret_cast<LPVOID*>(&pInfo), &verLen) == 0) {
        return false;
    }
    union {
        struct {
            DWORD fileVersionMS;
            DWORD fileVersionLS;
        };
        Version info;
    } ver = { pInfo->dwFileVersionMS, pInfo->dwFileVersionLS };
    version = ver.info;
    return true;
}

int main()
{
    const char* filePath = R"(C:\Windows\System32\notepad.exe)";

    // バージョン取得
    Version ver = {};
    if (GetFileVersion(filePath, ver)) {
        std::cout << "Major:" << ver.major << std::endl;
        std::cout << "Minor:" << ver.minor << std::endl;
        std::cout << "Build:" << ver.build << std::endl;
        std::cout << "Revision:" << ver.revision << std::endl;
    }
    else {
        std::cout << "エラー" << std::endl;
    }

    system("pause");
    return 0;
}

以上。

C++ 環境変数を含むパスをstd::stringに展開する方法

はじめに

パスに限らず、環境変数を含む文字列は、
WindowsAPIの ExpandEnvironmentStrings で展開できます。
std::string に展開する方法をメモ。

実装

#include <windows.h>
#include <iostream>
#include <string>

// 環境変数を含むパスを展開する
bool GetExpandEnvironmentPath(const std::string& path, std::string& dest)
{
    dest.resize(ExpandEnvironmentStringsA(path.c_str(), nullptr, 0));
    if (path.empty()) {
        return false;
    }
    ExpandEnvironmentStringsA(path.c_str(), &dest.front(), dest.size());
    return true;
}

int main()
{
    // 元のパス
    const char* basePath = "%tmp%\\data.bin";

    // 展開
    std::string resultPath;
    if (GetExpandEnvironmentPath(basePath, resultPath)) {
        std::cout << "展開前:" << basePath << std::endl;
        std::cout << "展開後:" << resultPath << std::endl;
    }
    else {
        std::cout << "失敗" << std::endl;
    }

    system("pause");
    return 0;
}

あとがき

今回は、stringに展開しましたが、
wstring なら ExpandEnvironmentStringsW で展開できます。

C++ で、コマンドの標準出力を取得する方法(CreateProcessなし)

はじめに

Windows でコマンドを実行する際、標準出力を取得する方法です。
コンソール画面が表示されても問題ない場合、
もしくは、CUIプログラムで使用できる方法です。

前提

system 関数だけでは、標準出力が簡単に取得できません。
Windows 環境では、APIの CreateProcess を使用する方法が良く使用されますが、popen を使用した方がシンプルに実装できます。

標準エラーの対応は、行っていませんが、
画面に表示したくない場合は、実行コマンド末尾に「 2>&1」を付けて標準出力にリダイレクトする等
適宜対応しましょう。

実装

#include <array>  // array
#include <cstdio>    // _popen
#include <iostream>  // cout
#include <memory>    // shared_ptr
#include <string>    // string

bool ExecCmd(const char* cmd, std::string& stdOut, int& exitCode) {
    std::shared_ptr<FILE> pipe(_popen(cmd, "r"), [&](FILE* p) {exitCode = _pclose(p); });
    if (!pipe) {
        return false;
    }
    std::array<char, 256> buf;
    while (!feof(pipe.get())) {
        if (fgets(buf.data(), buf.size(), pipe.get()) != nullptr) {
            stdOut += buf.data();
        }
    }
    return true;
}

int main(int argc, char** argv)
{
    // 適当なコマンドを用意する
    const char* cmd = "dir";
    std::string stdOut;
    int exitCode;
    if (ExecCmd(cmd, stdOut, exitCode)) {
        std::cout << stdOut << std::endl;
    }
    else {
        std::cout << "標準出力の取得に失敗しました。" << std::endl;
    }

    system("pause");
    return 0;
}

あとがき

Windows では、popen が使用できないと思い込んでいましたが、
_popen の名前で、使用できるので、実装して備忘録行き。