ActiveX コントロールを Web サーバーで動かす

諸般の事情により、急遽 ActiveX コントロールを Web サーバーで動かす要件が発生しました。
本記事はその備忘録になります。そのためかなり読みづらい個所があると思いますが、ご了承ください。


リソースファイルの作成


ActiveX コントロールを使う場合、アプリケーションリソースを使うため、リソースファイルを用意する必要がある。
ちなみに ActiveX コントロールシリアライズ用のインターフェースの実装が義務付けられているため、シリアライズされたデータがリソースファイルに格納される。これがないと ActiveX コントロールインスタンスを生成できない。

まず Form を用意し、アプリケーションで必要な ActiveX を Form に貼りつける。

次に ActiveX を使用するクラスと同じ名前のリソースファイルを作成する。

Form のリソースファイルを XML エディタで開くと以下のようなデータが存在する筈である。

<data name="HogeHoge.OcxState" mimetype="application/x-microsoft.net.object.binary.base64">
    <value>
        AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj0yLjAuMC4w
        LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACFTeXN0
        ZW0uV2luZG93cy5Gb3Jtcy5BeEhvc3QrU3RhdGUBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAtwAAAAIB
        AAAAAQAAAAAAAAAAAAAAAKIAAAAAAwAACAAAAAAABQAAAAAAAADwPwMAAAAAAAUAAAAAAAAAAAAIAAIA
        AAAAAAMAAQAAAAsA//8DAAAAAAALAP//CAACAAAAAAADADIAAAALAAAACAAKAAAAZgB1AGwAbAAAAAsA
        AAALAAAACwD//wsA//8LAAAACAACAAAAAAAIAAIAAAAAAAgAAgAAAAAACAACAAAAAAALAAAAwAcAAGEC
        AAAL
    </value>
</data>

「○○.OcxState」の部分をコピーし、クラス名.resx を開いて タグ間の末尾にコピペする。コピペが完了したら不要になった Form を削除する。


ComponentResourceManager によるリソースの取得


次に ActiveX コントロールインスタンス生成であるが、ComponentResourceManager を使ってリソースを取得する。

Dim resources As New ComponentResourceManager(GetType(HogeHoge))

_hogeObj = New AxHogeHogeLib.AxObject()
CType(_hogeObj, ISupportInitialize).BeginInit()
_hogeObj.OcxState = CType(_resources.GetObject("HogeHoge.OcxState"), AxHost.State)
_hogeObj.CreateControl()
' 以下、プロパティの初期設定を行う
' ・・・・・・・
CType(_hogeObj, ISupportInitialize).EndInit()

AspCompat属性の設定によるページのシングルスレッド化


ActiveX の生成はシングルスレッドアパートメント(STA) でしか生成できないため、、@Pageディレクティブの AspCompat 属性に True を設定し、Web ページ自体を STA で動作するように変更する。

<%@ Page Language="vb" AspCompat="true"・・・・・・ %>

これで、以下のような感じで ASP.NETActiveX コントロールが利用できる。

sample.aspx

<%@ Page Language="vb" AutoEventWireup="false" AspCompat="true"
    CodeBehind="result.aspx.vb" Inherits="WebApplication1.sample" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:Label ID="lblTallCharge" runat="server" Text="0" />
    </form>
</body>
</html>


sample.aspx.vb

Partial Public Class sample
    Inherits System.Web.UI.Page

#Region "イベント"

    ''' <summary>
    ''' ページロード時のイベントハンドラ
    ''' </summary>
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load

        If (Page.PreviousPage IsNot Nothing) Then

            ' 他ページからのポストバック
            If (Page.PreviousPage.IsCrossPagePostBack) Then

                _start = CType(Page.PreviousPage.FindControl("txtStart"), TextBox).Text
                _goal = CType(Page.PreviousPage.FindControl("txtGoal"), TextBox).Text
                Try
                    Using hogehoge As New HogeClass()
                        Dim toll As Integer = hogehoge.GetTallCharge(_start, _goal)
                        lblTallCharge.Text = String.Format("{0:#,##0}", toll)
                    End Using

                Catch ex As Exception
                    lblTallCharge.Text = _
                        ex.Message + vbCrLf + _
                        "サーバーで障害が発生しました。しばらくお待ちください。"
                End Try
            End If

        End If

    End Sub

#End Region

End Class


以下は Page クラスで呼び出す ActiveX コントロールをラップしたクラス。リソースファイル「HogeClass.resx」を同じフォルダに作成し、ActiveX コントロールで使うリソースを用意しておく。

HogeClass.vb

Imports System.ComponentModel
Imports System.Windows.Forms
Imports AxHogeHogeLib


''' <summary>
''' 高速料金算出クラス
''' </summary>
''' <remarks>高速料金を算出します。</remarks>
Public Class HogeClass
    Implements IDisposable

#Region "フィールド"

    ''' <summary>経路検索用 ActiveX オブジェクト</summary>
    Private WithEvents _hogeObj As AxHogeHogeLib.AxObject
    ''' <summary>重複する呼び出しを検出するには</summary>
    Private _disposedValue As Boolean = False

#End Region

#Region "コンストラクタ・デストラクタ"

    ''' <summary>
    ''' コンストラクタ
    ''' </summary>
    Public Sub New()
        Try
            Me.InitializeComponent()
        Catch ex As Exception
            Throw
        End Try
    End Sub

    ''' <summary>
    ''' リソースを解放します。
    ''' </summary>
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If (_disposedValue = False) Then
            If (disposing) Then
                ' TODO: 他の状態を解放します (マネージ オブジェクト)。
            End If

            If (_hogeObj IsNot Nothing) Then
                _hogeObj.Dispose()
            End If

        End If
        _disposedValue = True
    End Sub

    ''' <summary>
    ''' リソースを解放します。
    ''' </summary>
    Public Sub Dispose() Implements IDisposable.Dispose
        Me.Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub

#End Region

#Region "メソッド"

    ''' <summary>
    ''' 経路検索用コントロールを初期化します。
    ''' </summary>
    Private Sub InitializeComponent()

        Try
            Dim resources As New ComponentResourceManager(GetType(HogeClass))

            _hogeObj = New AxHogeHogeLib.AxObject()
            CType(_hogeObj, ISupportInitialize).BeginInit()
            _hogeObj.OcxState = CType(_resources.GetObject("HogeHoge.OcxState"), AxHost.State)
            _hogeObj.CreateControl()
            ' 以下、プロパティの初期設定を行う
            ' ・・・・・・・
            CType(_hogeObj, ISupportInitialize).EndInit()

        Catch ex As Exception
            Throw
        End Try

    End Sub

    ''' <summary>
    ''' 高速料金を検出します。
    ''' </summary>
    ''' <param name="start">出発地</param>
    ''' <param name="goal">目的地</param>
    ''' <returns>算出された高速料金</returns>
    Public Function GetTallCharge(ByVal start As String, ByVal goal As String) As Integer

        Dim ret As Integer = 0
        Try
            ret = _hogeObj.GetTollWayCharge(start, goal)
        Catch ex As Exception
            Throw
        End Try

        Return ret

    End Function

#End Region

End Class