変奏現実

パソコンやMMORPGのことなどを思いつくまま・・・記載されている会社名・製品名・システム名などは、各社の商標、または登録商標です。

この画面は、簡易表示です

ActiveX

[ActiveX]ブラウザでActiveXを使いたい

EdgeのIEモードもいつまであるのか判らないので・・・

C#のEXEでRESTサービスサーバを作り、ブラウサからデータベースにSQLでアクセスできる様な仕組みをポチポチと作ってみる。勿論、ポート開放なんてしない。

しかし思っていた以上に面倒なことが判明した。

public static object? CreateObject(string progId)
{
Type? t = Type.GetTypeFromProgID(progId);
return t == null ? null : Activator.CreateInstance(t);
}
object? activeXObject = CreateObject("ADODB.Connection");

それっぽいオブジェクトは出来るが、activeXObject変数のクラス名が”System.__ComObject”で、希望するメソッドやプロパティの情報は取得できず、ウォッチビューで中身を見ると[動的ビュー]の中にそれっぽいプロパティが見えるがアクセスする方法は判らなかった。

但し、その変数をそのActiveXObjectのクラスに置き換えると、Accessファイルも開けてしまうのでとりあえず、キャストしてコードを書けば良いらしい。

object? activeXObject
ADODB.Connection? cn = activeXObject;
cn.Open("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=\"C:C:\\Users\・・・\Database1.accd\"");

