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

趣味プログラマのメモ

JScript.NET をバッチに埋め込む方法

はじめに

Microsoft製のドマイナー言語、JScript .NET をバッチファイルに埋め込んで実行する方法です。

JScript .NET は、スクリプト言語である WSHJScript の後継的な位置づけでありながら、コンパイル必須という扱いづらい言語なので、
コンパイル処理と実行を一度に処理できるように、バッチファイルに埋め込んでしまおうというお話。

実装

おまじない部分

バッチに埋め込んで実行するためのコード。
コンパイルに成功したら実行するだけ。

@if(0)==(0) for /f "delims=" %%i in ('where /r %windir%\Microsoft.NET jsc.exe^|sort') do @set jsc=%%i
@"%jsc%" /nologo /out:"%~dp0%~n0.exe" "%~f0"&&"%~dp0%~n0.exe" %*
@exit /b&@end

バッチをUTF-8で保存する場合

コンパイラ文字コードを知らせるため、/codepage オプションを指定します。
UTF-8のコードページは、65001です。

@"%jsc%" /nologo /codepage:65001 /out:"%~dp0%~n0.exe" "%~f0"&&"%~dp0%~n0.exe" %*

多重コンパイルしたくない場合

実行毎にコンパイルが走ると、その分起動に時間がかかってしまうので、
実行ファイル名にハッシュ値を入れて、コンパイルが必要かどうか判定します。

@if(0)==(0) echo off&for /f "delims=" %%i in ('certutil -hashfile "%~f0"^|find /v ":"') do set app_path=%~dp0%~n0[%%i].exe
if not exist "%app_path%" del "%~dp0%~n0[*].exe">nul 2>&1&for /f "delims=" %%i in ('where /r %windir%\Microsoft.NET jsc.exe^|sort /r') do "%%i" /nologo /out:"%app_path%" "%~f0"&&goto:run||pause&goto:EOF
:run
"%app_path%" %*&exit /b &@end

// 以下に実装を書く
import System;

/**
 * エントリーポイント用
 */
Environment.Exit(function():int{
    var exitCode:int = 123;
    try{
        Console.WriteLine("Hello JScript.NET!!");
        Console.ReadKey();
    }catch(e){
        exitCode = int.MaxValue;
        print(e), Console.ReadKey();
    }finally{
    }
    return exitCode;
}());

その他

別解として、
実行ファイルを作らず powershell 経由でインメモリ実行する例。 qiita.com

DXライブラリ向け 簡易キー入力クラス

はじめに

DXライブラリで、キー入力の開始や終了を取る際、自前で機能を用意する必要があります。
必要になるたびに作るのが面倒そうなので、キー入力クラステンプレート的なものを置いときます。
対象は、キーボードとマウスです。

昔、DxLibをラップした自作ライブラリで学習がてら作ったものから、
切り出しだけなので、気になる部分があれば、適当に調整してください。

実装

押した瞬間、押している間、離した瞬間 が取れる実装です。

ビルド環境:Microsoft Visual C++ 2015

(長いので折り畳み)

#include <DxLib.h>
#include <array>

namespace dxlut
{
    // 入力クラス
    struct Input final
    {
        // 入力情報更新
        static bool Update() {
            Mouse::Update();
            return Keyboard::Update();
        }
        
        // 何らかのキーの入力開始
        static bool AnyKeyPress() {
            return DxLib::CheckHitKeyAll() != 0;
        }
        
        // 何らかのキーの入力中
        static bool AnyKeyDown() {
            return Keyboard::AnyDown() || Mouse::AnyDown();
        }

        // 何らかのキーの入力終了
        static bool AnyKeyUp() {
            return Keyboard::AnyUp() || Mouse::AnyUp();
        }

        // キーボード
        struct Keyboard final
        {
            typedef std::array<char, 256> KeyStateBuffer;

            // 入力情報更新
            static bool Update() {
                keyBuffer_.swap(prevKeyBuffer_);
                return GetHitKeyStateAll(&keyBuffer_.front()) == 0;
            }

            // 入力開始
            static bool IsPress(const int keyCode) {
                return keyBuffer_[keyCode] ? true : false;
            }

            // 入力中
            static bool IsDown(const int keyCode) {
                return keyBuffer_[keyCode] && !prevKeyBuffer_[keyCode];
            }

            // 入力終了
            static bool IsUp(const int keyCode) {
                return !keyBuffer_[keyCode] && prevKeyBuffer_[keyCode];
            }

            // 何らかのキーの入力開始(キーボード)
            static bool AnyPress() {
                for (unsigned idx = 0; idx < keyBuffer_.size(); ++idx) {
                    if (keyBuffer_[idx] != 0) {
                        return true;
                    }
                }
                return false;
            }

            // 何らかのキーの入力中(キーボード)
            static bool AnyDown() {
                for (unsigned idx = 0; idx < keyBuffer_.size(); ++idx) {
                    if (keyBuffer_[idx] && !prevKeyBuffer_[idx]) {
                        return true;
                    }
                }
                return false;
            }

            // 何らかのキーの入力終了(キーボード)
            static bool AnyUp() {
                for (unsigned idx = 0; idx < keyBuffer_.size(); ++idx) {
                    if (!keyBuffer_[idx] && prevKeyBuffer_[idx]) {
                        return true;
                    }
                }
                return false;
            }

        private:
            static KeyStateBuffer  keyBuffer_;
            static KeyStateBuffer  prevKeyBuffer_;
        };

