nos*_*tio 12 .net c# com multithreading com-interop
默认情况下,.NET对象是自由线程的.如果通过COM封送到另一个线程,它们总是被封送到自己,无论创建者线程是否是STA,并且无论其ThreadingModel
注册表值如何.我怀疑,他们聚合了Free Threaded Marshaler(有关COM线程的更多细节可以在这里找到).
我想让我的.NET COM对象在封送到另一个线程时使用标准的COM marshaller代理.问题:
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();
}
}
Run Code Online (Sandbox Code Playgroud)
输出:
{ CurrentManagedThreadId = 11 } { equal = True } { CurrentManagedThreadId = 12 }
注意我使用CoMarshalInterThreadInterfaceInStream
/ 从一个STA线程CoGetInterfaceAndReleaseStream
编组ComObject
到另一个.我希望Test()
在同一原始线程上调用两个调用,例如11
,在C++中实现的典型STA COM对象就是这种情况.
一种可能的解决方案是禁用IMarshal
.NET COM对象上的接口:
[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;
}
}
Run Code Online (Sandbox Code Playgroud)
输出(根据需要):
{ CurrentManagedThreadId = 11 } { equal = False } { CurrentManagedThreadId = 11 }
这有效,但感觉就像特定于实现的黑客.是否有更好的方法来完成这项工作,就像我可能忽略了一些特殊的互操作属性?请注意,在现实生活ComObject
中,传统的非托管应用程序会使用(并进行编组).
您可以继承StandardOleMarshalObject
或ServicedComponent
为此效果:
暴露给COM的托管对象的行为就像它们聚合了自由线程封送程序一样.换句话说,它们可以以任何线程方式从任何COM公寓调用.唯一没有出现此自由线程行为的托管对象是从ServicedComponent或StandardOleMarshalObject派生的对象 .
Paulo Madeira 的出色回答为何时可以从StandardOleMarshalObject
.
不过,这让我想到,如何处理已经有一个基类的情况,比如 say System.Windows.Forms.Control
,StandardOleMarshalObject
它的继承链中没有?
事实证明,可以聚合标准 COM 封送拆收器。类似于 Free Threaded Marshaler 的CoCreateFreeThreadedMarshaler
,有一个 API:CoGetStdMarshalEx
。这是如何做到的:
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(IComObject))]
public class ComObject : IComObject, ICustomQueryInterface
{
IntPtr _unkMarshal;
public ComObject()
{
NativeMethods.CoGetStdMarshalEx(this, NativeMethods.SMEXF_SERVER, out _unkMarshal);
}
~ComObject()
{
if (_unkMarshal != IntPtr.Zero)
{
Marshal.Release(_unkMarshal);
_unkMarshal = IntPtr.Zero;
}
}
// IComObject methods
public void Test()
{
Console.WriteLine(new { Environment.CurrentManagedThreadId });
}
// ICustomQueryInterface
public CustomQueryInterfaceResult GetInterface(ref Guid iid, out IntPtr ppv)
{
ppv = IntPtr.Zero;
if (iid == NativeMethods.IID_IMarshal)
{
if (Marshal.QueryInterface(_unkMarshal, ref NativeMethods.IID_IMarshal, out ppv) != 0)
return CustomQueryInterfaceResult.Failed;
return CustomQueryInterfaceResult.Handled;
}
return CustomQueryInterfaceResult.NotHandled;
}
static class NativeMethods
{
public static Guid IID_IMarshal = new Guid("00000003-0000-0000-C000-000000000046");
public const UInt32 SMEXF_SERVER = 1;
[DllImport("ole32.dll", PreserveSig = false)]
public static extern void CoGetStdMarshalEx(
[MarshalAs(UnmanagedType.IUnknown)] object pUnkOuter,
UInt32 smexflags,
out IntPtr ppUnkInner);
}
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
1900 次 |
最近记录: |