VBA から .NET 8 の処理を呼びたい場面はまだ普通にあります。特に、Excel や Access の既存資産はそのまま残しつつ、重い処理、文字列処理、HTTP、暗号、業務ロジックのような部分だけを C# に逃がしたいときです。
ただ、CreateObject で遅延バインディングに寄せると、VBA 側では Object だらけになります。IntelliSense は弱くなり、メソッド名の打ち間違いは実行時まで見つからず、だんだん文字列頼みのぬかるみに沈みます。
そこで今回は、.NET 8 の DLL を COM 公開し、dscom でタイプライブラリ(TLB)を生成し、VBA から早期バインディングで型付き利用するところに絞って整理します。
.NET Framework + RegAsm の昔話、IDL を手書きして MIDL で固める話、Reg-Free COM の話は今回は横に置きます。ここでは、.NET 8 / COM host / dscom / VBA early binding の一本道だけを扱います。
なお、この記事に登場するコードは、ビルド・検証できるサンプル一式(COM 公開ライブラリ、TLB 生成・登録スクリプト、VBA モジュール、ユニットテスト)として GitHub で公開しています。
dotnet8-dll-typed-vba-com-dscom-tlb - komurasoft-blog-samples (GitHub)
1. まず結論
先に結論だけ並べると、流れはこうです。
- .NET 8 のクラスライブラリを
EnableComHosting=trueでビルドする - COM に見せる 明示的なインターフェイス と クラス を作る
- クラスは
ClassInterfaceType.Noneにして、AutoDualに逃げない - VBA から使うインターフェイスは
InterfaceIsDualにする - ビルド後にできた
*.dllから、dscom tlbexportで*.tlbを作る regsvr32で*.comhost.dllを登録するdscom tlbregisterで*.tlbを登録する- VBA で参照設定を追加し、
Dim x As ライブラリ名.IYourInterfaceのように型付きで使う
要するに、COM の入口は .NET SDK が作る *.comhost.dll、型情報は dscom が作る *.tlb、VBA はその TLB を見て早期バインディングする、という構成です。
2. この構成の全体像
まず、何が何の役目なのかを 1 枚で見ます。
flowchart LR
VBA["VBA / Excel / Access"] -->|参照設定した TLB で型情報取得| TLB["VbaTypedComSample.tlb"]
VBA -->|COM 呼び出し| COMHOST["VbaTypedComSample.comhost.dll"]
COMHOST --> DOTNET["VbaTypedComSample.dll (.NET 8)"]
DOTNET --> RUNTIME[".NET 8 Runtime"]
それぞれの役割はこうです。
| ファイル | 役割 |
|---|---|
VbaTypedComSample.dll |
.NET 8 の実装本体 |
VbaTypedComSample.comhost.dll |
COM から呼ばれる入口 |
VbaTypedComSample.tlb |
VBA が見る型情報 |
VbaTypedComSample.deps.json |
依存関係の解決情報 |
VbaTypedComSample.runtimeconfig.json |
.NET ランタイム起動情報 |
ここで大事なのは、VBA が型を知るために必要なのは TLB で、COM の起動入口として必要なのは comhost だという点です。
.dll 単体を渡して終わり、とはいかないあたりが、COM の世界の素直でないところです。
3. 最初に決めること - 32bit / 64bit を揃える
ここを外すと、かなりの確率で ActiveX コンポーネントはオブジェクトを作成できません。 方面へ転がります。
Office / VBA と COM サーバーの bitness は揃えてください。
| 利用側 | .NET 側の目安 | TLB 生成 | 登録コマンド |
|---|---|---|---|
| 64bit Office | x64 / win-x64 |
dscom |
C:\Windows\System32\regsvr32.exe |
| 32bit Office(64bit Windows 上) | x86 / win-x86 |
dscom32.exe |
C:\Windows\SysWOW64\regsvr32.exe |
.NET 5+ 以降の COM host では、AnyCPU のままにすると *.comhost.dll が 64bit 側に寄りやすく、32bit Office と噛み合わないことがあります。なので、Office に合わせて x86 / x64 を明示したほうが安全です。
この記事のコードは 64bit Office 向けを例にします。32bit Office なら、後で出てくる x64 を x86、win-x64 を win-x86 に読み替えてください。
4. .NET 8 側を作る
ここでは、VBA から Add、Divide、Hello を呼べる最小サンプルにします。
4.1 .csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<EnableComHosting>true</EnableComHosting>
<PlatformTarget>x64</PlatformTarget>
<NETCoreSdkRuntimeIdentifier>win-x64</NETCoreSdkRuntimeIdentifier>
</PropertyGroup>
</Project>
ポイントは EnableComHosting です。これを付けると、ビルド時に VbaTypedComSample.comhost.dll が生成されます。
4.2 アセンブリ全体はデフォルトで COM 非公開にしておく
COM に見せる型だけ ComVisible(true) にしたいので、アセンブリ全体は false にしておくのが楽です。
using System.Runtime.InteropServices;
[assembly: ComVisible(false)]
4.3 公開するインターフェイスとクラスを書く
using System.Runtime.InteropServices;
namespace VbaTypedComSample;
[ComVisible(true)]
[Guid("2A1BBEDE-DE6E-4C34-AD60-2E9E0E33E999")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface ICalculator
{
[DispId(1)]
int Add(int x, int y);
[DispId(2)]
double Divide(double x, double y);
[DispId(3)]
string Hello(string name);
}
[ComVisible(true)]
[Guid("FAD1C752-0BB6-4DDD-889F-FE446350847A")]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(ICalculator))]
public class Calculator : ICalculator
{
public Calculator()
{
}
public int Add(int x, int y) => checked(x + y);
public double Divide(double x, double y)
{
if (y == 0)
{
throw new ArgumentOutOfRangeException(nameof(y), "0 では割れません。");
}
return x / y;
}
public string Hello(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return "Hello";
}
return $"Hello, {name}";
}
}
このコードで押さえておきたいのは、以下の点です。
Guidは インターフェイスと クラスに別々に振るClassInterfaceType.Noneにして、自動生成クラスインターフェイスに依存しない- VBA で扱いやすいように
InterfaceIsDualにする DispIdを振っておくと、公開後にメソッド順をいじったときの事故を減らしやすい- COM から
Newされるので、public な引数なしコンストラクターを用意する
5. ビルドする
Release ビルドします。
dotnet build -c Release
ビルド後、出力フォルダには少なくとも次のようなファイルが並びます。
bin/
Release/
net8.0-windows/
VbaTypedComSample.dll
VbaTypedComSample.comhost.dll
VbaTypedComSample.deps.json
VbaTypedComSample.runtimeconfig.json
配布や登録で使うのはこのフォルダです。あとで配置場所を変えるなら、登録もやり直しになります。
6. dscom で TLB を生成する
6.1 64bit の場合
まずは dscom を入れます。
dotnet tool install --global dscom
次に、ビルドしたアセンブリから TLB を生成します。
dscom tlbexport .\bin\Release\net8.0-windows\VbaTypedComSample.dll --out .\bin\Release\net8.0-windows\VbaTypedComSample.tlb
6.2 32bit Office 向けの場合
ここは少しだけ罠です。32bit Office 向けに TLB を作るなら dscom32.exe を使うのが安全です。
.\tools\dscom32.exe tlbexport .\bin\Release\net8.0-windows\VbaTypedComSample.dll --out .\bin\Release\net8.0-windows\VbaTypedComSample.tlb
7. COM host と TLB を登録する
ここは 管理者権限のコマンドプロンプト / PowerShellで実行してください。
7.1 64bit Office / 64bit COM の場合
$out = Resolve-Path .\bin\Release\net8.0-windows
C:\Windows\System32\regsvr32.exe "$out\VbaTypedComSample.comhost.dll"
dscom tlbregister "$out\VbaTypedComSample.tlb"
7.2 32bit Office(64bit Windows 上)の場合
$out = Resolve-Path .\bin\Release\net8.0-windows
C:\Windows\SysWOW64\regsvr32.exe "$out\VbaTypedComSample.comhost.dll"
.\tools\dscom32.exe tlbregister "$out\VbaTypedComSample.tlb"
ここでやっていることは 2 つです。
regsvr32で*.comhost.dllを COM サーバーとして登録するtlbregisterで*.tlbをタイプライブラリとして登録する
8. VBA で参照設定して、型付きで使う
- Excel または Access を開く
- VBA エディタを開く
ツール->参照設定- 一覧にライブラリが出ていればチェックを入れる
- 一覧に見えなければ
参照...からVbaTypedComSample.tlbを選ぶ
Option Explicit
Public Sub UseCalculator()
Dim calc As VbaTypedComSample.ICalculator
Set calc = New VbaTypedComSample.Calculator
Debug.Print calc.Add(10, 20)
Debug.Print calc.Divide(10, 4)
Debug.Print calc.Hello("VBA")
End Sub
これで、VBA 側にはこんな恩恵があります。
- IntelliSense が効く
- メソッド名の typo が実行前に見つけやすい
- Object Browser で公開 API を確認できる
Objectベタ書きより読みやすい
8.1 例外は VBA 側では COM エラーになる
たとえば Divide(10, 0) のように .NET 側で例外が投げられると、VBA 側では COM エラーとして見えます。
Option Explicit
Public Sub UseCalculatorWithErrorHandling()
On Error GoTo EH
Dim calc As VbaTypedComSample.ICalculator
Set calc = New VbaTypedComSample.Calculator
Debug.Print calc.Divide(10, 0)
Exit Sub
EH:
Debug.Print Err.Number
Debug.Print Err.Description
End Sub
9. 配布するときの考え方
配布時に大事なのは、DLL 単体を配るのではなく、出力一式を置くことです。
VbaTypedComSample.dll
VbaTypedComSample.comhost.dll
VbaTypedComSample.deps.json
VbaTypedComSample.runtimeconfig.json
VbaTypedComSample.tlb
(必要なら依存 DLL 一式)
さらに、クライアント PC には 対応する .NET 8 ランタイムが必要です。COM host は self-contained 配布ではなく、基本的に framework-dependent な運用になります。
10. はまりどころ
10.1 AnyCPU のまま放置しない
VBA / Office の bitness と COM host の bitness がズレると、かなり気持ち悪い失敗のしかたをします。
- 64bit Office なら
x64/win-x64 - 32bit Office なら
x86/win-x86
10.2 ClassInterfaceType.AutoDual を使わない
一見ラクですが、公開後にメンバー順や構成を触ると壊しやすいです。
VBA から型付きで安定して使いたいなら、明示インターフェイスを定義し、クラスは ClassInterfaceType.Noneにしておくのが定石です。
10.3 GUID を軽率に再生成しない
COM では GUID が契約そのものです。IID や CLSID を公開後に軽率に入れ替えると、既存の VBA 参照や登録が壊れます。
10.4 公開済みインターフェイスを壊さない
COM は「後から 1 個メソッド足しただけ」でも平和に済まないことがあります。
ICalculatorは残す- 変更が大きいなら
ICalculator2を新設する - クラスは両方実装してもよい
10.5 型は地味に寄せる
VBA に見せる境界では、あまり格好をつけないほうが安全です。
相性が良いのは、まずはこのへんです。
intdoubleboolstringDateTimedecimalenum
10.6 Office を開いたまま更新しない
Excel や Access が DLL を掴んだままになり、ビルドや再登録で面倒が起きることがあります。
- Office を閉じる
- 必要なら登録解除する
- ビルドし直す
- もう一度登録する
11. まとめ
.NET 8 の DLL を型付きで VBA から使うという話は、COM 公開 + dscom で TLB 生成に絞ってしまえば、それほど怖い手順ではありません。.NET 8 側は EnableComHosting=true にして明示インターフェイス(クラスは ClassInterfaceType.None、VBA 向けは InterfaceIsDual)を用意し、dscom tlbexport で TLB を作り、regsvr32 で *.comhost.dll を、dscom tlbregister で *.tlb を登録する。あとは VBA で参照設定を入れて早期バインディングするだけです。
迷ったときは、COM host と TLB を分けて考えるのがコツです。
- 起動入口は
*.comhost.dll - 型情報は
*.tlb - 実装本体は
*.dll
12. 参考資料
- この記事のサンプルコード一式(COM 公開ライブラリ、スクリプト、VBA、テスト) - komurasoft-blog-samples (GitHub)
- Expose .NET components to COM - Microsoft Learn
- COM 相互運用のために .NET 型を修飾する - Microsoft Learn
- ComInterfaceType 列挙型 - Microsoft Learn
- ClassInterfaceType 列挙型 - Microsoft Learn
- COM 呼び出し可能ラッパー - Microsoft Learn
- DispIdAttribute クラス - Microsoft Learn
- dscom - NuGet Gallery
- How to use the Regsvr32 tool and troubleshoot Regsvr32 error messages - Microsoft Support
- .NET 8 downloads
関連する記事
同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。
C#のExcel操作でEXCEL.EXEが残る問題 ── COM参照の解放パターンと置き換えの判断
C#からExcelをCOM操作するとEXCEL.EXEプロセスが残る問題を、COM参照カウントとRCWの仕組みから整理します。2ドットルールの罠、ReleaseComObjectとGCによる解放パターン、置き換え判断まで解説します。
ExcelマクロVBAをPower Automateへ移行する ── Officeスクリプトで置き換える範囲と、VBAのまま残す範囲
Excel VBAマクロをPower Automateへ移行できるかを整理します。Officeスクリプトで置き換えられる範囲とVBAにしかできないこと、コネクタの制限値、ライセンス要件、棚卸しから始める段階移行の進め方まで解説します。
DLL・COMインターフェースの後方互換性 ── どの変更が呼び出し側を壊すのかの判断表
DLLやCOMコンポーネントのどの変更が呼び出し側を壊すのか。バイナリ互換・ソース互換・動作互換の3層を整理し、変更内容別の判断表、COMインターフェース不変の鉄則、semver運用までを実務ガイドとしてまとめます。
Arm版Windowsで業務アプリは動くのか ── x64エミュレーション(Prism)とネイティブDLL・COMの現実
「Arm版Windowsで業務アプリは動くのか」に開発者・情シス向けに答えます。x64エミュレーション(Prism)の仕組み、ドライバーなど動かない層、.NETのAnyCPUとP/Invokeの問題、Arm対応チェックリストまで整理します。
Windowsのプロセス間通信をどう選ぶか ── 名前付きパイプ / TCP / gRPC / 共有メモリ / COM 判断表
Windowsアプリ同士の連携手段をどう選ぶか。名前付きパイプ、ローカルTCP、gRPC、共有メモリ、ファイル連携、COMの得意分野と落とし穴を判断表で整理し、定番構成と名前付きパイプの実装例まで解説します。
関連トピック
このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。
Windows技術トピック
Windows 開発、不具合調査、既存資産活用の技術トピックをまとめた入口です。
ActiveX / 移行テーマ
COM / ActiveX / OCX を残すか、包むか、置き換えるかを整理するトピックです。
このテーマがつながるサービス
この記事は次のサービスページにつながります。近い入口からご覧ください。
Windowsアプリ開発
VBA、COM、Office、.NET 8、型ライブラリ生成まで含めた接続面の設計は Windows アプリ開発と強く結び付くので、Windowsアプリ開発 と相性がよいテーマです。
技術相談・設計レビュー
既存 VBA 資産を .NET 8 とつなぐ境界設計、bitness、登録、TLB 生成、配布方式まで含めて整理したい場合は、技術相談・設計レビューとして進めやすいです。
よくある質問
この記事のテーマについて、相談時によくある質問をまとめています。
- .NET 8のDLLをVBAから型付き(早期バインディング)で使うには何が必要ですか?
- .NET 8のクラスライブラリをEnableComHosting=trueでビルドして*.comhost.dllを生成し、dscom tlbexportで*.tlbを作ります。次にregsvr32で*.comhost.dllを、dscom tlbregisterで*.tlbを登録し、VBAの参照設定でそのTLBを追加すれば、Dim x As ライブラリ名.IYourInterfaceの形で型付きに使えます。役割分担としては、COMの起動入口が*.comhost.dll、VBAが見る型情報が*.tlb、実装本体が*.dllです。
- 「ActiveX コンポーネントはオブジェクトを作成できません」と出る原因は何ですか?
- 典型的な原因はOffice/VBAとCOMサーバーのbitness不一致です。64bit Officeならx64/win-x64でビルドしてSystem32のregsvr32で登録し、32bit Office(64bit Windows上)ならx86/win-x86でビルドしてSysWOW64のregsvr32で登録、TLB生成もdscom32.exeを使います。.NET 5+のCOM hostではAnyCPUのままにすると*.comhost.dllが64bit側に寄りやすく、32bit Officeと噛み合わないことがあるため、Officeに合わせてx86/x64を明示するのが安全です。
- ClassInterfaceType.AutoDualを使ってはいけないのですか?
- 一見ラクですが、公開後にメンバー順や構成を触ると壊しやすいため避けるべきです。VBAから型付きで安定して使いたいなら、明示的なインターフェイスを定義してクラスはClassInterfaceType.Noneにし、VBAから使うインターフェイスはInterfaceIsDualにするのが定石です。DispIdを振っておくとメソッド順変更時の事故を減らせます。またCOMではGUIDが契約そのものなので、IIDやCLSIDを公開後に軽率に再生成すると既存のVBA参照や登録が壊れます。
- 配布するときはDLL単体を渡せばよいですか?
- DLL単体では動きません。実装本体の*.dll、*.comhost.dll、*.deps.json、*.runtimeconfig.json、*.tlb、必要なら依存DLL一式をまとめて配置します。さらにクライアントPCには対応する.NET 8ランタイムが必要で、COM hostはself-contained配布ではなく基本的にframework-dependentな運用になります。配置場所をあとで変えるなら登録もやり直しになる点にも注意が必要です。
著者プロフィール
記事の著者プロフィールページです。
小村 豪
合同会社小村ソフト 代表
Windows ソフト開発、技術相談、不具合調査を中心に、既存資産が残る案件や原因が見えにくい障害調査に強みがあります。
公開リンク