如何在C#中使用COM接口
在C++中,可以使用CoCreateInstance函数来创建COM接口的实例。
以下教程可以帮助你方便的在C#中实现同样的功能。
方法一、手动生成(适用于所有.NET版本)
1、确定要使用的COM接口
Windows中很多功能都是通过COM实现的,有时候我们想实现一些系统功能,但是又没有直接的Win32 API代调用,就可以寻找COM接口替代。
至于使用哪个COM接口,这个可以通过搜索引擎。
例如,我想设置桌面壁纸,可以通过IDesktopWallpaper接口来实现。
2、查找COM接口的GUID
这里提供了几种方案
一、通过搜索引擎,常用的COM接口,可以通过搜索引擎直接搜索到GUID
二、对于不常用的COM接口,可能搜索引擎不能搜索到对应的GUID,我们可以创建一个Win32工程(需要Visual Studio安装C++桌面开发),然后输入CLSID_接口名称,再按F12就可以看到GUID。
例如:CLSID_DesktopWallpaper,按F12如下所示
IDesktopWallpaper
三、如果电脑上没有安装C++桌面开发负载,可以访问stevemk14ebr的gist来进行搜索
3、接口声明
有了COM接口的GUID后,我们需要对COM接口进行声明
这里有几个方法可供参考:
一、通过C# + COM接口为关键进行进行搜索
例如搜索[C# IDesktopWallpaper],然后在结果中查找,一般会有C#的接口声明,如果没找到相关结果,可以查看方法2
二、访问pinvoke.net搜索
我们打开pinvoke.net: the interop wiki!,搜索IDesktopWallpaper
目前该网站已经停止维护,很大机率会搜索不出来。
三、访问MSDN文档,通过数据类型映射,自行声明COM接口
数据类型的映射可以参考下面的文章:
Platform Invoke Data Types | Microsoft Learn
这种方法虽然比较麻烦,但也算是最终解决方案了。
像我平常跟硬件交互比较多,这种映射也是家常便饭了。
需要注意的是,接口中涉及的类型也要进行声明。
例如void SetPosition(DESKTOP_WALLPAPER_POSITION position)参数里涉及了DESKTOP_WALLPAPER_POSITION,我们需要对这个DESKTOP_WALLPAPER_POSITION类型进行定义。
对于POINT或RECT之类的,建议也是自己定义,不要使用C#内置类型,否则有可能会封送失败。
IDesktopWallpaper在C#中声明如下:
1 [ComVisible(true)] 2 public enum DESKTOP_SLIDESHOW_DIRECTION 3 { 5 DSD_FORWARD = 0, 7 DSD_BACKWARD = 1 8 } 9 10 11 public struct RECT 12 { 13 public int left; 14 public int top; 15 public int right; 16 public int bottom; 17 } 18 19 [ComVisible(true)] 20 public enum DESKTOP_WALLPAPER_POSITION 21 { 23 DWPOS_CENTER = 0, 25 DWPOS_TILE = 1, 27 DWPOS_STRETCH = 2, 29 DWPOS_FIT = 3, 31 DWPOS_FILL = 4, 33 DWPOS_SPAN = 5 34 } 37 38 [ComVisible(true)] 39 [Flags] 40 public enum DESKTOP_SLIDESHOW_STATE 41 { 42 DSS_ENABLED = 1, 44 DSS_SLIDESHOW = 2, 46 DSS_DISABLED_BY_REMOTE_SESSION = 4 47 } 49 50 [ComImport] 51 [Guid("B92B56A9-8B55-4E14-9A89-0199BBB6F93B")] 52 [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 53 public interface IDesktopWallpaper 54 { 55 void SetWallpaper([MarshalAs(UnmanagedType.LPWStr)] string monitorID, [MarshalAs(UnmanagedType.LPWStr)] string wallpaper); 56 57 [return: MarshalAs(UnmanagedType.LPWStr)] 58 StringBuilder GetWallpaper([MarshalAs(UnmanagedType.LPWStr)] string monitorID); 59 60 [return: MarshalAs(UnmanagedType.LPWStr)] 61 StringBuilder GetMonitorDevicePathAt(uint monitorIndex); 62 63 [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] 64 uint GetMonitorDevicePathCount(); 65 66 RECT GetMonitorRECT([MarshalAs(UnmanagedType.LPWStr)] string monitorID); 67 68 void SetBackgroundColor(uint color); 69 70 uint GetBackgroundColor(); 71 72 void SetPosition([MarshalAs(UnmanagedType.I4)] DESKTOP_WALLPAPER_POSITION position); 73 74 [return: MarshalAs(UnmanagedType.I4)] 75 DESKTOP_WALLPAPER_POSITION GetPosition(); 76 77 //未引入IShellItemArray类型,暂时不导入 78 //void SetSlideshow(IShellItemArray items); 79 80 //未引用IShellItemArray类型,暂时不导入 81 //IShellItemArray GetSlideshow(); 82 //IntPtr GetSlideshow(); 83 84 void SetSlideshowOptions(uint options, uint slideshowTick); 85 86 void GetSlideshowOptions(out uint options, out uint slideshowTick); 87 88 void AdvanceSlideshow([MarshalAs(UnmanagedType.LPWStr)] string monitorID, [MarshalAs(UnmanagedType.I4)] DESKTOP_SLIDESHOW_DIRECTION direction); 89 90 DESKTOP_SLIDESHOW_STATE GetStatus(); 91 92 void Enable([MarshalAs(UnmanagedType.Bool)] bool enable); 93 }
4、定义类
这个步骤和步骤3类似,但是不需要定义类的成员函数。这里的GUID使用的是CLSID_DesktopWallpaper的GUID
1 [ComImport] 2 [Guid("C2CF3110-460E-4FC1-B9D0-8A1C0C9CC4BD")] 3 public class DesktopWallpaper 4 { 5 6 }
5、使用
1 IDesktopWallpaper desktopWallpaper = (IDesktopWallpaper)new DesktopWallpaper(); 2 3 //调用成员函数 4 desktopWallpaper.xxxx();
方法二、自动生成(适用于.NET6+版本)
自动生成主要是借助Cswin32项目来实现这个功能,CsWin32是一个源代码生成器,用于在 C# 项目中添加一组用户定义的 Win32 P/Invoke 方法和支持类型。
CsWin32项目地址:https://github.com/microsoft/CsWin32
这种方法会比较简单方便,但是仅适用于.NET Core。.Net Framework无法使用。
另外还要求Visual Studio的版本至少是Visual Studio 2019 Update 11 (16.11)。
使用CsWin32生成COM接口的声明,在官方的文档中并未直接说明,我也是在一个issue中找到了实现方法。
实现步骤如下:
1、nuget导入Microsoft.Windows.CsWin32包
2、在项目下,新建一个NativeMethods.txt文件
3、在NativeMethods.txt下输入需要导入的COM接口
例如我们想使用IDesktopWallpaper接口,就在NativeMethods.txt下输入
1 IDesktopWallpaper 2 DesktopWallpaper
注意:
1、两个类型都需要写,如果只写了IDesktopWallpaper,就无法实例化接口。我一开始就是卡在这里。
2、需要生成接口的类型都可以写在NativeMethods.txt里,每个类型单独一行。
4、通过代码生成器生成的类型在哪
对于自动生成的类型,命名空间都不一样,但是都是在Windows.Win32命名空间下。
在Visual Studio中,输入Windows.Win32,自己定位所需要类型所在的命名空间即可。
例如IDesktopWallpaper所在的命名空间是:Windows.Win32.UI.Shell
也可以通过Ctrl+T,输入类型名称进行查找
5、使用
1 Windows.Win32.UI.Shell.IDesktopWallpaper desktopWallpaper = (Windows.Win32.UI.Shell.IDesktopWallpaper)new Windows.Win32.UI.Shell.DesktopWallpaper(); 2 Windows.Win32.Foundation.PWSTR pWSTR = new Windows.Win32.Foundation.PWSTR(); 3 4 5 unsafe 6 { 7 char* p = stackalloc char[1]; 8 p[0] = '0'; 9 Windows.Win32.Foundation.PWSTR szMonitorId = new Windows.Win32.Foundation.PWSTR(p); 10 #pragma warning disable CA1416 // 验证平台兼容性 11 desktopWallpaper.GetWallpaper(szMonitorId, &pWSTR); 12 #pragma warning restore CA1416 // 验证平台兼容性 13 14 MessageBox.Show(pWSTR.ToString()); 15 }
说明:CsWin32项目在生成LPWSTR/PWSTR类型时没有使用C#的类型进行映射,例如只读字符串的使用string,需要写入字符串的使用分配空间后的StringBuilder。
所以不得不使用unsafe关键字,并使用指针。这种方法并不太友好 。
示例代码:
下载
参考资料:
IDesktopWallpaper (shobjidl_core.h) - Win32 apps | Microsoft Learn