1
0

WinRT COM Interface から Win32 COM Interface へキャストする

Last updated at Posted at 2024-09-13

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 インターフェースである IMemoryBufferReferenceIMemoryBufferByteAccess にキャストしているが、後者は 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 で正常に動作している。

1
0
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
1
0