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

趣味プログラマのメモ

WSH JScript で、JSONファイルをパースする(Chakra を利用する場合)

メモ

既定のJScriptJSONオブジェクトを利用する方法の別解。
Chakra エンジンを使うとJSONオブジェクトが定義済みで、そのまま使える模様。

実装

エンジンを指定するため、適当な .bat に埋め込んで使います。

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

(()=>{
    try
    {    // JSONをパース
        const jsonTxt =
        `{"Application": {"title": "SampleApp", "width": 1280, "height": 720}}`;
        let rootObj = JSON.parse(jsonTxt);
        
        // 結果を表示
        let app = rootObj.Application;
        WSH.Echo(`${app.title}\n${app.width}\n${app.height}`);
    }
    catch(e){
        WSH.Echo(e.name + " : " + e.message);
    }
    finally{}
})();

結果

SampleApp
1280
720

以上、おわり。

関連

developer.mozilla.org inemaru.hatenablog.com

WSH JScript CScript プログレスバー

はじめに

軽く検索しても手軽なサンプルが見つからなかったので、
CScript と JScript を使ったコマンドライン環境向けに、プログレスバーを用意します。

↓ のように動きます。
f:id:inemaru:20200321072912g:plain

実装

最低限の機能があれば良いので、設定できるオプションは下記だけ。

画面更新の仕組みとしては、C++等と同様で、
制御文字CR(\r)を使っています。(行頭に戻して書き直しするだけ。)

var ProgressBar = (function(){
    function ProgressBar() {
        this.MaxValue = 100;
        this.BarWidth = 30;
        this.Label = "progress";
    };
    ProgressBar.prototype.Update = function(value){
        var rate = value/this.MaxValue;
        var val = Math.ceil(Math.min(Math.max(1,rate*this.BarWidth),this.BarWidth));
        var barComplete = Array(val+1).join("=");
        var barIncomplete = Array(this.BarWidth-val+1).join("-");
        var isProcessing = 1>rate;
        WSH.StdOut.Write("\r"+this.Label+" ["+barComplete+barIncomplete+"] "
            +(isProcessing?(rate*100).toFixed(2)+"%":"complete\n"));
        return isProcessing;
    };
    ProgressBar.prototype.Complete = function(){this.Update(this.MaxValue)};
    return ProgressBar;
}());

使い方

進捗状態を更新するときは、Update
強制的に完了にしたければ、Completeメソッドを呼びます。

WSH.Quit(function()
{
    try{
        var prog = new ProgressBar();
        {
            prog.Label = "サンプルA:整数\t\t";
            for (i=0; prog.Update(i); i+=2) {
                WSH.Sleep(100);
            }
        }
        {
            prog.Label = "サンプルB:浮動小数\t";
            prog.MaxValue = Math.PI;
            for (i=0; prog.Update(i); i+=0.1) {
                WSH.Sleep(100);
            }
        }
        {
            prog.Label = "サンプルC:バーの幅変更\t";
            prog.BarWidth = 50;
            for (i=0; i < prog.MaxValue; i+=0.1) {
                prog.Update(i);
                WSH.Sleep(100);
            }
            // 強制する場合
            prog.Complete();
        }
    }
    catch(e){
        WSH.Echo(e.name + ":" + e.message);
        return -1;
    }
    finally{
        // 3秒待機
        WSH.Sleep(3000);
    }
    return 0;
}());

おわりに

時間のかかる処理ではプログレスバーを出す方が親切ですが、
コンソール画面(CUI, CLI)では、ログを出していればとりあえず処理が進んでいるのが目視できるのでプログレスバーは必須では無いと思います。

WSH wsf の resource を外部ファイルに切り出す(WSC を使った実装の分割)

はじめに

WSH(.wsf) では、<resource>タグを使って任意のテキストデータをスクリプトに埋め込みできます。
しかし、設定ファイル用途で使用したり、ある程度容量が増えてきたりすると、外部ファイルに分割したくなります。

こういった場合ファイルの分割は、Windows Script Component (WSC) の仕組みを使って実現できます。

WSC は、WSF形式のスクリプトをCOMコンポーネントとして扱えるようにするテクノロジーですが、
モニカという機能を使って、レジストリに登録すること無くコンポーネントを利用できます。

つまり、<resource>タグごとコンポーネント化することで、外部ファイルに切り出せるということです。