事前にメソッドの名前さえ判っているなら、動的にメソッドを呼ぶ方法が使える。(カモ

参照:https://dobon.net/vb/dotnet/programing/typeinvokemember.html

string progId="ADODB.Connection";
activeXObject = ActiveX.CreateObject(progId);
// TEST
if (activeXObject != null)
{
    Type t = activeXObject.GetType();
    t.InvokeMember("Open",
        BindingFlags.InvokeMethod,
        null,
        activeXObject,
        new object[] { "Provider=Microsoft.ACE.OLEDB・・・\Database1.accdb\"" });
}
実行すると例外発生
Exception ex:
{"Exception has been thrown by the target of an invocation."}

ん?クラス名がダメなのかな?正しいTypeを取得する方法は予想の斜め上にあった

参照:https://docs.microsoft.com/ja-jp/dotnet/api/system.type.gettype?view=net-6.0#system-type-gettype(system-string)

string progId="ADODB.Connection";
activeXObject = ActiveX.CreateObject(progId);
// TEST
if (activeXObject != null)
{
    Type t = Type.GetType(progId);
    t.InvokeMember("Open",
        BindingFlags.InvokeMethod,
        null,
        activeXObject,
        new object[] { "Provider=Microsoft.ACE.OLEDB・・・\Database1.accdb\"" });
}

これで、メソッドを呼び出すことができたので、キャストだらけのコードも改良の余地がありそうだ。

何でも(メソッド)勘でも(プロパティ)非同期通信で処理すると重そうので、newの後に一式プロパティを送信し、メソッドの実行後に置き換わりそうなプロパティを戻り値を[activeXObject]と[returnValue]に纏めて送信することにする。メソッドを実行するオブジェクトは、ハッシュ管理し、ブラウザからはハッシュを指定して実行する様にした。

※というか

IEの場合、
var table_name = schema.Fields("TABLE_NAME").Value

IE以外の場合、※(…)を[…]に書き換えるしかない様だ。
var table_name = schema.Fields["TABLE_NAME"].Value

な呼び出しをする場合が多い、ここにawait 入れると解読不能になるとしか思えなかった。(ので

※ハッシュ値は諸事象から $”{クラス名}_{ハッシュ値:x}” にした。

javascriptのnewはasyncが指定できないので、newの直後にJavaScript側でプロパティやメソッドの入り口を用意しておく。メソッド実行時にまだハッシュを受信していない場合は、ハッシュ受信時にonobjectIDを処理し、再度メソッド実行を送信するようにする。

※ブラウザ側で不要になったと判断したActiveXObjectオブジェクトは、ActiveXObject.term()を呼び出して、DELETEメソッドでオブジェクトの消去をするように。(できたらいいなぁ

if (this.objectID == null) {
    this.onobjectID = async function () {
        let rc = await this[methodName].apply(this, args);
        resolve(rc);
        return;
     }
} else {
     let path = `ActiveXObject/${this.objectID}/${collectionName}/${methodName}`;
     return new Promise(async (resolve, reject) => {
     let json = await ActiveXObject.staticSendMessage('PUT', this.domain, this.port, path, args);
obj.objectID = json["objectID"];
if (typeof (obj.onobjectID) !== 'undefined')
{
    obj.onobjectID();
}

JavaScript側でActiveXオブジェクトのインスタンスを配列で管理すると使用済みでも残ってしまうので、

インスタンスの管理はC#側のみで行う。

非同期通信の処理待ちをするため await を差し込まないといけないのは面倒だが仕方が無い。

await指定で呼び出す関数の方はasync指定して最後にresolveを渡す様にする。

何気に途中でreturn ; してるケースも同様。

※resolve(true)で処理が途切れるかと思い、手抜きしたら、ダブルresolve(true)してしまい動作が変になったので、直後にreturn を入れておく。

async function xxxxxx () {
・・・・・最後に
Promise.resolve(true);
return;
}

C#側から返す値はJSON形式にしたけど、javascript側からオブジェクトを渡すパラメータはtoStringで済ませているのでちゃんとしないとまずいなぁ。(そのうち何とかしよう

とか、簡単にしようとするとハマるパターンだ。

ちゃんと同期が取れていると

所感)

ブラウザとVisualStudioの両方でブレークポイントを指定して順に動かしていくと、ブラウザとVisualStudioが交互にポップアップして切り替わるのが面白かった。一度お試しあれ。



[ActiveX]JavascriptのActiveXの代案 ※検討中

Windows11でIEが消えていたので、IE用のJavaScriptでActiveXを使ってブラウザでMDBの中身を見るツールが使えない。

そこで、C#でローカルWEB(+REST)サーバEXEを作り、ActiveXをEXEで処理してブラウザは非同期通信でリモートでコントロールさせてみたい。

IE用のindex.htmlとJSファイルはEXEのwwwrootフォルダに入れておき

C#のMainから8000ポートを開き、GETコマンドリクエストがあれば、exeファイルのトコのwwwrootフォルダのファイルを返す様にしておく。

参考文献:簡易Webサーバを実装するには?[2.0のみ、C#、VB]

using System;
using System.IO;
using System.Net;

namespace LocalWebServer
{
class WebFileServer
{
  static void Main()
  {
    string root = @"wwwroot\"; // ドキュメント・ルートは好きな場所でOK
    string prefix = "http://localhost:8000/"; // URLはしっかり書かないとエラる
    // ブラウザでindex.htmlを開く。ココからjsファイルもGETされるハズ
    System.Diagnostics.Process.Start(prefix + "index.html");
  //
    HttpListener listener = new HttpListener();
    listener.Prefixes.Add(prefix); // プレフィックスの登録
    listener.Start();
    while (true) {
      HttpListenerContext context = listener.GetContext();
      HttpListenerRequest req = context.Request;
      HttpListenerResponse res = context.Response;
      Console.WriteLine(req.RawUrl);
      // リクエストされたURLからファイルのパスを求める
      string path = root + req.RawUrl.Replace("/", "\\");
      // ファイルがあれば出力
      if (File.Exists(path)) {
        byte[] content = File.ReadAllBytes(path);
        res.OutputStream.Write(content, 0, content.Length);
      } else {
         // TODO エラー処理 404 あるいはREST処理をしないといけないかもしれない
      }
      res.Close();
    }
  }
}
}

とRESTのWEBサービスっぽく

HTMLファイルは、

<script src="./ActiveX.js">
・・・
<script src="./${他のjsファイル}">

と付け加え、JavaScript上のActiveX(“xxxxx”)の実装を差替える。

ActiveX(objectID) => {
  let promise = new Promise( (resolve, reject) => {
    let domain = "localhost";
    let port = 8000;
    let xhr = new XMLHttpRequest();
    xhr.open('GET', `http://${domain}:${port}/ActiveX/`, true);
    xhr.responseType = 'json';
    xhr.send(null);
    xhr.onload = function(e) {
      if (xhr.readyState == 4) {
        if (xhr.status == 200 ) {
          resolve(JSON.parse(xhr.response));
        }
      }
    };
  });
  return promise;
};

ActiveXの予備元やそのメソッドの呼び出しは全てawaitを追記

async function  xxxx(...)
{
  let obj = await ActiveX("xxxxx");
}

プロパティ呼び出しはC#側でデータを展開しておいた方が良さそう

しかし、メソッドもプロパティもいっぱいあるので

class  ADODB_xxxxxx
    constractor()
    {
        this.className = "ADODB.xxxxxx";
        dummy_properties();
        dummy_methods();
    }
    dummy_properties()
    {
      let properties = ['BOF','EOF',....,'fields'];
      properties.foreach( (p) => {
          this[p] = new Function (`
            set ${p}(v) {
               alert('set ${p} no support.');
            }
            get ${p}() {
               alert('get ${p} no support.');
            }
          `);
      });
    }
    dummy_methods()
    {
      let methods = ['BOF','EOF',....,'fields'];
      methods.foreach( (m) => {
          this[m] = new Function (`
               alert('set ${m} no support.');            
          `);
      });
    }

で、TODO風に作っておいて、後で実装を考えた方が良さそう。

あるいは、各ActiveX用のクラスJSファイルは、ワーカースレッドよろしく

var adodbWorkers = [];
・・・
adodbWorkers["ADODB.xxxxxx"] = new Worker('ADODB.xxxxxx.js');

とやって

adodbWorkers["ADODB.xxxxxx"].postMessage(${メソッド名},${パラメータ1},...,${パラメータn});

onmessage = function(e) {
 switch(e)
 {
   case "BOF":
    どうしよう
     break;
    ・・・
 }
}

adodbWorkers["ADODB.xxxxxx"].onmessage = function(e) {
  何とかかんとか  
 resolve(e,data)とか
}

して、がら空きの実装で使うとこだけ実装する方式で済ませるのがいいかもしれない。




top