4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

.NETCoreで静的にCOMを使う

Last updated at Posted at 2020-05-27

TL;DR;

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

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>
Program.cs
class Program
{
    // COMのクラスが使える
    static void Main(string[] args) => new SpeechLib.SpVoice().Speak("Hello world");
}
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)

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

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

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 msbuildmsbuild.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でやりたい)

参考

PS

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

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

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?