はじめに
Definition
Interface used to participate in a type cast failure.public interface IDynamicInterfaceCastableDerived System.Runtime.InteropServices.Marshalling.ComObject
Remarks
Implementation of this interface on a value type will be ignored. Only non-value types are allowed to participate in a type cast failure through this interface.
これは一体何なのでしょうか。一言でいうと、C# コード上で C# オブジェクトとは違い、静的な型情報を持たないオブジェクト(例えば COM オブジェクト)に対して is や as、キャストをインラインで行うためのものです。ドキュメントはこれを抽象化して「型キャストの失敗処理を行うためのインターフェース」と定義しています。とはいっても、普通に C# コードを書いていて意識するものではありませんし、私の知る限り 2 か所でしか使われておらず、ほぼ Com Wrappers のために作られたものです。
シナリオ1:COM Wrappers
存在目的は、端的に言うと上記のようになります。COM インスタンスは実行時に型情報を持ちません。COM インターフェースのインスタンスに対するキャストは必ず、以下のように QueryInterface という関数を通して行います。
IShellItem* psi;
hr = SHCreateItemFromParsingName(L"C:\\Users\me\Desktop", NULL, IID_IShellItem, &psi);
IShellItem2* psi2;
psi->QueryInterface(IID_IShellItem2, &psi2);
C++ には static_cast がありますが、COM では仕様上こうなっています(とは言っても、実際 QueryInterface は COM オブジェクト側が実装するもので、内部で static_cast を使います)。
これを C# でわざわざ書いてもいいのですが、C# には素晴らしいコンパイラが存在するので、もう少し賢く書きたいです。そのために IDynamicInterfaceCastable が存在します。IShellItem COM インスタンスを作成するために CsWin32 を使って SHCreateItemFromParsingName を呼び出しましょう。定義は以下の通りです。
[LibraryImport("shell32.dll")]
public static unsafe partial HRESULT SHCreateItemFromParsingName(
PCWSTR pszPath,
[Optional] IBindCtx pbc,
Guid* riid,
[MarshalAs(UnmanagedType.Interface)] out object ppv);
[MarshalAs(UnmanagedType.Interface)] により、LibraryImport は COM Wrappers 用に ppv を ComObject マネージド型でラップします。すると、IDynamicInterfaceCastable を実装する ComObject 内部で QueryInterface の呼び出しを行ってくれるようになります。
PInvoke.SHCreateItemFromParsingName("C:\\Users\me\Desktop", null, IID_IshellItem, out var shellItemObj);
var shellItem = (IShellItem)shellItemObj;
var shellItem2 = (IShellItem2)shellItem;
こうしたキャストに限らず、is や as でも効果を発揮します。
if (shellItemObj is IShellItem2 shellItem2)
{
// bla bla bla
}
シナリオ2:CsWinRT
二つ目の使用例は、WinRT コンポネントの C# へのプロジェクション(winmd からの C# 定義の生成)です。WinRT も COM オブジェクトですから、必要性は自明です。
WinUI において、例えば ContentControl インスタンスを FrameworkElement にキャストできるのはこれのおかげです(WinUI 自体は C++/WinRT で実装されています)。ちなみに WPF は実装自体 C# ですので実装せずに済みます。
var contentControl = new ContentControl;
if (contentControl is FrameworkElement element)
{
// bla bla bla
}
実装の仕方
これは C++ における COM 用のスマートポインタ ComPtr の C# における実装例です。これは本質的に Com Wrappers の ComObject における実装と同一です。
public sealed unsafe class ComPtr : IDynamicInterfaceCastable, ...
{
private readonly void* _ptr;
RuntimeTypeHandle IDynamicInterfaceCastable.GetInterfaceImplementation(RuntimeTypeHandle interfaceType)
{
var iid = Type.GetTypeFromHandle(handle).GUID;
void* pv;
int hr = ((IUnknown*)_ptr)->QueryInterface(&iid, &pv);
if (hr < 0) Marshal.ThrowExceptionForHR(hr);
return interfaceType;
}
bool IDynamicInterfaceCastable.IsInterfaceImplemented(RuntimeTypeHandle interfaceType, bool throwIfNotImplemented)
{
var iid = Type.GetTypeFromHandle(handle).GUID;
void* castedPtr;
int hr = ((IUnknown*)_ptr)->QueryInterface(&iid, &castedPtr);
return hr >= 0 && castedPtr is not null;
}
}
おわりに
見ての通り、Microsoft 側がやるような実装で、私たち一般 C# ユーザーが使うものではなさそうです。