C#如何获取COM接口的实例 [英] C# How do you get an instance of a COM interface

查看:123
本文介绍了C#如何获取COM接口的实例的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在做很多事情,试图找到获取COM接口实例的标准方法。



Microsoft在以下示例中提供了一个示例他们的文章


I've been doing quite a bit of googling trying to find the standard way to get an instance of a COM interface.

Microsoft provides an example of this in their article 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();

However, it appears as though they are instantiating a COM object and casting it to a COM interface.

For the interface I am interested in, IDesktopWallpaper, there does not seem to be an implementing COM object to instantiate.

An example I found here defines some class that gets instantiated and then casts it to the interface the same way that the msdn example does:

[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
}

I'm not understanding what the instantiated object is. It seems like an arbitrary object, it has a GuidAttribute which seems to indicate that it is an actual COM object.

Another example i found here System.Type and System.Runtime.InteropServices.Marshal to instantiate an object and then casts it to the interface:

IntPtr ptrRet;
SHGetMalloc(out ptrRet);

System.Type mallocType = System.Type.GetType("IMalloc");
Object obj = Marshal.GetTypedObjectForIUnknown(ptrRet,mallocType);
IMalloc pMalloc = (IMalloc)obj;

This method seems to be requesting a pointer to an existing instance of the interface. I can't find any methods like SHGetMalloc for IDesktopWallpaper in the Windows Shell documentation.

Question

So, long story short, what's the standard way to get an instance of a COM interface?

In the event that there is no one-size-fits-all solution, what are the standard ways that one can use to get an instance of a COM interface and in what circumstance is each of these ways most useful?

Edit

After downloading the Windows 10 SDK and referencing that against the Requirements section of the IDesktopWallpaper interface documentation, I have discovered that you can look up the MIDL from Shobjidl.h and use that in the GuidAttribute for your interface declaration and then look up the CLSID from Shobjidl.idl and use that in conjunction with Type.GetTypeFromCLSID(Guid) and Activator.CreateInstance(Type) to get an instance of an object that implements IDesktopWallpaper.

I also see now that the CLSID is what is used in the second method listed above for the GuidAttribute of the seemingly arbitrary object. It seems like this method allows you to mimic managed instantiation of the object by instantiating the class and then casting the instance to the COM interface.

However I am still interested to know if this is the best way to do this and what pros and cons may be associated with this method vs others.

解决方案

You can get a pointer to a COM object reference by a variety of methods:

  • P/Invoke CoCreateInstance
  • P/Invoke CLSIDFromProgIDCoCreateInstance
  • P/Invoke IRunningObjectTable.GetObject
  • Type.GetTypeFromCLSIDActivator.CreateInstance
  • Type.GetTypeFromProgIDActivator.CreateInstance
  • new SomeType() where SomeType is marked with ComImport

Activator.CreateInstance and new SomeType() eventually hit CoCreateInstance (if they do not get intercepted by various in-app-domain stuff). Calls to CoCreateInstance for an out-of-process server will eventually hit IRunningObjectTable with a class moniker (I think). The best option depends on what you are trying to do:

  • For an in-process server, just use ComImport
  • For an out-of-process server that is not implemented in .Net, ComImport will work, I would prefer to call CoCreateInstance to pass the right CLSCTX.
  • For a .net implemented out-of-process server implemented in .Net, you must call CoCreateInstance directly to avoid the "optimizations" added by ComImport that would result in the server being run in-process
  • If you are dealing with a moniker, use IRunningObjectTable
  • If you are starting out with a ProgID rather than a CLSID, use either CLSIDFromProgID or Type.GetTypeFromProgID

Regardless of how we get a reference to an object, we start out with IUnknown (object in .Net), and then have to call IUnknown->QueryInterface to get a pointer to a particular interface. Calling QueryInterface in .Net is effected by casting to an interface marked as ComVisible (and usually annotated with GuidAttribute).

In the example you named, you would end up with:

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

An example use of which would be:

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

Which produces the form:

这篇关于C#如何获取COM接口的实例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