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?

【C#】IDynamicInterfaceCastable による動的キャスト

4
Last updated at Posted at 2026-02-02

はじめに

Definition
Interface used to participate in a type cast failure.

public interface IDynamicInterfaceCastable

Derived 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 オブジェクト)に対して isas、キャストをインラインで行うためのものです。ドキュメントはこれを抽象化して「型キャストの失敗処理を行うためのインターフェース」と定義しています。とはいっても、普通に 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 用に ppvComObject マネージド型でラップします。すると、IDynamicInterfaceCastable を実装する ComObject 内部で QueryInterface の呼び出しを行ってくれるようになります。

PInvoke.SHCreateItemFromParsingName("C:\\Users\me\Desktop", null, IID_IshellItem, out var shellItemObj);
var shellItem = (IShellItem)shellItemObj;
var shellItem2 = (IShellItem2)shellItem;

こうしたキャストに限らず、isas でも効果を発揮します。

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# ユーザーが使うものではなさそうです。

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?