gpt4 book ai didi

c# - 如何使 .NET COM 对象成为单元线程?

转载 作者:可可西里 更新时间:2023-11-01 08:25:28 25 4
gpt4 key购买 nike

.NET 对象默认是自由线程的。如果通过 COM 编码到另一个线程,它们总是被编码到自己,无论创建者线程是否为 STA,也无论它们的 ThreadingModel 注册表值如何。我怀疑,他们汇总了 Free Threaded Marshaler (有关 COM 线程的更多详细信息,请参见 here)。

我想让我的 .NET COM 对象在编码到另一个线程时使用标准的 COM 编码器代理。问题:

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 }

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;
}
}

输出(根据需要):

{ CurrentManagedThreadId = 11 }{ equal = False }{ CurrentManagedThreadId = 11 }

这可行,但感觉像是特定于实现的 hack。有没有更体面的方法来完成这项工作,比如我可能忽略的一些特殊互操作属性?请注意,在现实生活中,ComObject 由遗留的非托管应用程序使用(并被编码(marshal))。

最佳答案

你可以继承自StandardOleMarshalObject or ServicedComponent为此效果:

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.

关于c# - 如何使 .NET COM 对象成为单元线程?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28628811/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com