        // マウス
        struct Mouse final
        {
            // 入力情報更新
            static void Update() {
                backKeyBuffer_ = keyBuffer_;
                keyBuffer_ = GetMouseInput();
            }

            // 入力開始
            static bool IsPress(const int keyCode) {
                return (keyBuffer_ & keyCode) != 0;
            }

            // 入力中
            static bool IsDown(const int keyCode) {
                return ((keyBuffer_ & keyCode) != 0) && !((backKeyBuffer_ & keyCode) != 0);
            }

            // 入力終了
            static bool IsUp(const int keyCode) {
                return !((keyBuffer_ & keyCode) != 0) && ((backKeyBuffer_ & keyCode) != 0);
            }

            // 何らかのボタン入力開始(マウス)
            static bool AnyPress() {
                return keyBuffer_ != 0;
            }

            // 何らかのボタン入力中(マウス)
            static bool AnyDown() {
                return (keyBuffer_ != 0) && !(backKeyBuffer_ != 0);
            }

            // 何らかのボタン入力終了(マウス)
            static bool AnyUp() {
                return !(keyBuffer_ != 0) && (backKeyBuffer_ != 0);
            }

        private:
            static int  keyBuffer_;
            static int  backKeyBuffer_;
        };
    };
    Input::Keyboard::KeyStateBuffer Input::Keyboard::keyBuffer_ = {};
    Input::Keyboard::KeyStateBuffer Input::Keyboard::prevKeyBuffer_ = {};
    int Input::Mouse::keyBuffer_ = 0;
    int Input::Mouse::backKeyBuffer_ = 0;

    // DXライブラリ簡易ラップクラス
    struct DxLibApp final
    {
        DxLibApp()
        {
            SetGraphMode(1024, 600, 32);
            ChangeWindowMode(TRUE);
            if (DxLib_Init() == -1) {
                throw;
            }
            SetDrawScreen(DX_SCREEN_BACK);
        }

        ~DxLibApp() {
            DxLib_End();
        }

        static bool Update() {
            return (ScreenFlip() == 0 && ProcessMessage() == 0 && ClearDrawScreen() == 0 && Input::Update());
        }
    } _app;
}

// エントリーポイント
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
    using namespace dxlut;

    // メインループ
    while (DxLibApp::Update())
    {
        // Esc 終了
        if (Input::Keyboard::IsDown(KEY_INPUT_ESCAPE)) {
            break;
        }

        // とりあえず、「キーボードのZ」と「マウス左クリック」の入力検査サンプル
        // 他のキーは、キーコードだけ変えればよし

        // Zキーの状態
        if (Input::Keyboard::IsDown(KEY_INPUT_Z)) {
            DxLib::printfDx(_T("[Z] 入力開始\n"));
        }
        if (Input::Keyboard::IsPress(KEY_INPUT_Z)) {
            DxLib::printfDx(_T("[Z] 入力中\n"));
        }
        if (Input::Keyboard::IsUp(KEY_INPUT_Z)) {
            DxLib::printfDx(_T("[Z] 入力終了\n"));
        }

        // 左クリックの状態
        if (Input::Mouse::IsDown(MOUSE_INPUT_LEFT)) {
            DxLib::printfDx(_T("[Mouse L] 入力開始\n"));
        }
        if (Input::Mouse::IsPress(MOUSE_INPUT_LEFT)) {
            DxLib::printfDx(_T("[Mouse L] 入力中\n"));
        }
        if (Input::Mouse::IsUp(MOUSE_INPUT_LEFT)) {
            DxLib::printfDx(_T("[Mouse L] 入力終了\n"));
        }
    }

    return 0;
}

あとがき

たまたま、DXライブラリ製の小物プログラムを改修する機会がありました。
入力周りの制御をネットで調べると意外と簡易的なものが見つからなかったので、昔のコードを掘り返し備忘録行き。

DXライブラリは、よく使いそうな便利関数も自分で用意しないといけない部分が多い印象です。
この点において、イマドキだとUnityだったり、C++ならSiv3D(OpenSiv3D)が便利関数豊富で初心者が取っつきやすい環境かなと感じています。

WSH WSF JScript・VBScript をバッチに埋め込む方法(現在時刻を読み上げるバッチファイル)

メモ

タイトルの通り、wsf 形式のWSHスクリプトをバッチファイルに埋め込む方法です。
wsfを使えば、VBScriptJScript を混在させたり、
HTML の scriptタグ と同じように記述できるのでCDNを利用できます。
バッチに埋め込むメリットは、用途次第ということで省略。

コマンド

おまじない部分は、下記の通り。

<!-- :
@%windir%\System32\cscript.exe //nologo "%~f0?.wsf" %*
@exit /b %errorlevel%
-->

これを駆使して、現在時刻を読み上げる機能を作ると下記のようになります。

<!-- :
@%windir%\System32\cscript.exe //nologo "%~f0?.wsf" %*||pause
@exit /b %errorlevel%
-->
<!-- :

WSH WSF をバッチに埋め込む方法

サンプル:
    現在時刻を読み上げるバッチファイル

-->
<job>
<script language="VBScript">
' 現在時刻を返すだけ の関数
Function GetDateTime()
    GetDateTime = Now
End Function
</script>
<script language="JScript">
/**
 *  @brief  エントリーポイント用即時関数
 */
WSH.Quit(function ()
{
    var sp = WSH.CreateObject("SAPI.SpVoice");
    // 現在時刻を読み上げる
    sp.Speak(GetDateTime());
    return 0;
}());
</script>
</job>

参照

埋め込む方法は、下記から。 stackoverflow.com

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__から取り出せるよねと言う話。