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

时间:2017-01-07 01:46:25

标签: c# com interop com-interop

我一直在谷歌搜索试图找到获得COM接口实例的标准方法。

Microsoft在其文章COM Interop Part 1: Client Tutorial中提供了此示例:

// 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();

但是,看起来好像是在实例化COM对象并将其转换为COM接口。

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

我发现here的一个例子定义了一些实例化的类,然后以与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
}

我不明白实例化对象是什么。它似乎是一个任意对象,它有GuidAttribute,似乎表明它是一个真正的COM对象。

另一个例子我发现here System.TypeSystem.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;

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

问题

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

如果没有一个通用的解决方案,那么可以使用什么标准方式来获取COM接口的实例,以及在什么情况下每个这些方式最有用吗?

修改

下载Windows 10 SDK并根据IDesktopWallpaper interface documentation的要求部分引用它后,我发现您可以从Shobjidl.h查找MIDL并在GuidAttribute中使用它为您的接口声明,然后从Shobjidl.idl查找CLSID并将其与Type.GetTypeFromCLSID(Guid)Activator.CreateInstance(Type)结合使用,以获取实现IDesktopWallpaper的对象的实例。

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

然而 我仍然有兴趣知道这是否是这种方法的最佳方式,以及与其他方法相比,这种方法可能有哪些优缺点。

1 个答案:

答案 0 :(得分:0)

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

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

Activator.CreateInstancenew SomeType()最终点击了CoCreateInstance(如果它们没有被各种应用内域内的内容拦截)。对进程外服务器的CoCreateInstance调用最终将使用类名字对象IRunningObjectTable命中(我认为)。最佳选择取决于您要做的事情:

  • 对于进程内服务器,只需使用ComImport
  • 对于未在.Net中实施的进程外服务器,ComImport将起作用,我更愿意致电CoCreateInstance以传递正确的CLSCTX
  • 对于在.Net中实现的.net实现的进程外服务器,您必须直接调用CoCreateInstance以避免"优化"由ComImport添加,这将导致服务器在进程中运行
  • 如果您正在处理绰号,请使用IRunningObjectTable
  • 如果您使用的是ProgID而不是CLSID,请使用CLSIDFromProgIDType.GetTypeFromProgID

无论我们如何获得对象的引用,我们都从IUnknown(.Net中的object开始),然后必须调用IUnknown->QueryInterface来获取指向特殊界面。在.Net中调用QueryInterface是通过强制转换为标记为ComVisible的接口(通常用GuidAttribute注释)来实现的。

在您命名的示例中,您最终会得到:

// 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;
}

其使用示例如下:

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);
    }
}

生成表格:

Form showing list of monitors and result of GetWallpaper