How to make make a .NET COM object apartment-threaded?(如何使 .NET COM 对象成为单元线程?)
问题描述
.NET 对象默认是自由线程的.如果通过 COM 编组到另一个线程,它们总是被编组给自己,无论创建者线程是否是 STA,也不管它们的 ThreadingModel
注册表值如何.我怀疑,他们聚合了 Free Threaded Marshaler(可以找到关于 COM 线程的更多详细信息 这里).
我想让我的 .NET COM 对象在编组到另一个线程时使用标准 COM 编组器代理.问题:
使用系统;使用 System.Runtime.InteropServices;使用 System.Threading;使用 System.Threading.Tasks;使用 System.Windows.Threading;命名空间控制台应用程序{课堂节目{静态无效主要(字符串 [] 参数){var apt1 = new WpfApartment();var apt2 = new WpfApartment();apt1.Invoke(() =>{var comObj = 新的 ComObject();comObj.Test();IntPtr pStm;NativeMethods.CoMarshalInterThreadInterfaceInStream(NativeMethods.IID_IUnknown, comObj, out pStm);apt2.Invoke(() =>{对象unk;NativeMethods.CoGetInterfaceAndReleaseStream(pStm, NativeMethods.IID_IUnknown, out unk);Console.WriteLine(new { equal = Object.ReferenceEquals(comObj, unk) });var marshaledComObj = (IComObject)unk;marshaledComObj.Test();});});Console.ReadLine();}}//ComObject[ComVisible(真)][Guid("00020400-0000-0000-C000-000000000046")]//IID_IDispatch[接口类型(ComInterfaceType.InterfaceIsIDispatch)]公共接口 IComObject{无效测试();}[ComVisible(真)][ClassInterface(ClassInterfaceType.None)][ComDefaultInterface(typeof(IComObject))]公共类 ComObject : IComObject{//IComObject 方法公共无效测试(){Console.WriteLine(new { Environment.CurrentManagedThreadId });}}//WpfApartment - WPF 调度程序线程内部类 WpfApartment : IDisposable{线程_thread;//STA线程公共 System.Threading.Tasks.TaskScheduler TaskScheduler { get;私人套装;}公共 WpfApartment(){var tcs = new TaskCompletionSource
();//使用 WPF Dispatcher 启动 STA 线程_thread = 新线程(_ =>{NativeMethods.OleInitialize(IntPtr.Zero);尝试{//发布回调以获取 TaskSchedulerDispatcher.CurrentDispatcher.InvokeAsync(() =>tcs.SetResult(System.Threading.Tasks.TaskScheduler.FromCurrentSynchronizationContext()),DispatcherPriority.ApplicationIdle);//运行 WPF Dispatcher 消息循环调度程序.Run();}最后{NativeMethods.OleUninitialize();}});_thread.SetApartmentState(ApartmentState.STA);_thread.IsBackground = true;_thread.Start();this.TaskScheduler = tcs.Task.Result;}//关闭STA线程公共无效处置(){if (_thread != null && _thread.IsAlive){InvokeAsync(() => System.Windows.Threading.Dispatcher.ExitAllFrames());_thread.Join();_thread = null;}}//Task.Factory.StartNew 包装器公共任务 InvokeAsync(操作动作){返回Task.Factory.StartNew(动作,CancellationToken.None, TaskCreationOptions.None, this.TaskScheduler);}公共无效调用(动作动作){InvokeAsync(action).Wait();}}公共静态类 NativeMethods{公共静态只读 Guid IID_IUnknown = new Guid("00000000-0000-0000-C000-000000000046");公共静态只读 Guid IID_IDispatch = new Guid("00020400-0000-0000-C000-000000000046");[DllImport("ole32.dll", PreserveSig = false)]公共静态外部无效CoMarshalInterThreadInterfaceInStream([在,MarshalAs(UnmanagedType.LPStruct)] Guid riid,[MarshalAs(UnmanagedType.IUnknown)] 对象 pUnk,出 IntPtr ppStm);[DllImport("ole32.dll", PreserveSig = false)]公共静态外部无效CoGetInterfaceAndReleaseStream(IntPtr pStm,[在,MarshalAs(UnmanagedType.LPStruct)] Guid riid,[MarshalAs(UnmanagedType.IUnknown)] 出对象 ppv);[DllImport("ole32.dll", PreserveSig = false)]公共静态外部无效OleInitialize(IntPtr pvReserved);[DllImport("ole32.dll", PreserveSig = true)]公共静态外部无效OleUninitialize();}} 输出:
<上一页>{ CurrentManagedThreadId = 11 }{ 等于 = 真 }{ CurrentManagedThreadId = 12 }注意我使用
CoMarshalInterThreadInterfaceInStream
/CoGetInterfaceAndReleaseStream
将ComObject
从一个 STA 线程编组到另一个线程.我希望在同一个原始线程上调用两个Test()
调用,例如11
,就像用 C++ 实现的典型 STA COM 对象一样.一种可能的解决方案是禁用 .NET COM 对象上的
IMarshal
接口:[ComVisible(true)][ClassInterface(ClassInterfaceType.None)][ComDefaultInterface(typeof(IComObject))]公共类 ComObject : IComObject, ICustomQueryInterface{//IComObject 方法公共无效测试(){Console.WriteLine(new { Environment.CurrentManagedThreadId });}公共静态只读 Guid IID_IMarshal = new Guid("00000003-0000-0000-C000-000000000046");公共 CustomQueryInterfaceResult GetInterface(ref Guid iid, out IntPtr ppv){ppv = IntPtr. 零;如果(iid == IID_IMarshal){返回 CustomQueryInterfaceResult.Failed;}返回 CustomQueryInterfaceResult.NotHandled;}}
输出(根据需要):
<上一页>{ CurrentManagedThreadId = 11 }{ 等于 = 假 }{ CurrentManagedThreadId = 11 }
这行得通,但感觉就像是特定于实现的 hack.有没有更体面的方法来完成这项工作,比如我可能忽略的一些特殊互操作属性?请注意,在现实生活中,ComObject
由旧版非托管应用程序使用(并被封送).
你可以继承自 StandardOleMarshalObject
或 ServicedComponent
用于该效果:
向 COM 公开的托管对象的行为就像它们聚合了自由线程封送拆收器一样.换句话说,它们可以以自由线程的方式从任何 COM 单元中调用.唯一没有表现出这种自由线程行为的托管对象是那些派生自 ServicedComponent 或 StandardOleMarshalObject.
.NET objects are free-threaded by default. If marshaled to another thread via COM, they always get marshaled to themselves, regardless of whether the creator thread was STA or not, and regardless of their ThreadingModel
registry value. I suspect, they aggregate the Free Threaded Marshaler (more details about COM threading could be found here).
I want to make my .NET COM object use the standard COM marshaller proxy when marshaled to another thread. The problem:
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
var apt1 = new WpfApartment();
var apt2 = new WpfApartment();
apt1.Invoke(() =>
{
var comObj = new ComObject();
comObj.Test();
IntPtr pStm;
NativeMethods.CoMarshalInterThreadInterfaceInStream(NativeMethods.IID_IUnknown, comObj, out pStm);
apt2.Invoke(() =>
{
object unk;
NativeMethods.CoGetInterfaceAndReleaseStream(pStm, NativeMethods.IID_IUnknown, out unk);
Console.WriteLine(new { equal = Object.ReferenceEquals(comObj, unk) });
var marshaledComObj = (IComObject)unk;
marshaledComObj.Test();
});
});
Console.ReadLine();
}
}
// ComObject
[ComVisible(true)]
[Guid("00020400-0000-0000-C000-000000000046")] // IID_IDispatch
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IComObject
{
void Test();
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(IComObject))]
public class ComObject : IComObject
{
// IComObject methods
public void Test()
{
Console.WriteLine(new { Environment.CurrentManagedThreadId });
}
}
// WpfApartment - a WPF Dispatcher Thread
internal class WpfApartment : IDisposable
{
Thread _thread; // the STA thread
public System.Threading.Tasks.TaskScheduler TaskScheduler { get; private set; }
public WpfApartment()
{
var tcs = new TaskCompletionSource<System.Threading.Tasks.TaskScheduler>();
// start the STA thread with WPF Dispatcher
_thread = new Thread(_ =>
{
NativeMethods.OleInitialize(IntPtr.Zero);
try
{
// post a callback to get the TaskScheduler
Dispatcher.CurrentDispatcher.InvokeAsync(
() => tcs.SetResult(System.Threading.Tasks.TaskScheduler.FromCurrentSynchronizationContext()),
DispatcherPriority.ApplicationIdle);
// run the WPF Dispatcher message loop
Dispatcher.Run();
}
finally
{
NativeMethods.OleUninitialize();
}
});
_thread.SetApartmentState(ApartmentState.STA);
_thread.IsBackground = true;
_thread.Start();
this.TaskScheduler = tcs.Task.Result;
}
// shutdown the STA thread
public void Dispose()
{
if (_thread != null && _thread.IsAlive)
{
InvokeAsync(() => System.Windows.Threading.Dispatcher.ExitAllFrames());
_thread.Join();
_thread = null;
}
}
// Task.Factory.StartNew wrappers
public Task InvokeAsync(Action action)
{
return Task.Factory.StartNew(action,
CancellationToken.None, TaskCreationOptions.None, this.TaskScheduler);
}
public void Invoke(Action action)
{
InvokeAsync(action).Wait();
}
}
public static class NativeMethods
{
public static readonly Guid IID_IUnknown = new Guid("00000000-0000-0000-C000-000000000046");
public static readonly Guid IID_IDispatch = new Guid("00020400-0000-0000-C000-000000000046");
[DllImport("ole32.dll", PreserveSig = false)]
public static extern void CoMarshalInterThreadInterfaceInStream(
[In, MarshalAs(UnmanagedType.LPStruct)] Guid riid,
[MarshalAs(UnmanagedType.IUnknown)] object pUnk,
out IntPtr ppStm);
[DllImport("ole32.dll", PreserveSig = false)]
public static extern void CoGetInterfaceAndReleaseStream(
IntPtr pStm,
[In, MarshalAs(UnmanagedType.LPStruct)] Guid riid,
[MarshalAs(UnmanagedType.IUnknown)] out object ppv);
[DllImport("ole32.dll", PreserveSig = false)]
public static extern void OleInitialize(IntPtr pvReserved);
[DllImport("ole32.dll", PreserveSig = true)]
public static extern void OleUninitialize();
}
}
Output:
{ CurrentManagedThreadId = 11 } { equal = True } { CurrentManagedThreadId = 12 }
Note I use CoMarshalInterThreadInterfaceInStream
/CoGetInterfaceAndReleaseStream
to marshal ComObject
from one STA thread to another. I want both Test()
calls to be invoked on the same original thread, e.g. 11
, as it would have been the case with a typical STA COM object implemented in C++.
One possible solution is to disable IMarshal
interface on the .NET COM object:
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(IComObject))]
public class ComObject : IComObject, ICustomQueryInterface
{
// IComObject methods
public void Test()
{
Console.WriteLine(new { Environment.CurrentManagedThreadId });
}
public static readonly Guid IID_IMarshal = new Guid("00000003-0000-0000-C000-000000000046");
public CustomQueryInterfaceResult GetInterface(ref Guid iid, out IntPtr ppv)
{
ppv = IntPtr.Zero;
if (iid == IID_IMarshal)
{
return CustomQueryInterfaceResult.Failed;
}
return CustomQueryInterfaceResult.NotHandled;
}
}
Output (as desired):
{ CurrentManagedThreadId = 11 } { equal = False } { CurrentManagedThreadId = 11 }
This works, but it feels like an implementation-specific hack. Is there a more decent way to get this done, like some special interop attribute I might have overlooked? Note that in real life ComObject
is used (and gets marshaled) by a legacy unmanaged application.
You can inherit from StandardOleMarshalObject
or ServicedComponent
for that effect:
Managed objects that are exposed to COM behave as if they had aggregated the free-threaded marshaler. In other words, they can be called from any COM apartment in a free-threaded manner. The only managed objects that do not exhibit this free-threaded behavior are those objects that derive from ServicedComponent or StandardOleMarshalObject.
这篇关于如何使 .NET COM 对象成为单元线程?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!
本文标题为:如何使 .NET COM 对象成为单元线程?
基础教程推荐
- 如何在 IDE 中获取 Xamarin Studio C# 输出? 2022-01-01
- MS Visual Studio .NET 的替代品 2022-01-01
- c# Math.Sqrt 实现 2022-01-01
- 将 XML 转换为通用列表 2022-01-01
- 有没有办法忽略 2GB 文件上传的 maxRequestLength 限制? 2022-01-01
- 将 Office 安装到 Windows 容器 (servercore:ltsc2019) 失败,错误代码为 17002 2022-01-01
- SSE 浮点算术是否可重现? 2022-01-01
- 为什么Flurl.Http DownloadFileAsync/Http客户端GetAsync需要 2022-09-30
- 如何激活MC67中的红灯 2022-01-01
- rabbitmq 的 REST API 2022-01-01