last updated:2021/10/09

Hidemaru Q and A

第III部〜秀丸マクロのいろはにほへと
 COMオブジェクト操作関連


COMオブジェクト操作関連

【0】COMオブジェクトとは

COMとは、Component Object Modelの略です。 Windowsでソフトウェアの機能を部品化して外部から呼び出して利用する仕組みを定めた技術仕様になります。

ただし、現状(2021/10/09現在)では、古い技術になっています。とはいえ、使えなくなったという事ではなく Word Excel などの Office製品や、「Windows Script Host」などOSの一部の機能には残っていますし、対応しているアプリケーションもまだあります。

秀丸マクロで出来ないことでも、COMオブジェクトを使えば可能になることもあるので、使い方を覚えておくのも良いでしょう。(いつまで使えるかはわかりませんが)

ここでは、OSに標準搭載されている「Windows Script Host」等を使って説明します。

【1】使用前の準備

まず、使用するCOMオブジェクトの情報を収集します。

今回は以下のCOMオブジェクトを使用します。

  1. 「Windows Script Host」の各種オブジェクト

    以前は、chm形式の日本語ヘルプがダウンロードできましたが、現時点(2021/10/09)ではダウンロード出来なくなっています。

    その為、Microsoftのwebサイトを参照します。

    トップページはMicrosoft Windows スクリプト テクノロジになります。今回使用するWindows Script Hostを選び、リファレンスから、オブジェクトを選びます。

    このページにある各種オブジェクトを使用します。

    このページに記載されている全てのオブジェクトが使える訳ではないので、注意してください。

    • WScript オブジェクト は、ホストプログラム(cscript.exe、wscript.exe)のルートオブジェクトで、外部からは使用できません。
    • WshArgumentsオブジェクト WshNamedオブジェクト WshUnnamedオブジェクト は、ホストプログラム(cscript.exe、wscript.exe)から実行した時に引数を取得するためのオブジェクトなので、秀丸マクロからでは使い道がありません。
  2. FileSystemObject オブジェクト

    Microsoft Windows スクリプト テクノロジにある、 Script ランタイム-FileSystemObjectにあります。

  3. Shell オブジェクト

    Shell object (Windows)

各オブジェクトのリンク先には、使用できるプロパティ、メソッドの説明があります。この情報を元に、秀丸マクロを作成していくことになります。 (逆に言うと、このような情報がない場合には、マクロの作成は出来ません)

Office製品を対象とする場合には、各製品の「VBA リファレンス」が参考になります。ただし、秀丸マクロは基本的には文字と整数しか扱えないので、そのままでは使えない場合もあります。

秀丸マクロ側は「COMの呼び出し」にある、関数/文 を使うことになります。

プロパティの呼び出し/設定、メソッドで戻り値が、数値、文字、オブジェクトの場合で命令が異なるので少し複雑です。また戻り値によって命令が異なるので、使用するCOMオブジェクトの情報がないと、何を使っていいのか判断に困まります。

【2】秀丸マクロからCOMオブジェクトを使ってみる

COMオブジェクトを使用するには、まず createobject関数を使用します。

createobject関数では、使用するオブジェクトの「アプリケーション名」「クラス(オブジェクト)名」が必要です。場合によっては「バージョン」を指定します。

//"WScript"の"WshShell"オブジェクトを使う
#n = createobject("WScript.Shell");

//"Excel"の"Application"オブジェクトを使う
#excel = createobject("Excel.Application");

//"WinHttp"の"WinHttpRequest"オブジェクトのバージョン"5.1"を使う
#objHttp = createobject("WinHttp.WinHttpRequest.5.1");

createobject関数でCOMオブジェクトを使用できるようにし、そのオブジェクトが持つ各種メソッド呼び出しや、プロパティを設定します。

【2.1】「Windows Script Host」の "WshShell" オブジェクトを使ってみる

WshShell オブジェクト にある、"Popup"メソッドを使ってみます。

