C# 如何获取 COM 接口的实例

Thi*_*heT 6 c# com interop com-interop

我做了很多谷歌搜索,试图找到获取 COM 接口实例的标准方法。

Microsoft 在其文章COM Interop 第 1 部分:客户端教程中提供了一个示例:

// Create an instance of a COM coclass:
FilgraphManager graphManager = new FilgraphManager();

// See if it supports the IMediaControl COM interface. 
// Note that this will throw a System.InvalidCastException if 
// the cast fails. This is equivalent to QueryInterface for 
// COM objects:
IMediaControl mc = (IMediaControl) graphManager;

// Now you call a method on a COM interface: 
mc.Run();
Run Code Online (Sandbox Code Playgroud)

然而,看起来好像他们正在实例化一个 COM 对象并将其转换为 COM 接口。

对于我感兴趣的接口,IDesktopWallpaper似乎没有可实例化的实现 COM 对象。

我在这里找到的一个示例定义了一些被实例化的类,然后将其转换为接口,就像 msdn 示例一样:

[ComImport, Guid("C2CF3110-460E-4fc1-B9D0-8A1C0C9CC4BD")]
internal class IDesktopWallpaper
{

}

[Guid("B92B56A9-8B55-4E14-9A89-0199BBB6F93B"), //B92B56A9-8B55-4E14-9A89-0199BBB6F93B
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface DesktopWallpaperInterface
{
    // declared members
}
Run Code Online (Sandbox Code Playgroud)

我不明白实例化的对象是什么。它看起来像一个任意对象,它有一个GuidAttribute似乎表明它是一个实际的 COM 对象。

在这里 System.Type找到了另一个例子,System.Runtime.InteropServices.Marshal实例化一个对象,然后将其转换为接口:

IntPtr ptrRet;
SHGetMalloc(out ptrRet);

System.Type mallocType = System.Type.GetType("IMalloc");
Object obj = Marshal.GetTypedObjectForIUnknown(ptrRet,mallocType);
IMalloc pMalloc = (IMalloc)obj;
Run Code Online (Sandbox Code Playgroud)

该方法似乎正在请求指向该接口的现有实例的指针。我在 Windows Shell 文档中找不到类似SHGetMallocfor的任何方法。IDesktopWallpaper

问题

那么,长话短说,获取 COM 接口实例的标准方法是什么?

如果没有一刀切的解决方案,那么可以使用哪些标准方法来获取 COM 接口的实例?每种方法在什么情况下最有用?

编辑

下载 Windows 10 SDK 并根据IDesktopWallpaper 接口文档的要求部分引用该 SDK 后,我发现您可以从中查找 MIDLShobjidl.h并将其GuidAttribute用于您的接口声明,然后从中查找 CLSIDShobjidl.idl并在中使用它结合 和Type.GetTypeFromCLSID(Guid)Activator.CreateInstance(Type)获取实现 的对象的实例IDesktopWallpaper

我现在还看到,CLSID 是上面列出的第二种方法中用于看似GuidAttribute任意对象的内容。看起来这个方法允许您通过实例化类然后将实例转换为 COM 接口来模拟对象的托管实例化。

不过, 我仍然有兴趣知道这是否是最好的方法,以及此方法与其他方法相比有何优缺点。

Mit*_*tch 6

您可以通过多种方法获取指向 COM 对象引用的指针:

  • P/调用CoCreateInstance
  • P/调用CLSIDFromProgIDCoCreateInstance
  • P/调用IRunningObjectTable.GetObject
  • Type.GetTypeFromCLSIDActivator.CreateInstance
  • Type.GetTypeFromProgIDActivator.CreateInstance
  • new SomeType()其中SomeType标有ComImport

Activator.CreateInstancenew SomeType()最终命中CoCreateInstance(如果它们没有被各种应用程序域内的东西拦截)。CoCreateInstance对进程外服务器的调用最终将受到IRunningObjectTable类名称的影响(我认为)。最佳选择取决于您想要做什么:

  • 对于进程内服务器,只需使用ComImport
  • 对于未在 .Net 中实现的进程外服务器,ComImport将可以工作,我更愿意调用CoCreateInstance以传递正确的CLSCTX.
  • 对于在 .Net 中实现的进程外服务器,您必须直接调用 CoCreateInstance 以避免添加的“优化”ComImport会导致服务器在进程内运行
  • 如果您正在处理昵称,请使用IRunningObjectTable
  • 如果您从 ProgID 而不是 CLSID 开始,请使用CLSIDFromProgIDType.GetTypeFromProgID

无论我们如何获取对象的引用,我们都是从IUnknownobject在.Net中)开始,然后必须调用IUnknown->QueryInterface以获取指向特定接口的指针。.Net 中的调用是通过转换到标记为(通常用 注释)QueryInterface的接口来实现的。 ComVisibleGuidAttribute