やり方

モニカを使ったコンポーネントの取得には、
WSHJScriptVBScript)が公開している GetObject 関数の引数に script:<wscの絶対パス> を指定します。
wscのパスは、相対パスだとエラーになります。
その他、拡張子は wsc である必要は無く xml 形式として誤りが無ければ任意の拡張子が指定可能です。(サンプルでは、拡張子を xml とします。)

sample.bat(スクリプト側) resource.xml(リソース側) は、同じディレクトリに配置して動かしています。

Workspace
├─sample.bat        
└─resource.xml      

スクリプト

簡素化のために、bat ファイルに埋め込んでいますが、処理内容はシンプルです。
バッチでプロセス環境変数に、wsc(resource.xml) のパスをsetしておいて、
JScriptで、setした環境変数を読み取っています。

<!-- :
@echo off&title %~n0&setlocal&pushd %~dp0

rem リソース(wsc)のパス
set resource_name=resource.xml
set resource_path=%~dp0%resource_name%
set resource_moniker=script:%resource_path%

rem job実行
cscript //nologo "%~f0?.wsf" %* || pause

popd&endlocal&exit /b
-->
<job>
<object id="shell" progid="WScript.Shell"/>
<script language="JScript">
WSH.Quit(function()
{
    try{
        // リソース読み込み
        var procEnv = shell.Environment("Process");
        var rc = GetObject(procEnv("resource_moniker"));
        
        // とりあえず、表示
        var result = rc.Get("sln");
        WSH.Echo(result);

        // (ここで、リソースを使ってやりたい処理など)
    }
    catch(e){
        WSH.Echo(e.name + ":" + e.message);
        return -1;
    }
    finally{
        // 表示確認のため、3秒待機
        WSH.Sleep(3000);
    }
    return 0;
}());
</script>
</job>

リソース側

<resource id="識別子"><![CDATA[]]></resource> の間が、埋め込むテキストです。
拡張子を .wscでなく、.xmlにしているのは、ダブルクリックでレジストリ登録が走らないようにするためのフェイルセーフ。

<?xml version="1.0" encoding="utf-16" ?>
<component>
<!-- 下記2行で、リソース取得関数を公開 -->
<public><method name="Get"><PARAMETER name="id"/></method></public>
<script language="JScript">function Get(id){return getResource(id)}</script>

<!-- 適当なリソース -->
<resource id="sln"><![CDATA[
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28010.2016
MinimumVisualStudioVersion = 10.0.40219.1
Global
    GlobalSection(SolutionProperties) = preSolution
        HideSolutionNode = FALSE
    EndGlobalSection
    GlobalSection(ExtensibilityGlobals) = postSolution
        SolutionGuid = {753BEE69-EAF9-44D4-86E7-78DDC490BA05}
    EndGlobalSection
EndGlobal
]]></resource>
</component>

あとがき

今回は、任意のディレクトリ構成でVisualStudio用のプロジェクトを作成するスクリプトで使用した方法の備忘録です。
設定ファイル程度であれば htmlfile オブジェクトから、JSONオブジェクトが取れるので.jsonで取り扱うことも出来ますが、
複数の既成フォーマットをひとまとめにしておくケースでは、テキストリソースの方が都合が良い訳です。

一応別解として、ES3程度の機能しかないJScriptでも正規表現を駆使したテクニックで、
ヒアドキュメントのような事ができるので、
<script>タグのsrc属性でリソース分割は可能ではありますが、これはランタイムで本来不要なテキストの加工が走るので好みません。

そういうわけで、WSCを使った方がパフォーマンス的にもコードの読みやすさとしても、スマートになるかと思います。

終わり。

C++ CLI Processクラスから HINSTANCE を取得する

メモ

C++/CLI 環境で、Process クラスで起動したプログラムのインスタンスハンドルを取得する方法。
Handleプロパティが、そのまま HINSTANCE として扱える。

実装

// 「ペイント」を起動
auto proc = gcnew System::Diagnostics::Process();
proc->StartInfo->FileName = "mspaint.exe";
auto result = proc->Start() && proc->WaitForInputIdle();
if (!result)
{   // 起動に失敗
    return;
}

// インスタンスハンドルを取得
auto hInst = static_cast<HINSTANCE>(proc->Handle.ToPointer());

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