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

趣味プログラマのメモ

WSH JScript Chakra を使用した ES2015(ES6) 対応 ( スクリプトエンジン まとめ )

はじめに

WSH JScript (wscript cscript) で使用できる スクリプトエンジン まとめです。

JavaScript限定、プリインストールされている物だけなので注意。
ツールとして独立しているChakraCoreや、その他サードパーティーは含みません。

JavaScript エンジン について

JScript (JavaScript) だけでも、4種類ほどスクリプトエンジンがあるようです。
以下、それぞれバッチファイルから cscript で実行する際のサンプルコマンドです。

JScript

JScript を使用する際、一般的に指定されるスクリプトエンジンです。
バージョンは、環境によって異なりますが、
現時点のOSサポートを考慮すれば、5.7 か 5.8 でしょう。

rem jscript.dll
%windir%\System32\cscript.exe /nologo /E:JScript "%~f0" %*

JScript.Compact

JScript Compact Profile (ECMA 327) で実行する、スクリプトエンジンです。
MSの情報が少ないので、詳細は未確認ですが
簡単に説明すれば、「いくつかの機能を制限して、パフォーマンス向上を図る設定を使用する」と言った感じでしょうか。
機能の制限について、少なくとも with ステートは、サポートされていないようです。

rem JScript Compact Profile (ECMA 327)
%windir%\System32\cscript.exe /nologo /E:JScript.Compact "%~f0" %*

JScript9

IE9 から使用されている、JScript9.dll をスクリプトエンジンとして指定できます。
ProgID が公開されていないので、CLSID を指定する必要があります。

ちなみに、JScript9 の 9 は、エンジンバージョンと言うわけでは無い様で、
Windows10 では、バージョン 11.0 として扱われていました。(中身は、Chakra っぽい?)

JScript と同程度の機能しか使えませんが、JScript9.dll の方が高速に動作します。
ただし、JScript 互換は完全ではなく WScript.Quit が使用できません。

rem jscript9.dll
%windir%\System32\cscript.exe /nologo /E:{16d51579-a30b-4c8b-a276-0ff4dc41e755} "%~f0" %*

Chakra

Microsoft EdgeJavaScriptエンジン です。
こちらも、ProgID が公開されていないので、CLSID を指定する必要があります。

Chakra を使用する場合は、ES2015(ES6) に対応している為、クラスなどが使用できます。
ただし、JScript9 と同様に、WScript.Quit に加え GetObject、VBArray 等が、使用できません。

rem chakra.dll
%windir%\System32\cscript.exe /nologo /E:{1B7CD997-E5FF-4932-A7A6-2A9E636DA385} "%~f0" %*

Chakraを使用したスクリプト を バッチに埋め込む場合

JScript9までは、@if を使用してバッチファイルにJScriptを埋め込むことが出来ましたが、
Chakra は、IE由来の条件付きコンパイルをサポートしていないため、@if を解釈できません。
JScript でハマる日々 - m2

@if(0==0) /*
@rem 指定できるエンジンは、JScript9 まで。Chakra 不可
@%windir%\System32\cscript.exe /nologo /E:JScript "%~f0" %*||pause
@exit /b %errorlevel%&*/@end

JScript共通の埋め込み

Chakra 以外のエンジンを共存させる場合は、
@if を使用しないshebangモドキがあるので、そちらを使います。
ildar-shaimordanov/hello.jscript.bat

0</* ::
@%windir%\System32\cscript.exe /nologo /E:{1B7CD997-E5FF-4932-A7A6-2A9E636DA385} "%~f0" %*||pause
@exit /b %errorlevel%&*/0;

Chakra限定の埋め込み

Chakra のみ使う場合は、
Chakra が「<!--」をコメント扱いすることを利用した方法があります。
https://qiita.com/snipsnipsnip/items/50e4ca88e3ce3f8cffda

<!-- : ^
/*
@%windir%\System32\cscript.exe /nologo /E:{1B7CD997-E5FF-4932-A7A6-2A9E636DA385} "%~f0" %*||pause
@exit /b %errorlevel%&*/

JScript9 や Chakra で、戻り値を返したい場合(2022/05/28 更新)

WScript.Quit は、例外を握りつぶすと使えます。
.bat にコピペで、動作確認できます。

0</* ::
@%windir%\System32\cscript.exe /nologo /E:{1B7CD997-E5FF-4932-A7A6-2A9E636DA385} "%~f0" %*
@echo 戻り値は、%errorlevel%です。&pause
@exit /b %errorlevel%&*/0;

// エントリ
let global_result=(()=>{
    
    // ポップアップを表示
    // ActiveXObject は未定義なので、CreateObject を使う
    const shell = WSH.CreateObject("WScript.Shell");
    shell.popup(`${WSH} for Chakra`);
    
    return 123;
})();

try {WScript.Quit(global_result);
}catch(e){// JavaScript runtime error: Object doesn't support this action
}

補足

一応触れておくとChakraCoreを利用する場合、
WScript.Quit は定義済みで普通に呼び出せるので、このような悩みとは無縁です。
https://github.com/microsoft/ChakraCore/blob/bd91335361ddd215622d810cc7341a180abd2db4/bin/ch/WScriptJsrt.cpp#L1090

おわりに

WSH ってだけで、今更感満載のレガシーなのですが、
初期状態のWindowsで使用できる手軽さで、根強く?使われている様子。

今回のまとめは、WSH JScript で、クライアントの環境に依存せずに
ES2015 ベースのコードを使えないか調査したときの副産物です。

結果的に、ES2015 対応は行っていませんが、
TypeScript 経由で、対応できそうかなと言ったところでオチ。

参考リンク

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 で展開できます。