【完全版】.NET 6/8のCOM登録エラー(regasm RA0000)解決とC++からC# DLLを呼び出す実装ガイド

「regasm RA0000」「System.Runtime, Version=6.0.0.0 が見つからない」——.NET 6/8 で C# 製 DLL を COM 登録しようとしてつまずく典型例です。本記事は、.NET 5 以降の正しい登録手順(comhost + regsvr32)と C++ からの呼び出しまでを、エラー対処・ビルド順・配布の実務ノウハウ込みで徹底解説します。MetaTrader 5 を含む純 C++ 環境でも安全・高速に動かせる実装を、コピペ可能なコードと表でまとめました。

目次

.NET 6/8 環境での COM 登録エラー(regasm RA0000)の正体と対処

結論から言うと、.NET 5 以降では regasm.exe は使えません。これは .NET Framework 時代の COM 登録方式であり、System.Runtime, Version=6.0.0.0 が見つからない などの RA0000 エラーは、.NET 6/8 のアセンブリに対して regasm を誤用したことが原因です。代替として Microsoft が公式に用意したルートが「comhost(COM ホスティング)」です。C# のプロジェクトに EnableComHosting を設定してビルドすると <プロジェクト名>.comhost.dll が生成され、これを regsvr32 で登録します。

  1. プロジェクトに comhost を有効化
    *.csproj に <EnableComHosting>true</EnableComHosting> を追加。
  2. ビルドで comhost DLL が生成
    例: NetServerTest.comhost.dll。
  3. 管理者権限で登録
    regsvr32 /s <出力パス>\NetServerTest.comhost.dll。
  4. タイプライブラリを埋め込み・登録
    IDL→MIDL で作成した *.tlb を comhost に埋め込み、[ComRegisterFunction]/[ComUnregisterFunction] 経由で登録・解除。
  5. LTS の .NET 8 を推奨
    .NET 6 はサポートが切れているため、安定運用・最新の COM 改善を得る目的で .NET 8 に更新を推奨。