秀丸マクロの message文 と同じメッセージを表示する命令ですが、タイムアウト時間を設定でき自動で閉じることが出来ます。
(秀丸エディタ Ver.8.75以降では、message文が拡張され、タイムアウトする事も可能)

Popupメソッドの引数、戻り値は以下になります。

intButton = object.Popup(strText,[nSecondsToWait],[strTitle],[nType])

WSH(VBScript)の場合は以下になります。

'オブジェクトの生成
Set WshShell = WScript.CreateObject("WScript.Shell")
'メソッドの呼び出し、7秒待機する。
BtnCode = WshShell.Popup("ご機嫌いかがですか?", 7, "お答えください。", 4 + 32)
'結果の表示
Wscript.echo BtnCode
'オブジェクトの解放
Set WshShell = Nothing

これを秀丸マクロに変換していきます。

まず、WshShell オブジェクトを生成するため、createobject 関数を使います。

WSH(VBScript)で実行する場合と違い、秀丸マクロではCOM関連の実行時エラーが出ないので、メソッドの実行やプロパティアクセス結果のチェックを行う必要があります。 createobject関数は戻り値で判断できますが、基本的には getresultex関数 を使い、成功/失敗を判断します。 (getresultex(10) で、COM関連命令が成功したか、失敗したかを返す)

//オブジェクトの生成
#WshShell = createobject("WScript.Shell")
//createobject関数の成功/失敗をチェック
if(#WshShell == 0){ //if(getresultex(10) == false){
    //createobject関数が失敗した場合はメッセージを表示して終了
    message "WScript.Shell オブジェクトの生成に失敗しました。";
    endmacro;
}

次に"Popupメソッド"を呼び出します。

呼び出すメソッドの戻り値によって、命令を使い分けます。

プロパティの場合は、取得する/設定する内容により命令を使い分けます。

Popupメソッドで、戻り値が数値なので"callmethod_returnnum関数"を使いメソッド呼び出します。

#BtnCode = callmethod_returnnum(#WshShell, "Popup", "ご機嫌いかがですか?", 7, "お答えください。", 4 + 32);
//メソッドの呼び出しが成功したか確認
if(getresultex(10) == false){
    //失敗した場合はメッセージを表示して終了
    message "Popupメソッドの呼び出しに失敗しました。";
    endmacro;
}

あとは、結果の表示だけなので、最終的には以下のようなマクロになります。

