Help us understand the problem. What is going on with this article?

C# で COM オブジェクトを作ってみる

More than 5 years have passed since last update.

VBA を書くのはもういやだー(´;ω;`)

C# の味を覚えてしまった私には、もはや VBA で大きなコードを書くのは苦痛です。
VBA もインターフェースなどを駆使すれば綺麗なコードを維持できるけれど、ライブラリは貧弱だし何かと手間が掛かります。
せっかく C# で作った DLL があるのだから、使える環境なら積極的に使って楽しちゃおう。

※COM 自体は言語に依存しているわけではないので Ruby など様々な言語でも使えますよ。
※IEに限定すればクライアントサイドのJavaScriptでも。。。

COM コンポーネントは登録しないと使えないよ

最初に手動で COM コンポーネントを登録する方法を説明します。
コマンドプロンプトから RegAsm.exe を使うんですけど、私の環境では以下の2カ所にありました。
C:\Windows\Microsoft.NET\Framework\v2.0.50727\RegAsm.exe
C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe

どっち使うんだろうなぁ・・・。新しい方使っておけばいいのかな?
わかる方いたらコメントください(o*。_。)oペコッ

hoge.dll を登録する場合
登録:RegAsm.exe /codebase hoge.dll
解除:RegAsm.exe /u hoge.dll

開発環境で登録する場合には Visual Studio 付属のコマンドプロンプトを使えば RegAsm.exe へのパスも通っているので楽ちんです。
Visual Studio 2010
 スタートメニューのVisual Studio 2010 -> Visual Studio Tools -> Visual Studio コマンドプロンプト(2010)
Visual Studio 2013
 スタートメニューのVisual Studio 2013 -> Visual Studio ツール -> 開発者コマンド プロンプト for VS2013

Visual Studio がビルド時に登録してくれるよ

COM の登録はレジストリの変更を伴うので管理者権限が必要ですから、Visual Studio を管理者権限で起動する必要があります。
スタートメニューから右クリして管理者権限で起動するか、以下の方法で常に管理者権限で起動するようにしてしまいましょう。

レジストリエディターを起動して以下のキーに管理者権限で実行したい実行ファイルのフルパスを登録します。
HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers

文字列値を作成して、「値の名前」に実行ファイルのフルパス、「値のデータ」に「RUNASADMIN」と入力します。
Visual Studio のプロセスである devenv.exe と、ソリューションファイルの関連付けに割り当てられている VSLauncher.exe を登録しておくといい感じですよ。

2014-08-29_161157.png

ずっと前に遊んだゲームの体験版のパスが登録されてた(゚д゚)
アンインストール済みのゲームなのに残ってるのは気持ち悪いわ・・・

プロジェクトの設定

新規作成 -> プロジェクトからクラスライブラリを選択します。
プロジェクトのプロパティを開いてアプリケーション -> アセンブリ情報画面の「アセンブリを COM 参照可能にする」をチェック。

2014-08-30_041355.png

ビルド の「COM 相互運用機能の登録」をチェック。

2014-08-30_050028.png

COM コンポーネントを作ってみよう(゚∀゚)

1から作るのはめんどくさいので、前回の記事の自然順で文字列をソートするクラスを使って説明します。

公開するインターフェースを定義する

まずは COM として公開したいメソッドやプロパティをインターフェースにまとめます。

[Guid("C7BE210D-6460-443c-A34D-01727732E56C")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface INaturalComparer
{
    [Description("昇順か降順を指定してね。")]
    NaturalSortOrder SortOrder { get; set; }

    [Description("比較おぷしょん。")]
    NaturalComparerOptions Options { get; set; }

    [Description("オブジェクトの大小関係を比較します。")]
    int Compare(string s1, string s2);
}

インターフェースには Guid 属性と InterfaceType 属性を付けます。
Guidの値についてはあとで説明します。
InterfaceType は事前バインディングと遅延バインディングのどちらに対応するか、もしくは両方に対応するかなどを決定します。
ComInterfaceType.InterfaceIsDual は既定値で両方にします。
詳細は MSDN で確認してください。

インターフェースのメンバーには Description 属性を付けると、COM コンポーネントを利用するほうの開発環境で説明文が表示されます。
VBA だとメソッドの説明は表示されますが、プロパティの説明は表示されません。
COM の仕様でメソッドにしか説明が付かないのか、VBA がプロパティに対応していないのかは分かりませんでした。

2014-08-30_071625.png

クラスにインターフェースを割り当てる

[Guid("50884865-710F-47B8-8398-3E51E8E0C01B")]
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(INaturalComparer))]
public class NaturalComparer : INaturalComparer, IComparer<string>, IComparer
{
    // 実装
}

このようにクラスの属性を設定します。
ComDefaultInterface には先ほど定義した INaturalComparer インターフェースの型情報を指定します。
それ以外の属性については説明が面倒なのでググレカスでお願いします><

インターフェースで参照している Enum を COM に公開する

[ComVisible(true)]
public enum NaturalSortOrder : int
{
    // ...
}

[Flags()]
[ComVisible(true)]
public enum NaturalComparerOptions
{
    // ...
}

ComVisible 属性に true を指定することで COM で参照可能になります。
COM に公開するインターフェースを実装しているクラスや Enum にはすべて ComVisible 属性を付けてください。

GUID を作成する

GUID は他の人と同じ値を使うと衝突してどちらかのクラスが機能しなくなってしまいます。
なのでこの記事のソースからコピペして使ったり、ネットで拾ってくるのはダメ!絶対。
Visual Studio のメニューのツール -> GUID の作成から簡単に作れます。

2014-08-30_043732.png

出来上がった COM オブジェクトを VBA で使ってみる

Excel を起動して VBA のエディタを表示します。
参照設定から、先ほどのプロジェクト名と同じ名前を探してみます。
そういえば、プロジェクト名なににしたっけなぁ・・・。

2014-08-30_072635.png

ClassLibrary1 でした(゚д゚)
みなさんはちゃんとした名前を付けてくださいね

そしたら標準モジュールで NaturalComparer クラスを New して使ってみる。

標準モジュール
Sub test()
    Dim comp As New NaturalComparer
    Debug.Print comp.Compare("hoge1", "hoge10")  '-1
    Debug.Print comp.Compare("hoge2", "hoge10")  '-1
    Debug.Print comp.Compare("hoge11", "hoge10") ' 1
End Sub

OKですね。

イベントも実装してみよう

ちょっと微妙な例ですけど、NaturalComparer クラスで1文字ずつ比較する処理の中で、等価と判断したときにイベントを発生させて追加の比較処理を実装出来るようにしてみます。

[Guid("DDD17585-5DF0-4A5D-9090-21FFEFC0B9F1")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface INaturalComparerEvent
{
    [Description("追加の比較処理をするときにつかってね。")]
    int ExCompare(string a, string b);
}

[Guid("50884865-710F-47B8-8398-3E51E8E0C01B")]
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(INaturalComparer))]
[ComSourceInterfaces(typeof(INaturalComparerEvent))]
public class NaturalComparer : INaturalComparer, IComparer<string>, IComparer
{
    [ComVisible(false)]
    public delegate int ExCompareEventHandler(string a, string b);
    public event ExCompareEventHandler ExCompare;

    protected virtual int LocalCompare(string s1, string s2)
    {
        ...
        // 両方の文字が同じときはカスタム比較するよ
        if (ExCompare != null)
        {
            // イベント発生
            diff = ExCompare(s1[p1].ToString(), s2[p2].ToString());
            if (diff != 0)
            {
                return diff;
            }
        }
        ...
    }
}

このようにイベントは別のインターフェースとして定義して ComSourceInterfaces 属性でクラスに割り当てます。
イベントの実装はオーソドックスにデリゲートを定義してイベントを公開するだけです。
ただしデリゲートは ComVisible 属性で非公開にしておかないと、意味のない空っぽの ExCompareEventHandler クラスが公開されてしまいますので注意です。

2014-08-30_065606.png

VBA から利用してみる

Class1クラスモジュール
Private WithEvents comp As NaturalComparer

Private Sub Class_Initialize()
    Set comp = New NaturalComparer
End Sub

Sub Compare(a As String, b As String)
    Call comp.Compare(a, b)
End Sub

Private Function comp_ExCompare(ByVal a As String, ByVal b As String) As Long
    Debug.Print a
    comp_ExCompare = 0
End Function
標準モジュール
Sub test()
    Dim cls As New Class1
    cls.Compare "hoge1", "hoge10"
End Sub
イミディェイト
h
o
g
e

さいごに

でもさ~、これどんなときに使うの?(´・ω・`)
って思う人が結構いるかもですよね。
私の場合は、主に Excel とクラウドを連携させるためとかで Excel のアドインを C# で作ったりします。
Excel は私のお客さんの間では大人気なので、結構使う機会が多いんですよ。
マクロで C# 使えるようになったらいいのになぁ~

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away