问题描述
.NET对象是自由线程默认情况下。如果封通过COM另一个线程,他们总是得到封给自己,而不管创造者线程是否是STA与否,也不管他们的的ThreadingModel
注册表值。我怀疑,他们聚集自由线程封送(约COM线程的详细信息,可以发现的)。
.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).
我想让我的.NET COM对象使用标准的COM代理编组时封送到另一个线程。问题:
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();
}
}
输出:
{ CurrentManagedThreadId = 11 }
{ equal = True }
{ CurrentManagedThreadId = 12 }
请注意我用 CoMarshalInterThreadInterfaceInStream对该
/ CoGetInterfaceAndReleaseStream
元帅 ComObject
从一个STA线程到另一个。 我想这两个测试()
调用同一个原始的线程,例如在被调用 11
,因为它会一直用C ++实现一个典型的STA COM对象的情况。
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++.
一个可能的解决办法是禁用的IMarshal
接口的.NET COM对象:
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 }
这个作品,但感觉像一个实现特定的黑客。是否有一个更体面的方式来完成这件事,像一些特殊的互操作属性,我可能忽略了?需要注意的是在现实生活中 ComObject
被使用(被编组)由传统的非托管应用程序。
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:
这是暴露在COM的行为,就好像他们已经聚集了自由线程编组管理对象。换句话说,它们可以被称为从任何COM公寓在一个自由线程的方式。没有表现出这种自由线程行为的唯一管理的对象是那些从 ServicedComponent的或 StandardOleMarshalObject 。
这篇关于如何使使.NET.COM对象进行单元线程?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!