.NETCoreで静的にCOMを使う

# TL;DR;

Windows10組み込みの文章読み上げエンジンをCOM経由で操作してみます。

```xml:StaticComTest.csproj
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <COMReference Include="SpeechLib.dll"> <!-- 👈これでCOMInteropをリンク -->
      <Guid>c866ca3a-32f7-11d2-9602-00c04f8ee628</Guid>
      <VersionMajor>5</VersionMajor>
      <VersionMinor>4</VersionMinor>
    </COMReference>
  </ItemGroup>
</Project>
```

```cs:Program.cs
class Program
{
    // COMのクラスが使える
    static void Main(string[] args) => new SpeechLib.SpVoice().Speak("Hello world");
}
```

```sh:console
# VSについてくるやつ（dotnet msbuildでは駄目）
msbuild /t:restore
msbuild /t:rebuild

StaticComTest.exe 
# 🔊 はろぅわぁるど（ねっとり片言ボイス）
```

# COMの呼び出し

私自身、まだCOMへの理解が甘いのですが、何となくこんな感じ…というのをメモします。

.NETにおけるCOMの静的リンク（VSでいう参照の追加>COM）というのは、実際にはCOMをリンクしているわけではなく、**COMIntropのDLL**をリンクすることを指しています。そもそも、COMというのは原則GUIDをKeyとしてOS登録して、使うときに`CoInitialize`でOSにCOMオブジェクトをCOMサーバーにリクエストするインターフェイスです。（Registration-Free COMというのもあるらしい）COMIntropはCOMをラッピングした.NETのDLLでありCOMではありません。実際にDirectX12のようなIntropが存在しないCOMは、VSの参照の追加ダイアログの中にもありません。

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

1. コンパイル時に、ユーザーコードに`Introp.Speech.dll`（COMIntropのDLL）をリンク
2. `new SpVoice()`でSpVoiceインターフェイスを取得。（インターフェイスだが、`CoClass`属性付きなのでnewできる）
3. SpVoice（の基底のISpeechVoice）の`Speak`メソッドを呼び出し
4. SpVoiceの実体となるSpVoiceClassの`ISpeechVoice.Speak`メソッドが呼び出される
5. `ISpeechVoice.Speak`から、`SpVoice.Speak`が呼び出される。
6. `SpVoice.Speak`はexternメソッドでCOMのメソッドのコール（DispID:12）に置き換えられる。

一度、SpVoiceインターフェイスを経由するのは、SpVoiceClassの実装を隠蔽するためです。SpVoiceClassはCOMと直接つながっているダーティな存在のため、COMの仕様が変わった場合、ユーザーCOMIntropライブラリの利用側に変更が必要にならないようにインターフェイスをかませています。まぁ、アクセス修飾子と名前衝突をかわしてexternメソッドを外から呼べないようにすれば隠蔽できますが…なんだかそういうルールらしいです。

[COM 呼び出し可能ラッパー クラス インターフェイスの概要 (MSDN)](https://docs.microsoft.com/ja-jp/dotnet/standard/native-interop/com-callable-wrapper#introducing-the-class-interface)

# .NETCoreのコンパイラではコンパイルできない

COMを静的にリンクしたとしても、.NETCoreをターゲットにコンパイルすることはできます。そのため、CsProjには.NETCore3.1を指定しています。
しかし、COMReferenceは通常のMSBuildのみの機能のため、.NET Coreの`dotnet msbuild`では利用できません。

```console:console
$ dotnet build

C:\Program Files\dotnet\sdk\3.1.101\Microsoft.Common.CurrentVersion.targets(2726,5) : error : MSB4803:
タスク "ResolveComReference" は .NET Core バージョンの MSBuild ではサポートされていません。
.NET Framework バージョンの MSBuild をご使用ください。
詳細については、https://aka.ms/msbuild/MSB4803 をご覧ください。
```

ちなみに、`dotnet msbuild`は`msbuild.exe`と同じ機能を提供するものではなく、`msbuild.exe`と同じオプションで.NETCoreのコンパイラを使うものらしいです。

以下の一文を読むと、互換なコンパイラに切り替える（内蔵の`msbuild.exe`相当のコンパイラに切り替えると思っていた）ように聞こえますが…

> The command has the exact same capabilities as the existing MSBuild command-line client for SDK-style projects only.
> https://docs.microsoft.com/ja-jp/dotnet/core/tools/dotnet-msbuild#description

そこで、VisualStudioのコンパイラであるところの`msbuild.exe`を利用します。
（VisualStudioでコンパイルすればいいのですが…VSCodeとCLIでやりたい）

# 参考

* [COM への .NET Core コンポーネントの公開 (MSDN)](https://docs.microsoft.com/ja-jp/dotnet/core/native-interop/expose-components-to-com)
* [dotnet/samples COM Server Demo(Github)](https://github.com/dotnet/samples/tree/master/core/extensions/COMServerDemo)
* [dotnet msbuild (MSDN)](https://docs.microsoft.com/ja-jp/dotnet/core/tools/dotnet-msbuild#description)
* [MSBuild プロジェクトの共通項目 (MSDN)](https://docs.microsoft.com/ja-jp/visualstudio/msbuild/common-msbuild-project-items?view=vs-2019)
* [COM Interop (Wikipedia)](https://en.wikipedia.org/wiki/COM_Interop)
* [COM Interop in .NET (MSDN)](https://docs.microsoft.com/en-us/dotnet/standard/native-interop/cominterop)
* [COM Interop (Visual Basic) (MSDN)](https://docs.microsoft.com/en-us/dotnet/visual-basic/programming-guide/com-interop/)
* [COM 呼び出し可能ラッパー クラス インターフェイスの概要 (MSDN)](https://docs.microsoft.com/ja-jp/dotnet/standard/native-interop/com-callable-wrapper#introducing-the-class-interface)
* [COMの基礎 (HSPからのCOM操作)](http://chokuto.ifdef.jp/urawaza/com/com.html)
* [Registration-Free COM Interop (MSDN)](https://docs.microsoft.com/en-us/dotnet/framework/interop/registration-free-com-interop)
* [COM クラスの例 (C# プログラミング ガイド) (MSDN)](https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/interop/example-com-class)

# PS

「COMはWindowsの機能である」旨の誤りと記述ミスをご指摘いただきました、@Midoliy さん、編集リクエストありがとうございました。

COMはWindows固有の機能と位置付けられたものではありません。プロトコルであり、それを実装して利用できる状態で提供されているのが主にWindwosとなります。