前回のエントリに書いたように、.NET の SpeechSynthesizer クラスから新唄詠を使おうとすると使えないので、その原因を探っていく。引き続き、massao さんの支援をいただいている。

そもそも、使えないというのはどういうことかというと、クライアントアプリ(テキストスピーチソフト)が SpeechSynthesizer.SelectVoice を唄詠音源指定で呼びだした際に、
ArgumentException:音声を設定できません。一致する音声がインストールされていないか、または音声が無効になっています。
という例外が発生する。

クライアントが SelectVoice を呼ぶと、恐らく別スレッドが起動し、その中で、COM サーバーである唄詠が生成される。生成後、本来であれば SetObjectToken が呼ばれるのだが、これが呼ばれない。今回はその状況をメモとして整理する。

そもそも、SelectVoice された際の新唄詠の動きを、COM サーバーと言って良いのかは不明だ。

現行(Ver 6.xx 系列)の唄詠は C++ で開発しているので、おそらく普通に COM サーバーとして振る舞っている。新唄詠も、SAPI として使われている場合(動作する 3 種類のテキストスピーチソフトから使われる場合)は、QueryInterface に応答しているので、COM サーバーとして振る舞っていると思われる。

しかし、新唄詠が SelectVoice された際は、コンストラクタが呼ばれた後に QueryInterface が呼ばれない。新唄詠も SelectVoice も両方 .NET Framework なので、COM の手順ではなく、名前と型による制御になっているのかもしれない。

とりあえず、現時点でできることは、コンストラクタが呼ばれる際のコールスタックを追うくらいなので、それを整理する。何故か、うちの環境ではコンストラクタが 4 回呼ばれる。登録されている唄詠音源が多いほどコンストラクタも多く呼ばれるが、登録されている唄詠音源は現在 3 つなので、ぴったり数が一致するわけでもないのが謎だ。

ともあれ、コンストラクタが呼ばれたときのコールスタックは以下。

TestUtaYomiEngine.dll!TestUtaYomiEngine.TestUtaYomiTTSEngineObject.TestUtaYomiTTSEngineObject() 行 160    C#
[ネイティブからマネージへの移行]   
mscorlib.dll!System.RuntimeType.CreateInstanceSlow(bool publicOnly, bool skipCheckThis, bool fillCache, ref System.Threading.StackCrawlMark stackMark) 行 5568    C#
mscorlib.dll!System.Activator.CreateInstance(System.Type type, bool nonPublic) 行 206    C#
mscorlib.dll!System.Activator.CreateInstance(System.Type type) 行 147    C#
System.Speech.dll!System.Speech.Internal.ObjectTokens.ObjectToken.CreateObjectFromToken<object>(string name)    不明
System.Speech.dll!System.Speech.Internal.Synthesis.VoiceSynthesis.GetComEngine(System.Speech.Synthesis.VoiceInfo voiceInfo)    不明
System.Speech.dll!System.Speech.Internal.Synthesis.VoiceSynthesis.GetProxyEngine(System.Speech.Synthesis.VoiceInfo voiceInfo)    不明
System.Speech.dll!System.Speech.Internal.Synthesis.VoiceSynthesis.ThreadProc()    不明
mscorlib.dll!System.Threading.ThreadHelper.ThreadStart_Context(object state) 行 74    C#
mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) 行 581    C#
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) 行 531    C#
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) 行 520    C#
mscorlib.dll!System.Threading.ThreadHelper.ThreadStart() 行 111    C#


それぞれの関数が返ってきた直後の EAX を見て、関数が成功しているかどうかを判定してみると(0 以外なら成功していると判定)、

CreateInstanceSlow→成功(0x0252153C)
CreateInstance(Type, bool)→成功?(0252153C のまま)
CreateInstance(Type)→成功?(0252153C のまま)
CreateObjectFromToken→成功?(0252153C のまま)
GetComEngine→失敗
GetProxyEngine→失敗

CreateObjectFromToken は成功しているとはいえ、「instanceValue as ISpObjectWithToken」のキャストに失敗しているフシがある。CreateObjectFromToken が想定している ISpObjectWithToken と、新唄詠が実装している ISpObjectWithToken は、名前が同じでも、型が違うと見なされているのではないか。

ICustomTypeProvider で型の偽装をしようかとも思ったが、実装が悪かったのか、キャストの際に ICustomTypeProvider.GetCustomType() が呼ばれなかった。