在您指定的示例中,您最终会得到:

// based off of https://bitbucket.org/ciniml/desktopwallpaper
[ComImport]
[Guid("B92B56A9-8B55-4E14-9A89-0199BBB6F93B")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IDesktopWallpaper
{
    void SetWallpaper([MarshalAs(UnmanagedType.LPWStr)] string monitorID, [MarshalAs(UnmanagedType.LPWStr)] string wallpaper);

    [return: MarshalAs(UnmanagedType.LPWStr)]
    string GetWallpaper([MarshalAs(UnmanagedType.LPWStr)] string monitorID);

    [return: MarshalAs(UnmanagedType.LPWStr)]
    string GetMonitorDevicePathAt(uint monitorIndex);

    [return: MarshalAs(UnmanagedType.U4)]
    uint GetMonitorDevicePathCount();

    [return: MarshalAs(UnmanagedType.Struct)]
    Rect GetMonitorRECT([MarshalAs(UnmanagedType.LPWStr)] string monitorID);

    void SetBackgroundColor([MarshalAs(UnmanagedType.U4)] uint color);

    [return: MarshalAs(UnmanagedType.U4)]
    uint GetBackgroundColor();

    void SetPosition([MarshalAs(UnmanagedType.I4)] DesktopWallpaperPosition position);

    [return: MarshalAs(UnmanagedType.I4)]
    DesktopWallpaperPosition GetPosition();

    void SetSlideshow(IntPtr items);

    IntPtr GetSlideshow();

    void SetSlideshowOptions(DesktopSlideshowDirection options, uint slideshowTick);

    void GetSlideshowOptions(out DesktopSlideshowDirection options, out uint slideshowTick);

    void AdvanceSlideshow([MarshalAs(UnmanagedType.LPWStr)] string monitorID, [MarshalAs(UnmanagedType.I4)] DesktopSlideshowDirection direction);

    DesktopSlideshowDirection GetStatus();

    bool Enable();
}

[ComImport]
[Guid("C2CF3110-460E-4fc1-B9D0-8A1C0C9CC4BD")]
public class DesktopWallpaper
{
}

[Flags]
public enum DesktopSlideshowOptions
{
    None = 0,
    ShuffleImages = 0x01
}

[Flags]
public enum DesktopSlideshowState
{
    None = 0,
    Enabled = 0x01,
    Slideshow = 0x02,
    DisabledByRemoteSession = 0x04
}

public enum DesktopSlideshowDirection
{
    Forward = 0,
    Backward = 1
}

public enum DesktopWallpaperPosition
{
    Center = 0,
    Tile = 1,
    Stretch = 2,
    Fit = 3,
    Fill = 4,
    Span = 5,
}

[StructLayout(LayoutKind.Sequential)]
public struct Rect
{
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;
}
Run Code Online (Sandbox Code Playgroud)

其用法示例如下:

public partial class Form1 : Form
{
    private IDesktopWallpaper Wallpaper;

    public Form1()
    {
        InitializeComponent();
    }

    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);

        this.Wallpaper = (IDesktopWallpaper)new DesktopWallpaper();

        uint monitorCount = Wallpaper.GetMonitorDevicePathCount();
        for (uint i = 0; i < monitorCount; i++)
        {
            lbMonitors.Items.Add(Wallpaper.GetMonitorDevicePathAt(i));
        }
    }

    private void lbMonitors_SelectedValueChanged(object sender, EventArgs e)
    {
        var path = (string)lbMonitors.SelectedItem;

        tbWallpaper.Text = Wallpaper.GetWallpaper(path);
    }
}
Run Code Online (Sandbox Code Playgroud)

产生以下形式:

显示监视器列表和 GetWallpaper 结果的表单