WinRT COM から Win32 COM へキャストできなくなった
WinRT の AudioFrameInputNode.QuantumStarted
イベントハンドラ内で AudioFrame
インスタンスを生成する際、WinRT には存在しない COM インターフェースを使用する必要がある。
Microsoft の AudioGraph サンプル では、次のようにコーディングされている。
uint bufferSize = samples * sizeof(float);
AudioFrame frame = new Windows.Media.AudioFrame(bufferSize);
using (AudioBuffer buffer = frame.LockBuffer(AudioBufferAccessMode.Write))
using (IMemoryBufferReference reference = buffer.CreateReference())
{
byte* dataInBytes;
uint capacityInBytes;
float* dataInFloat;
// Get the buffer from the AudioFrame
((IMemoryBufferByteAccess)reference).GetBuffer(out dataInBytes, out capacityInBytes);
//... 以下略 ...
WinRT COM インターフェースである IMemoryBufferReference
を IMemoryBufferByteAccess
にキャストしているが、後者は WinRT には存在していない。
そのため、同サンプルでは以下の様に自前で定義している。(便宜上、これを Win32 COM と呼ぶ。)
[ComImport]
[Guid("5B0D3235-4DBA-4D44-865E-8F1D0E4FD04D")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
unsafe interface IMemoryBufferByteAccess
{
void GetBuffer(out byte* buffer, out uint capacity);
}
.NET 5 以降では動かない
この AudioGraph サンプルは、組み込み WinRT や組み込み RCW/CCW 関連の破壊的変更が行なわれた .NET 5 以降では動作しない。
ビルドはできるが、実行時にキャスト部分で InvalidCast 例外が発生する。キャスト先が WinRT ではないので、.As<>()
メソッドでも同様に例外が発生する。
対策
以下の2つの方法で何とかなった。
(1) IMemoryBufferByteAccess
を、ComImport
属性ではなく GeneratedComInterface
属性(コードジェネレーター)で定義する。
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
[GeneratedComInterface]
[Guid("5B0D3235-4DBA-4D44-865E-8F1D0E4FD04D")]
public unsafe partial interface IMemoryBufferByteAccess
{
[PreserveSig]
public int GetBuffer( out byte* buffer, out uint capacity );
}
(2) WinRT COM オブジェクトへのポインタを取得し、ComWrappers
経由で Win32 COM インターフェースを取得する。
_ComWrappers = new StrategyBasedComWrappers();
// ... 中略 ...
using( AudioBuffer audioBuffer = audioFrame.LockBuffer( AudioBufferAccessMode.Write ) )
using( IMemoryBufferReference reference = audioBuffer.CreateReference() )
{
// WinRT COM オブジェクトへのネイティブポインタを取得する。
nint referencePtr = ( (WinRT.IWinRTObject) reference ).NativeObject.ThisPtr;
// ComWrappers 経由で Win32 COM インターフェースを取得する。
var memoryAccess = (IMemoryBufferByteAccess) _ComWrappers.GetOrCreateObjectForComInstance( referencePtr, CreateObjectFlags.None );
memoryAccess.GetBuffer( out byte* dataInBytes, out uint capacityInBytes );
//... 以下略 ...
あとはだいたいサンプルどおり。
現状、net8.0-windows10.0.18362.0
で正常に動作している。