//オブジェクトの生成
#WshShell = createobject("WScript.Shell");
//createobject関数の成功/失敗をチェック
if(#WshShell == 0){ //if(getresultex(10) == false){
    //createobject関数が失敗した場合はメッセージを表示して終了
    message "WScript.Shell オブジェクトの生成に失敗しました。";
    endmacro;
}
// Popupメソッドの呼び出し、7秒待機する。
#BtnCode = callmethod_returnnum(#WshShell, "Popup", "ご機嫌いかがですか?", 7, "お答えください。", 4 + 32);
//メソッドの呼び出しが成功したか確認
if(getresultex(10) == false){
    //失敗した場合はメッセージを表示して終了
    message "Popupメソッドの呼び出しに失敗しました。";
    endmacro;
}
//結果の表示
message str(#BtnCode);
//オブジェクトの解放
releaseobject #WshShell;
endmacro;

エラー検出処理が入っているのでWSH(VBScript)と比較するとマクロが冗長になってしまいますが、エラー検出をしないと正常動作しない場合に、どこでエラーになっているのか追いかけるのが大変です。面倒ですが、ある程度動作確認が出来るまでは、各メソッドの呼び出しやプロパティアクセス毎にチェックする事をお勧めします。

また、WSH(VBScript、JScript)をつかって、事前に秀丸マクロで実行したい処理を実行してみるのも良いです。そもそも、処理方法自体が間違っているのか、秀丸マクロに変換する時点で間違っているのか判断の材料になります。

【2.2】"Shell"オブジェクトと、"FileSystemObject"オブジェクトを使ってみる

次に、"Shell"オブジェクトと、"FileSystemObject"オブジェクトを使ってファイルの一覧を新規秀丸エディタに出力します。 "Shell"オブジェクトでフォルダを選択し、"FileSystemObject"オブジェクトでファイル一覧を取得します。ファイル一覧(コレクション)にアクセスするため、"getcollection"を使っています。

このマクロでは、色々なオブジェクトのプロパティ・メソッドを使っていることから、戻り値の型に従い getpropobj / getpropstr / callmethod_returnobj / callmethod_returnstrを使い分けています。

各メソッド・プロパティの説明に従い適切な命令を使う必要がありますが、どうしてもわからない場合は、 "member文", "member関数"を使う方法もあります。(プロパティは取得する場合のみで、セットすることは出来ない)

// ファイル一覧の取得
newfile;

//shellオブジェクトの作成
//Set WshShell = Wscript.CreateObject("Shell.Application")
//Set getfolder = WshShell.BrowseForFolder(0,"フォルダを選んでください", &h10)
//If (getfolder Is Nothing) Then
//    WScript.Quit
//End If
//Searchfolder = getfolder.Items.Item.path

//shellオブジェクトの作成
#shell = createobject("Shell.Application");
//createobject関数の成功/失敗をチェック
if(#shell == 0){
    message "Shellのオブジェクト作成に失敗";
    endmacro;
}
insert "shellオブジェクトの作成\n";
insert "フォルダ選択ダイアログの表示\n";
//BrowseForFolder メソッドは、Folderオブジェクトを返す。
#getfolder = callmethod_returnobj(#shell, "BrowseForFolder", 0,"フォルダを選んでください",0x10);
//Folderオブジェクトの取得に成功したかチェック
if(#getfolder == 0){
    //失敗した場合はメッセージを表示して終了
    message "BrowseForFolderが失敗";
    endmacro;
}
insert "getfolder オブジェクト取得\n";

//wshだと、以下の一文で、pathプロパティを取得できるが、
//  Searchfolder = getfolder.Items.Item.path
//秀丸マクロだと、1個づつ順番に取得していく
//Folder オブジェクトから、FolderItems オブジェクトを取得
//FolderItems オブジェクトから、FolderItem オブジェクトを取得
//FolderItem オブジェクトの pathプロパティを参照する

#folderitems = callmethod_returnobj(#getfolder, "Items");
//FolderItems オブジェクトの取得に成功したかチェック
if(#folderitems == 0){
    message "#folderitemsが失敗";
    endmacro;
}
#folderitem = callmethod_returnobj(#folderitems, "Item");
//FolderItem オブジェクト の取得に成功したかチェック
if(#folderitem == 0){
    message "#folderitemが失敗";
    endmacro;
}
//FolderItem オブジェクトの pathプロパティを取得
$Searchfolder = getpropstr(#folderitem, "path");
//メソッドの呼び出しが成功したかどうか確認
if(getresultex(10) == false){
    message "pathプロパティが失敗";
    endmacro;
}
insert "読み込む場所:" + $Searchfolder + "\n";
//#shell は使わないので解放する
releaseobject #shell;

message "出力するファイルの拡張子を入力してください。\n" +
        "一度に複数の拡張子を指定する場合は\";\"で区切ってください。\n" +
        "\".\"は入力しないでください。例)txt;log)\nキャンセルで入力を終了します。";
$chk_ext = "";
while(1){
    $ext = input("拡張子を入力してください。キャンセルで終了");
    if(!result) break;
    if($ext == "")break;
    $chk_ext = $chk_ext + tolower($ext) + ";";
}
if(strlen($chk_ext) > 0){
    insert "表示するファイルの種類:" + $chk_ext + "\n";
    $chk_ext = ";" + $chk_ext;
}else{
    insert "表示するファイルの種類:全て\n";
}

//ファイル一覧の取得
//Set WshFso = Wscript.CreateObject("Scripting.FileSystemObject")
//'フォルダオブジェクトの取得
//Set SFolder = WshFso.GetFolder(Searchfolder)
//'ファイルコレクションを取得
//Set SFiles = SFolder.Files
//for each Checkfile in SFile
//    get_ext = WshFso.GetExtensionName(Checkfile.Path)
//next
/
//ファイル一覧の取得
//FileSystemObject を取得
#WshFso = createobject("Scripting.FileSystemObject");
//createobject関数の成功/失敗をチェック
if(#WshFso == 0){
    message "#folderitemが失敗";
    endmacro;
}

//指定されたフォルダの Folderオブジェクト を取得
#Folder = callmethod_returnobj(#WshFso, "GetFolder", $Searchfolder);
//Folder オブジェクトの取得に成功したかチェック
if(#Folder == 0){
    message "Folderオブジェクトの取得に失敗";
    endmacro;
}
//Folderオブジェクト からファイル一覧(filesコレクション)を取得
#Files  = getpropobj(#Folder,"Files");
// オブジェクトの取得に成功したかチェック
if(getresultex(10) == false){
    message "Filesコレクションの取得に失敗";
    endmacro;
}
insert "ファイルの一覧\n";
while(1){
    //filesコレクションから呼び出し
    //#Fileには、コレクションの中身である「ファイルオブジェクト」が入る
    #File = getcollection(#Files);
    if(#File == 0) break;
    //ファイルオブジェクトから、パスプロパティを読み出し
    $get_filepath = getpropstr(#File, "Path");
    if(getresultex(10) == true){
        //拡張子を取得
        $get_ext = callmethod_returnstr(#WshFso, "GetExtensionName", $get_filepath);
        if(getresultex(10) == true){
            if(strlen($chk_ext) > 0){
                $get_ext = tolower($get_ext);
                if(strstr($chk_ext, ";" + $get_ext + ";") > -1){
                    insert $get_filepath + "\n";
                }
            }else{
                insert $get_filepath + "\n";
            }
        }
    }
}
//filesコレクション を解放
#File = getcollection(#Files, 3);
//各オブジェクトを解放
releaseobject #Files;
releaseobject #Folder;
releaseobject #WshFso;

endmacro;

【2.3】COM関連の特殊な動作

allowobjparam 文

秀丸エディタマクロで使用できるのは 数値・文字列 だけですが、メソッド・プロパティの戻り値がバイナリデータの場合や、引数にオブジェクト・バイナリデータを指定する場合もあります。

そのような場合に、allowobjparam 文を使用します。

  • 引数に「1」を指定すると、数値をオブジェクトとして解釈する事を許可します。
  • 引数に「2」を指定すると、文字列をバイト配列(バイナリデータ)として解釈する事を許可します。
  • 引数に「0」を指定すると、「1」 「2」の許可を無効化します。

第二引数以降を指定すると、呼び出すメソッド等のパラメータに対し、対象を個別に指定することが出来ます。

オブジェクトを引数に渡す場合

COMのメソッドやプロパティには、引数にオブジェクトを指定する場合があります。

//ArrayList オブジェクト生成
#array = createobject("System.Collections.ArrayList");
//GetArrayListDataTypeメソッドは、
//第一引数にArrayList オブジェクト
//第二引数に、数値
//を指定する必要がある。
$ret = callmethod_returnstr(#lib, "GetArrayListDataType", #array, #n);

第一引数に「#array」を指定して、オブジェクトを指定しているように見えますが、上記マクロは正常に動作しません。

createobject callmethod_returnobj getpropobj で戻り値を数値変数で受け取っていますが、これは「オブジェクト識別用の番号」が格納されているだけで、オブジェクト本体ではありません。

つまり、以下の"GetArrayListDataType"メソッド呼び出しは、第一引数に「オブジェクト」を要求しているのに、「数値」を渡していることになるので失敗します(呼び出すメソッドやプロパティ次第ですが)。

$ret = callmethod_returnstr(#lib, "GetArrayListDataType", #array, #n);

引数に「オブジェクト」を渡したい場合は、allowobjparam 文 を使い、オブジェクトそのものを渡すように指示します。

使い方は2通りありますが、引数に指定する場合は後者の方法を推奨します。

  • 使い方その1

    allowobjparam 文の引数として、「1」を指定します。

    allowobjparam 1;
    $ret = callmethod_returnstr(#lib, "GetArrayListDataType", #array, #n);
    allowobjparam 0;

    この場合、callmethod_returnstr 文が失敗した場合、パラメータの数値部分を「オブジェクト」として再度実行します。

    気をつけないといけないのは、callmethod_returnstr 文の呼び出しが失敗しない場合、再実行されない事です。

  • 使い方その2

    対象となるパラメータを指定します。

    allowobjparam 文で、第二引数以降を指定すると、対象になるパラメータの扱いを指定できます。呼び出すメソッドの引数の数に応じて、解釈する方法を順に指定します。引数の意味を変換しない場合は、「0」を指定します。

    オブジェクトはパラメータとして渡すので、第一引数は"0"でもかまいません。 (メソッドの戻り値が、バイト配列の場合、第一引数に"2"を指定する必要があります)

    "GetArrayListDataType" の場合、引数は2個で、最初はオブジェクトを渡すので「1」を、次の引数は変換しないので「0」を、第二引数に指定していきます。

    allowobjparam 0,1,0;
    $ret = callmethod_returnstr(#lib, "GetArrayListDataType", #array, #n);
    allowobjparam 0;

allowobjparamで変換を有効にした場合は、解除するのを忘れないようにしましょう。解除しない場合は、他のメソッドやプロパティにも影響してしまします。

バイト配列を引数に渡したし、戻り値で受け取る場合

戻り値として「バイト配列」を受け取る場合は、第1引数のみ指定します。

//対象のヘッダ部分を読み込み為、ADODBを使用する。
#objStream = createobject( "ADODB.Stream" );
setpropnum #objStream, "Type", 1;//adTypeBinary
callmethod #objStream, "Open";
callmethod #objStream, "LoadFromFile", $target;
//バイナリデータとして扱うように設定
allowobjparam 2;
//先頭から512バイト読み込み
$buff = callmethod_returnstr(#objStream, "Read", 512);
allowobjparam 0;

引数として指定する場合は、オブジェクトと同じ方法が使えますが、指定する値は「2」になります。

allowobjparam 0,2;
//出力用ストリームにバイナリで書き込み
callmethod ##Stm_out, "Write", $$bindata;
allowobjparam 0;
戻り値が配列(SAFEARRAY)の場合

秀丸エディタ Ver.8.98以降では、戻り値が配列(SAFEARRAY)のメソッドに対応しています。

戻り値が配列(SAFEARRAY)の場合、自動的にオブジェクトに変換されます。オブジェクトにある以下のプロパティを使用し、各要素を取得します。

  • Count プロパティ
    配列の大きさ、要素数を返す。
  • Item プロパティ
    インデックス(数値)を指定し、要素を取得する。

使い方は多少複雑です。マクロヘルプのサンプルで説明します。

秀丸エディタマクロでは戻り値により命令を使い分けていますが、「配列」という表現はないので member 関数を使い、戻り値はオブジェクトになるので数値型変数で受け取ります。

#objDic = createobject("Scripting.Dictionary");
callmethod #objDic, "add", "Key1", "Value1";
callmethod #objDic, "add", "Key2", 2;
callmethod #objDic, "add", 3, "Value3";

#objKeys = member(#objDic, "Keys"); //ここでオブジェクトに変換される

「Count プロパティ」は数値を返すので、「getpropnum」を使用し、配列の要素数を取得します。

#c = getpropnum(#objKeys, "Count"); //"Count"で要素数

「Item プロパティ」を使用して要素を取得しますが、要素自体の型があらかじめわかっていれば対応した命令を使用します。

  • 数値なら「getpropnum」
  • 文字列なら「getpropstr」

要素の型がわからない場合は、プロパティ取得の成功/失敗( getresultex(10) )をみて判断しするしかありません。

最初に戻り値が文字列という前提で「getpropstr」を使っています。失敗した場合は、戻り値が数値と判断し「getpropnum」を使います。

$key = getpropstr(#objKeys, "Item", #i); //"Item"で要素
if(getresultex(10)!=false){
    //「getpropstr」が成功した場合
    <省略>
    #i=#i+1;
    continue;
}
#key = getpropnum(#objKeys, "Item", #i); //"Item"で要素
if(getresultex(10)!=false){
    //「getpropnum」が成功した場合
    <省略>
    #i=#i+1;
    continue;
}

【3】その他

「.NET Freamework」のクラス

「.NET Freamework」のクラスも、COMから使える物もあります。

以下のコレクションクラスを使えることが確認されています。

詳細は、以下のページから各クラスのページを参照してください。
System.Collections 名前空間 "ArrayList"は、動的配列なので配列サイズは気にしなくても良いし、ソート/逆順ソートも可能になってます。

連想配列は、WSHでも可能で、Dictionary オブジェクトを使います。

COMオブジェクトの有効範囲

createobject文を実行した秀丸エディタ上のみ有効です。秀丸エディタを切り替えた場合は、無効です。 (DLLのロードと同じ)

次のマクロでは、2回目の"Popup"メソッドは、新規秀丸エディタを開いて呼び出しているため、失敗します。 (新規秀丸エディタ上で、createobjectを実行していないので、"WScript.Shell" オブジェクトは存在してない)

//オブジェクトの生成
#WshShell = createobject("WScript.Shell");
if(#WshShell == 0){ //if(getresultex(10) == false){
    message "WScript.Shell オブジェクトの生成に失敗しました。";
    endmacro;
}
// メソッドの呼び出し、7秒待機する。
#BtnCode = callmethod_returnnum(#WshShell, "Popup", "ご機嫌いかがですか?", 7, "お答えください。", 4 + 32);
if(getresultex(10) == false){
    message "Popupメソッドの呼び出しに失敗しました。(1)";
    endmacro;
}
//結果の表示
message str(#BtnCode);

//新規秀丸エディタを開く
newfile;
// メソッドの呼び出し この行は失敗する。
#BtnCode = callmethod_returnnum(#WshShell, "Popup", "ご機嫌いかがですか?", 7, "お答えください。", 4 + 32);
if(getresultex(10) == false){
    #err = getresultex(11);
    message "Popupメソッドの呼び出しに失敗しました。(2)\n0x" + hex(#err);
    endmacro;
}

//オブジェクトの解放
releaseobject #WshShell;
endmacro;

DLLの場合と違い、呼び出しに失敗してもマクロの実行は止まらない為、異常には気がつきにくいです。 (メソッドの呼び出し、プロパティアクセス後は、エラーチェックをした方が良いです)

COMコンポーネントのメソッドの戻り値や、プロパティの型がわからない場合の確認方法

VBScriptの VarType 関数、TypeName 関数 を使えば、戻り値/プロパティの型を確認できます。 (秀丸エディタマクロ上からは、VarType 関数、TypeName 関数を実行できないので、VBScript から実行する必要あり)

これらの関数の引数に、型を知りたい変数、メソッドの戻り値、プロパティを指定すると TypeName 関数は文字列、VarType 関数は数値で型を示す値を返します。

以下のスクリプトを、コマンドプロンプトから、Cscriptで実行してみます。

a = "秀丸エディタ"
WScript.echo  "VarType:" & VarType(a) & vbCrLf & "TypeName:" & TypeName(a)

Set WshShell = Createobject("WScript.Shell")
WScript.echo  "VarType:" & VarType(WshShell) & vbCrLf & "TypeName:" & TypeName(WshShell)

上記スクリプトの実行結果です。TypeName関数は、オブジェクトの場合、オブジェクト名を返すようです。

VarType :8
TypeName:String
VarType :9
TypeName:IWshShell3

各関数の詳細は以下のリンクを参照。


目次に戻る