演练 dotnet 使用 GeneratedComInterface 源代码生成方式调用 COM 接口
官方文档:
- ComWrappers source generation - .NET Microsoft Learn
- Using the ComWrappers API - .NET Microsoft Learn
本文将演练在 WPF 应用里面手动写 COM 调用的方式,调用打开文件对话框。访问 COM 的方式为源代码生成器方式
本文内容里面只给出关键代码片段,如需要全部的项目文件,可到本文末尾找到本文所有代码的下载方法
必要的基础库安装:
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.106">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
编写 NativeMethods.txt 添加 Win32 方法调用
CoInitializeEx
CoCreateInstance
CoGetClassObject
上述逻辑详细请看 dotnet 使用 CsWin32 库简化 Win32 函数调用逻辑
编写 IFileOpenDialog 接口,代码如下
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
namespace NearnearfayyerchodaiHikelahuhaw;
[GeneratedComInterface(Options = ComInterfaceOptions.ComObjectWrapper)]
[Guid("d57c7288-d4ad-4768-be02-9d969532d960")]
internal partial interface IFileOpenDialog
{
void Show(IntPtr owner);
}
在 MainWindow 的 Loaded 事件调用,代码如下
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices.Marshalling;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Windows.Win32.System.Com;
using System.Runtime.Versioning;
using System.Windows.Interop;
namespace NearnearfayyerchodaiHikelahuhaw;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
}
private unsafe void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
//Windows.Win32.PInvoke.CoInitializeEx(null, COINIT.COINIT_APARTMENTTHREADED | COINIT.COINIT_DISABLE_OLE1DDE);
var cw = new StrategyBasedComWrappers();
var rclsid = Guid.Parse("DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7");
var riid = Guid.Parse("d57c7288-d4ad-4768-be02-9d969532d960");
void* ppv;
var hr = CoCreateInstance(&rclsid, IntPtr.Zero,
CLSCTX.CLSCTX_ALL, &riid, &ppv);
hr.ThrowOnFailure();
var fileOpenDialog = (IFileOpenDialog) cw.GetOrCreateObjectForComInstance(new IntPtr(ppv), CreateObjectFlags.None);
var windowInteropHelper = new WindowInteropHelper(this);
fileOpenDialog.Show(windowInteropHelper.Handle);
}
[DllImport("OLE32.dll", ExactSpelling = true)]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
[SupportedOSPlatform("windows5.0")]
internal static extern unsafe Windows.Win32.Foundation.HRESULT CoCreateInstance(global::System.Guid* rclsid, IntPtr pUnkOuter, Windows.Win32.System.Com.CLSCTX dwClsContext, global::System.Guid* riid, void* ppv);
}
运行代码,即可看到弹出打开文件对话框
由于当前正在 WPF 线程里执行 Loaded 事件的逻辑,因此 CoInitializeEx 是可不调用的。在 WPF 框架内部已经帮忙调用了
以上代码重新定义 CoCreateInstance 是为了获取 ppv
指针
以上的 IFileOpenDialog 将生成以下代码
// <auto-generated />
#pragma warning disable CS0612, CS0618
file unsafe class InterfaceInformation : global::System.Runtime.InteropServices.Marshalling.IIUnknownInterfaceType
{
public static global::System.Guid Iid { get; } = new([136, 114, 124, 213, 173, 212, 104, 71, 190, 2, 157, 150, 149, 50, 217, 96]);
public static void** ManagedVirtualMethodTable => null;
}
[global::System.Runtime.InteropServices.DynamicInterfaceCastableImplementationAttribute]
file unsafe partial interface InterfaceImplementation : global::NearnearfayyerchodaiHikelahuhaw.IFileOpenDialog
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Interop.ComInterfaceGenerator", "9.0.11.11010")]
[global::System.Runtime.CompilerServices.SkipLocalsInitAttribute]
void global::NearnearfayyerchodaiHikelahuhaw.IFileOpenDialog.Show(nint owner)
{
var(__this, __vtable_native) = ((global::System.Runtime.InteropServices.Marshalling.IUnmanagedVirtualMethodTableProvider)this).GetVirtualMethodTableInfoForKey(typeof(global::NearnearfayyerchodaiHikelahuhaw.IFileOpenDialog));
int __invokeRetVal;
{
__invokeRetVal = ((delegate* unmanaged[MemberFunction]<void*, nint, int> )__vtable_native[3])(__this, owner);
}
// NotifyForSuccessfulInvoke - Keep alive any managed objects that need to stay alive across the call.
global::System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(__invokeRetVal);
global::System.GC.KeepAlive(this);
}
}
file unsafe partial interface InterfaceImplementation
{
}
file unsafe partial interface InterfaceImplementation
{
}
namespace NearnearfayyerchodaiHikelahuhaw
{
[global::System.Runtime.InteropServices.Marshalling.IUnknownDerivedAttribute<InterfaceInformation, InterfaceImplementation>]
internal partial interface IFileOpenDialog
{
}
}
namespace NearnearfayyerchodaiHikelahuhaw
{
internal partial interface IFileOpenDialog
{
}
}
本文代码放在 github 和 gitee 上,可以使用如下命令行拉取代码。我整个代码仓库比较庞大,使用以下命令行可以进行部分拉取,拉取速度比较快
先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 333ee4d7f57cdecc80be1835a210a5d39a00d8a6
以上使用的是国内的 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码,将 gitee 源换成 github 源进行拉取代码。如果依然拉取不到代码,可以发邮件向我要代码
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 333ee4d7f57cdecc80be1835a210a5d39a00d8a6
获取代码之后,进入 WPFDemo/NearnearfayyerchodaiHikelahuhaw 文件夹,即可获取到源代码
更多技术博客,请参阅 博客导航
博客园博客只做备份,博客发布就不再更新,如果想看最新博客,请访问 https://blog.lindexi.com/
如图片看不见,请在浏览器开启不安全http内容兼容

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名[林德熙](https://www.cnblogs.com/lindexi)(包含链接:https://www.cnblogs.com/lindexi ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我[联系](mailto:lindexi_gd@163.com)。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 易语言 —— 开山篇