プロジェクト設定(C# / .csproj)

最小構成の *.csproj サンプルです。COM 公開に必要なメタ情報と comhost、タイプライブラリの埋め込みをまとめています。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <EnableComHosting>true</EnableComHosting>
    <AssemblyTitle>NetServerTest</AssemblyTitle>
    <AssemblyVersion>1.0.0.0</AssemblyVersion>
    <FileVersion>1.0.0.0</FileVersion>
    <NeutralLanguage>en-US</NeutralLanguage>
    <Platforms>AnyCPU</Platforms>  <!-- x64 固定なら "x64" -->
  </PropertyGroup>





 

型の公開には属性が必要です。ComVisible、Guid、InterfaceType(InterfaceIsDual)、クラス側は ClassInterface(ClassInterfaceType.None) を明示します(早期バインディング向け)。

using System;
using System.Runtime.InteropServices;

namespace NetServerTest
{
    [ComVisible(true)]
    [Guid("00000000-0000-0000-0000-000000000101")] // 置き換え
    [InterfaceType(ComInterfaceType.InterfaceIsDual)]
    public interface INetServer
    {
        int Add(int a, int b);

[return: MarshalAs(UnmanagedType.BStr)]

string Echo([MarshalAs(UnmanagedType.BStr)] string s); long Sum64(long x, long y); } [ComVisible(true)] [Guid(“00000000-0000-0000-0000-000000000102”)] // 置き換え [ClassInterface(ClassInterfaceType.None)] public sealed class NetServer : INetServer { public int Add(int a, int b) =&gt; a + b; public string Echo(string s) =&gt; s; public long Sum64(long x, long y) =&gt; x + y; } }

IDL → MIDL(タイプライブラリ)

IDL を定義し、MIDL で *.tlb を生成します。C++ クライアントはこれを #import して早期バインディング(dual)で呼び出します。

// NetServerTypes.idl
importlib("stdole2.tlb");

[
uuid(00000000-0000-0000-0000-000000000201), // library の GUID
version(1.0)
]
library NetServerTypes
{
[
object,
uuid(00000000-0000-0000-0000-000000000101), // INetServer の GUID (C# と一致)
oleautomation,
dual
]
interface INetServer : IDispatch
{
[id(1)] HRESULT Add([in] LONG a, [in] LONG b, [out, retval] LONG* result);
[id(2)] HRESULT Echo([in] BSTR s, [out, retval] BSTR* result);
[id(3)] HRESULT Sum64([in] LONGLONG x, [in] LONGLONG y, [out, retval] LONGLONG* result);
};

```
[
  uuid(00000000-0000-0000-0000-000000000102) // coclass の GUID (C# クラスと一致)
]
coclass NetServer
{
    [default] interface INetServer;
};
```

}; 

タイプライブラリ登録(RegisterFunction / UnRegisterFunction)

comhost の DllRegisterServer / DllUnregisterServer 実行時に呼ばれるフックを用意します。ComHostTypeLibrary で comhost DLL に埋め込んだ TYPELIB リソースを LoadTypeLibEx(登録モード)で読み込み、登録/解除します。DLL 名は「<アセンブリ名>.comhost.dll」です。

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Reflection;

internal static class TypeLibRegistration
{
    private enum REGKIND
    {
        REGKIND_DEFAULT = 0,
        REGKIND_REGISTER = 1,
        REGKIND_NONE = 2,
        REGKIND_LOAD_TLB_AS_32BIT = 0x20,
        REGKIND_LOAD_TLB_AS_64BIT = 0x40
    }

    [DllImport("oleaut32.dll", CharSet = CharSet.Unicode)]
    private static extern int LoadTypeLibEx(string szFile, REGKIND regkind, out IntPtr pptlib);

    [DllImport("oleaut32.dll")]
    private static extern int UnRegisterTypeLib(ref Guid libID, ushort wVerMajor, ushort wVerMinor, int lcid, int syskind);

    // IDL の library GUID + version と一致させる
    private static readonly Guid LIBID_NetServerTypes = new("00000000-0000-0000-0000-000000000201");
    private const ushort LIB_VERSION_MAJOR = 1;
    private const ushort LIB_VERSION_MINOR = 0;

    [ComRegisterFunction]
    public static void RegisterFunction(Type _)
    {
        string asmPath = Assembly.GetExecutingAssembly().Location;
        string dir = Path.GetDirectoryName(asmPath)!;
        string name = Path.GetFileNameWithoutExtension(asmPath);
        string comhost = Path.Combine(dir, name + ".comhost.dll");

        // TYPELIB を埋め込んだ comhost DLL を直接指定して登録
        int hr = LoadTypeLibEx(comhost, REGKIND.REGKIND_REGISTER, out _);
        if (hr != 0) Marshal.ThrowExceptionForHR(hr);
    }

    [ComUnregisterFunction]
    public static void UnRegisterFunction(Type _)
    {
        // SYSKIND: 0=Win16, 1=Win32, 2=Mac, 3=Win64
        const int SYS_WIN64 = 3;
        const int SYS_WIN32 = 1;

        bool is64 = IntPtr.Size == 8;
        int syskind = is64 ? SYS_WIN64 : SYS_WIN32;

        int hr = UnRegisterTypeLib(ref LIBID_NetServerTypes, LIB_VERSION_MAJOR, LIB_VERSION_MINOR, 0, syskind);
        if (hr != 0) Marshal.ThrowExceptionForHR(hr);
    }
}

ポイント:

  • 登録処理は comhost DLL のパス を指定します(TLB は comhost に埋め込まれるため)。
  • UnRegisterTypeLib は library の GUID とバージョン が必要です。IDL と同じ値を使います。
  • ユーザー単位登録が必要なら RegisterTypeLibForUser を使う選択肢もあります(企業 PC の権限制限で有用)。

regsvr32 で登録・解除(管理者で実行)

regsvr32 /s &quot;$(TargetDir)$(TargetName).comhost.dll&quot;   &lt;!-- 登録 --&gt;
regsvr32 /u /s &quot;$(TargetDir)$(TargetName).comhost.dll&quot; &lt;!-- 解除 --&gt;

64bit / 32bit の混在に注意してください。x64 で 32bit COM を登録する場合は %SystemRoot%\SysWOW64\regsvr32.exe、64bit COM は %SystemRoot%\System32\regsvr32.exe を用います。

サンプル ソリューション構成とビルド順

3 プロジェクトを依存関係でつなげ、ビルド順を自動化します(Visual Studio を管理者として実行)。

手順プロジェクト役割依存関係設定
1NetServerTypesIDL → MIDL で *.tlb 作成―
2NetServerTestC# COM サーバ(TLB 埋め込み)。Post‑build で regsvr32NetServerTypes に依存
3CppClient#import で *.tlb を読み込み DLL 呼び出しNetServerTest に依存

Post‑build イベント例(C# 側):

if exist &quot;$(TargetDir)$(TargetName).comhost.dll&quot; (
  &quot;%SystemRoot%\System32\regsvr32.exe&quot; /s &quot;$(TargetDir)$(TargetName).comhost.dll&quot;
) else (
  echo comhost.dll not found &amp; exit /b 1
)

regsvr32 のエラー: code 5 / code 3 の切り分け

エラー原因対処チェック
code 5権限不足(UAC / 書き込み不可)Visual Studio を管理者で起動/管理者 PowerShell で実行。
セキュリティ製品のブロック有無。
HKCR/CLSID・TypeLib へのアクセス権。
code 3パス不正・ビルド成果物未生成Post‑build のパスに空白がある場合は引用符で囲む。
x64/Win32 の構成フォルダ違い。
$(TargetName).comhost.dll の実在確認。

プロジェクト名・GUID 変更後の「there is no typelib registered for LIBID」

発生理由は「名前/GUID/ファイル名の不一致」と「古い生成物の残骸」です。次の順で解決します。

  1. comhost DLL 名はプロジェクト名依存:Post‑build の regsvr32 パスを新しい <Name>.comhost.dll に更新。
  2. IDL の library / coclass / uuid を更新:C# の [Guid] と一致させる。
  3. C++ の #import:libid(...) 指定やファイル名を新しいものへ。
  4. 古い生成物を全削除:x64\Debug 等に残る *.tlh/*.tli/*.tlb を削除 → Clean → Rebuild。

メソッド追加が *.tli / *.tlb に反映されない

  • 本デモは dual インターフェース(早期バインディング)。C++ の生成ヘッダーは vtable 参照のため DISPIDs は無関係。
  • 新メソッドを追加したら IDL のメソッド順 も一致させる。
  • 古い *.tli が残ると過去の情報を参照。必ず削除してから Rebuild。

C++ から C# COM サーバを呼び出す(早期バインディング)

C++ コンソール アプリの最小例です。#import で TLB を取り込み、CoCreateInstance で生成します。

// CppClient.cpp
#include <windows.h>
#include <combaseapi.h>
#import "..\\NetServerTypes\\x64\\Debug\\NetServerTypes.tlb" no_namespace, named_guids, raw_interfaces_only

int wmain()
{
HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
if (FAILED(hr)) return 1;

```
INetServer* p = nullptr;
hr = CoCreateInstance(CLSID_NetServer, nullptr, CLSCTX_INPROC_SERVER, IID_INetServer, (void**)&p);
if (SUCCEEDED(hr))
{
    long sum = 0;
    hr = p-&gt;Add(10, 32, &amp;sum);
    if (SUCCEEDED(hr)) wprintf(L&quot;Add = %ld\n&quot;, sum);

    BSTR echo = nullptr;
    hr = p-&gt;Echo(SysAllocString(L&quot;hello&quot;), &amp;echo);
    if (SUCCEEDED(hr)) { wprintf(L&quot;Echo = %s\n&quot;, echo); SysFreeString(echo); }

    LONGLONG s64 = 0;
    hr = p-&gt;Sum64(10000000000LL, 2LL, &amp;s64);
    if (SUCCEEDED(hr)) wprintf(L&quot;Sum64 = %lld\n&quot;, s64);

    p-&gt;Release();
}

CoUninitialize();
return FAILED(hr);
```

} 

補足:

  • STA/MTA は要件に応じて選択(UI スレッドは STA が無難)。
  • アプリのビット数と COM サーバのビット数は一致させます(x64 ↔ x64)。
  • 遅延バインディング(IDispatch)を使いたい場合は raw_interfaces_only を外し、_com_ptr_t ヘルパーを併用します。

デバッガ設定:C# のブレークポイントが効かない場合

  1. C# プロジェクト:ネイティブコードのデバッグは不要(Managed のみ)。
  2. C++ プロジェクト → プロパティ → デバッガーの種類 → Mixed (.NET Core) を選択。
  3. 実行時に Modules ウィンドウで DLL/PDB が読み込まれているか確認。パス不一致なら PDB はロードされません。

C++ ⇔ C# のデータ型マッピング表

C# 型IDL/COM 型C++ 側推奨型注意点
stringBSTR_bstr_t / BSTR自動解放ヘルパー(_bstr_t)が安全
boolVARIANT_BOOLVARIANT_BOOLTRUE=-1, FALSE=0
intLONGlongx86/x64 とも 32bit
long(64bit)LONGLONGlong long64bit 整数
配列SAFEARRAY 等ATL ヘルパ等P/Invoke と挙動差に注意

配布に必要なファイルと推奨構成

&lt;プロジェクト名&gt;.dll          ; IL 本体(C#)
&lt;プロジェクト名&gt;.comhost.dll  ; COM エントリポイント(登録対象)
&lt;埋め込み済み&gt;.tlb           ; 配布先で独立登録したい場合のみ別途同梱可
CppClient.exe                 ; C++ クライアント
(自己完結型 publish でない場合は .NET 8 ランタイムが必要)
  • リリース配布では PDB は通常不要。サーバ側トラブル解析があるならシンボルをセキュアに保管。
  • MSI 等のインストーラを使う場合、SelfReg は推奨されません。インストール時に regsvr32 を明示呼び出しするカスタムアクションに統一すると再現性が上がります。

全体手順(ショートリファレンス)

  1. IDL を書く → midl で *.tlb を生成。
  2. C# プロジェクト:<EnableComHosting>true</EnableComHosting>、<ComHostTypeLibrary> で TLB を埋め込み。インターフェース/クラスに ComVisible、Guid、InterfaceIsDual、ClassInterface(None)。
  3. RegisterFunction/UnRegisterFunction を実装(TLB の登録/解除)。
  4. Post‑build で regsvr32 を実行(x64/Win32 の整合性に注意)。
  5. C++ プロジェクト:#import で TLB を取り込み、CoCreateInstance → メソッド呼び出し。
  6. Visual Studio を管理者で起動 → Build → Build Solution。
  7. クライアントから C# メソッド呼び出し成功を確認 → デバッグ/配布テストへ。

よくある落とし穴と回避策(チェックリスト)

  • regasm を使用している:.NET 6/8 では 非対応。comhost + regsvr32 に切り替える。
  • GUID の重複・不一致:IDL/C#/C++ の libid/coclass/interface を同じ値に。
  • 古い生成物:*.tli/*.tlh/*.tlb を削除して Clean → Rebuild。
  • 32/64bit の取り違え:レジストリ、regsvr32、ビルド構成を統一。
  • 管理者権限不足:VS/ターミナルを管理者で。企業端末はユーザー単位登録の選択肢を検討。
  • DLL/PDB の場所相違:モジュールウィンドウでロードパスを確認。Copy Local / OutputPath を見直し。

トラブル詳細:RA0000(System.Runtime が見つからない)

これは「.NET Framework 用の regasm で .NET 6/8 のアセンブリを登録しようとした」ことを示す汎用エラーです。根治策は次のとおりです。

  1. EnableComHosting=true を設定してビルド。
  2. 生成された .comhost.dll を regsvr32 で登録。
  3. (必要に応じ)[ComRegisterFunction] で TLB を登録。
  4. .NET 8(LTS)へ更新。

運用ベストプラクティス

  • ソース管理:IDL の GUID とバージョンは履歴を明示。破壊的変更は version(2.0) 等に上げ、複数バージョン共存を考慮。
  • CI/CD:ビルド構成ごとに TLB パスを切り替える。登録は管理者権限のビルドエージェントか、ユーザー単位登録に分岐。
  • 障害切り分け:regsvr32 /n /i の使い方、ProcMon でのアクセス拒否確認、fuslogvw(.NET ロードログ)は自己完結型でない場合に有効。
  • セキュリティ:COM 露出範囲を最小化。不要なメソッドや可視性を避け、ComVisible(false) をデフォルトにして必要箇所のみ true。

MetaTrader 5 など純 C++ 環境での活用メモ

MetaTrader 5(MQL5)からは C++ 経由で DLL をロードする構成が一般的です。本記事の方式なら、C++ のアダプタ DLL を挟んで comhost ベースの C# ロジックへ橋渡しできます。計算・検証ロジックを C# 側で安全に保守し、C++ 側は薄いラッパに徹することで、更新容易性と実行速度のバランスを取れます。

FAQ(補足)

Q. .NET 側で ProgId は必要? A. 既存スクリプト資産が ProgId を期待するなら [ProgId("Vendor.Product.NetServer")] を付与。CLSID 直接指定で良いなら必須ではありません。 Q. 自己完結型(self-contained)publish にしても良い? A. 可能です。ただし配布サイズと更新運用のトレードオフを検討。ランタイム前提配布でも comhost は問題なく動作します。 Q. x86 と x64 を両対応にしたい A. プロジェクトをそれぞれのプラットフォームでビルドし、レジストリ衝突を避けるため TLB のバージョン分けや ProgId を調整します。

まとめ(実務チートシート)

  • regasm は使わない(.NET 5+ 非対応)。
  • EnableComHosting を true、ビルドで .comhost.dll が得られる。
  • regsvr32 で comhost を登録(管理者)。
  • IDL → MIDL → TLB を comhost に埋め込み、RegisterFunction で登録。
  • ビルド順:IDL → C# → C++(依存関係を設定)。
  • libid エラー:名前・GUID・生成物の不一致&残骸が原因。Clean → Rebuild。
  • デバッグ:C++ のデバッガー種類を Mixed (.NET Core)。Modules で DLL/PDB を確認。
  • 配布:.dll / .comhost.dll / (必要なら).tlb。通常 PDB は除外。
  • .NET 8 (LTS) 推奨:長期運用と品質のため。

この流れを守れば、MetaTrader 5 を含む純 C++ 環境でも .NET 8 製 DLL を COM 経由で安全かつ高速に活用できます。既存の C++ 資産に負荷をかけず、C# の生産性をそのまま組み込めるのが comhost 方式の強みです。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次