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

知りたいことだけ端的にまとめたい

WSH WSF JScriptで、YAML をパースする方法(js-yaml)

JScriptyaml ファイルをパースしたいとき、
wsf形式と外部パッケージのjs-yamlで、簡単に実現できます。
github.com

実装

yamlファイル。(sample.yaml
文字コードは、SJISを使用。

# 設定ファイル
config:
    title: "AppName"
    width: 1280
    height: 720

適当なバッチに埋め込んで使います。

<!-- :
@setlocal&pushd %~dp0
@cscript //nologo "%~f0?.wsf" %* || pause
@popd&endlocal&exit /b
-->
<job>
<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.5.14/es5-shim.min.js" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.5.14/es5-sham.min.js" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-yaml/3.13.1/js-yaml.js" />
<object id="shell" progid="WScript.Shell" />
<object id="fso" progid="Scripting.FileSystemObject" />
<script language="JScript">
WSH.Quit(function()
{
    try{
        // 読み込み
        var yamlFile = fso.OpenTextFile("sample.yaml", 1, true);
        var yamlText = yamlFile.ReadAll();
        yamlFile.Close();
        var doc = jsyaml.load(yamlText);
        
        // 表示
        WSH.Echo(doc.config.title);
        WSH.Echo(doc.config.width);
        WSH.Echo(doc.config.height);
        
        // Enter入力待ち
        WSH.StdIn.ReadLine();
    }
    catch(e){
        WSH.Echo(e.name + ":" + e.message);
        return -1;
    }
    return 0;
}());
</script>
</job>

仕組み

js-yamlでは、ES5に対応した環境が求められますが、
wsf 形式は、ES3程度の機能しか使えないのでes5-shimライブラリを使って
擬似的にES5環境を扱えるようにします。
読み込んだ後は、jsyamlオブジェクトを扱うだけです。

github.com

その他

オフライン環境で使いたい場合など、
ライブラリの参照にCDNを使いたくなければ、ローカルに保存して使いましょう。

<script src="./es5-shim.min.js" />
<script src="./es5-sham.min.js" />
<script src="./js-yaml.js" />

以上、おわり。

WSH JScript で、高精度に処理時間を計測する(Performance API)

はじめに

JScriptで、スクリプトの処理時間を計測するとき、
Date オブジェクトを使って計測することがありますが、これはミリ秒単位の精度です。

厳密な速度計測が不要であれば、ミリ秒もあれば事足りますが、
より精度の高いマイクロ秒単位が必要であれば、
Web API (Performance API) の Performance インターフェースを使います。

実装

計測対象の関数は、ここから拝借。
フィボナッチ数を出力する関数を作る - Qiita

function do_something()
{
    var fib = function (n) { return n > 2 ? fib(n - 1) + fib(n - 2) : 1; };
    fib(33);
}

Performance インターフェースによる計測

JScript環境の、Performance インターフェースは、
JSON オブジェクト等と同様で、htmlfile オブジェクトから取得できます。

WSH.Quit(function()
{
    try{
        // Performance を取得
        var htmlfile = WSH.CreateObject("htmlfile");
        htmlfile.write('<meta http-equiv="x-ua-compatible" content="IE=Edge"/>');
        var performance = htmlfile.parentWindow.performance;
        htmlfile.close();
        
        // 計測
        var startTime = performance.now();
        do_something();
        var endTime = performance.now();
        
        // 結果
        // 浮動小数が得られる。小数点以下が、マイクロ秒。
        // (小数第1位までしか得られない?)
        WSH.Echo((endTime - startTime) + " ms");
    }
    catch(e){
        WSH.Echo(e.name + ":" + e.message);
        return -1;
    }
    return 0;
}());

過去の手法

Date オブジェクトによる計測

WSH.Quit(function()
{
    try{
        // Performance を取得
        var htmlfile = WSH.CreateObject("htmlfile");
        htmlfile.write('<meta http-equiv="x-ua-compatible" content="IE=Edge"/>');
        var performance = htmlfile.parentWindow.performance;
        htmlfile.close();
        
        // 計測
        var startTime = new Date();
        do_something();
        var endTime = new Date();
        
        // 結果(※ これでは、ミリ秒に丸められた結果しか得られない)
        WSH.Echo((endTime.getTime() - startTime.getTime()) + " ms");
    }
    catch(e){
        WSH.Echo(e.name + ":" + e.message);
        return -1;
    }
    return 0;
}());

その他メモ

バッチコマンドの計測であれば、powershell の Measure-Command が手軽です。

powershell -c (Measure-Command { (ここに計測するコマンド) })

おわり。 developer.mozilla.org

Chakra版 JScriptで、JSONファイルをパースする

昔、既定のJScript ではJSONオブジェクトが無いことの対処法を記事にしましたが、
今回は、Chakra エンジンを使うケースの話。

結論から言うと、
Chakra エンジンを使うのであれば、そのままでJSONオブジェクトが使えます。

Chakra エンジンを使う方法については、こちら

実装

エンジンを指定するため、適当なバッチファイルに埋め込んで使います。

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

WSH JScript で、プログレスバー

はじめに

軽く検索しても手軽なサンプルが見つからなかったので、
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 を外部ファイルに切り出す

はじめに

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());

「AppNetworkCounter」を日本語化する手順

https://user-images.githubusercontent.com/12238431/70074333-d9233200-163d-11ea-8c0c-a25e0c81c5d4.png

はじめに

NirSoft の AppNetworkCounter v1.35 を日本語化する手順です。
現在(2019/12/09)、公式サイトには日本語向けの設定ファイルが無いので、GitHub上に公開されているもの(サードパーティ)を使います。

手順

手順は、以下の通り。

  1. 下記のリンクから、最新版のappnetworkcounter_japanese.zipをダウンロード
    Releases · namarium/AppNetworkCounter-language-file-for-Japanese · GitHub

  2. appnetworkcounter_japanese.zip を解凍して、
    AppNetworkCounter.exe と同じディレクトリにAppNetworkCounter_lng.iniを配置

  3. AppNetworkCounter.exe を起動して、日本語化されていれば完了

あとがき

AppNetworkCounter_lng.ini のベースになるファイルは、AppNetworkCounter.exe /savelangfile コマンドで生成できますが、
文字コードがシステムに依存しているようで、日本語環境では Shift_JIS が使用されました。

挙動が気になって Unicode(UTF-16) でも試したところ問題なく受け付けました。これは公式対応しても良い気がします。
Shift_JIS が使われて困るというわけではないものの、ふとしたことで文字化けしない文字コードの方が良いですね。
(リンク先からダウロードできる設定ファイルは、Unicode(UTF-16) 化されています。)