ExcelのRTD用DCOMサーバの作り方

Excelのワークシート関数 RTD (RealTimeData)用サーバ作成メモ - しがないプログラマ の日記 の続きです。
とりあえず、DCOMとして別プロセスで動くRTDサーバが作れたのではまった場所のメモです。

前提として前回も書きましたが、.NETで DCOMを書くのは大変なのでVBActiveX.EXEをWrapperとして .NETでの実装をVBから呼び出す形で動かします。VB .NETとの間はCOM連携機能を使うことになります。


COMとして RTDサーバを作る場合には、この方法で .NETで可能です。しかし、.NETの言語サポートではDCOMはやってくれないため、C++/CLIなどで自前で ATLをガシガシ書く必要があります。しかし、今回はそれはやらない方向で*1

ActiveX EXE Wrappers - CodeProject
ここを見ると、.NETでCOMに公開したクラスを VB6でWrapperを書けば DCOMとして動かすことができるらしいです。そんな情報が見つかったので今回はこのWarpperを作る方針にしました。


簡単にまとめると、以下のようになります。

  • .NETでGUIを用意しExeとする。
  • .NETでIRtdServerを実装したクラスを作り、COMに公開する。
    • .NETで exeを作成後に、regasmコマンドで以下のコマンドを実行する。

regasm RTDServer.exe /tlb /codebase *2

  • VB6でIRtdServerを実装した公開クラスを作り、ActiveX.EXEとする。
    • IRtdServerの実装は上記の .NETの実装をただ呼ぶだけにする。
  • ActiveX.EXEのEntryPointから、.NET側のGUIを表示する。
  • ActiveX.EXEをレジストリに登録し、ExcelのRTD関数から呼び出す。

RTDServerWrapper.exe /RegServer

.NETでの IRtdServerの実装例

何もしない空実装ですが

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using Microsoft.Office.Interop.Excel;

namespace RTDServer {
	public class MessageEventArgs: EventArgs {
		public string message;
		public MessageEventArgs(string msg) {
			message = msg;
		}
	}

	[ComVisible(true)]
	public class RTDFunctions : IRtdServer {
		public delegate void MessageEventHandler(object sender, MessageEventArgs e);
		public event MessageEventHandler message;

		private static RTDFunctions instance_;
		public static RTDFunctions GetInstance() {
			return instance_;
		}

		private IRTDUpdateEvent callback_;

		public RTDFunctions() {
			instance_ = this;
		}

		public bool Service {
			get { return callback_ != null; }
		}


		public int ServerStart(IRTDUpdateEvent CallbackObject) {
			SendMessage("Start Service.");
			callback_ = CallbackObject;
			return 1;
		}
		public void ServerTerminate() {
			SendMessage("Terminate Service.");
			callback_ = null;
		}

		public object ConnectData(int TopicID,
				[MarshalAs(UnmanagedType.SafeArray)] ref Array Strings,
				ref bool GetNewValues) {
			SendMessage(String.Format("ConnectData Detail: ID={0}", TopicID));
			return "";
		}
		public void DisconnectData(int TopicID) {
			SendMessage(String.Format("DisconnectData: ID={0}", TopicID));
		}
		public int Heartbeat() {
			SendMessage(String.Format("Heartbeat"));
			return 1;
		}

		[return: MarshalAs(UnmanagedType.SafeArray)]
		public Array RefreshData(ref int TopicCount) {
			object[,] refreshed = new object[2, 0];
			TopicCount = 0;
			SendMessage(String.Format("RefreshData: Count={0}", TopicCount));
			return refreshed;
		}

		private void SendMessage(string msg) {
			if (message != null) {
				message(this, new MessageEventArgs(msg));
			}
		}


		public void StartApplication() {
			System.Windows.Forms.Application.EnableVisualStyles();
			System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);
			System.Windows.Forms.Application.Run(new 画面());
		}
	}
}
VB6でのIRtdServerの実装例

上記の .NETが公開している RTDServer.RTDFunctionsのtlbファイルを参照するように設定します。

Option Explicit

Implements IRtdServer

Function IRtdServer_ConnectData(ByVal TopicID As Long, ByRef Strings() As Variant, GetNewValues As Boolean) As Variant
    Dim ret As Variant
    ret = MyRtdServer.ConnectData(TopicID, Strings, GetNewValues)
    IRtdServer_ConnectData = ret
End Function

Sub IRtdServer_DisconnectData(ByVal TopicID As Long)
    MyRtdServer.DisconnectData (TopicID)
End Sub

Function IRtdServer_Heartbeat() As Long
    IRtdServer_Heartbeat = MyRtdServer.Heartbeat()
End Function

Function IRtdServer_RefreshData(TopicCount As Long) As Variant()
    IRtdServer_RefreshData = MyRtdServer.RefreshData(TopicCount)
End Function

Function IRtdServer_ServerStart(ByVal CallbackObject As Excel.IRTDUpdateEvent) As Long
    Dim ret As Long
    ret = MyRtdServer.ServerStart(CallbackObject)
    IRtdServer_ServerStart = ret
End Function

Sub IRtdServer_ServerTerminate()
    MyRtdServer.ServerTerminate
End Sub

ソースを貼ったら長くなったのではまった点などは次エントリにします。

*1:そんな大変なこともできないし、そんなスキルもないので

*2:ここで /codebaseはGASに登録するアセンブリであれば不要です