我是否需要对由CreateStreamOnHGlobal返回的IStream进行编组,以跨线程使用?

我有一个使用CreateStreamOnHGlobal创build的COMstream对象( IStream )。

我想在同一个进程中的不同线程中使用它。 我需要编组stream对象本身(与CoMarshalInterface等)? 或者它已经是线程安全的?

EDITED ,读取/写入/寻求与我的代码中的锁正确同步。

COM将IStream视为特殊类型的接口,可以跨线程安全使用。 这是必要的,以便使用CoMarshalInterThreadInterfaceInStream可以跨IStream线程边界封送其他接口。

更多的信息可以在Dobb博士的2003年的文章中找到: Marshaling COM接口 。

更新:

最初的答案并不完全正确。 由CreateStreamOnHGlobal返回并通过CoMarshalInterThreadInterfaceInStream间接创建的OLE提供的IStream接口的实现可以在同一进程中的线程之间安全地访问。

文档分散,很难得到。 CoMarshalInterThreadInterfaceInStream声明如下:

在接收线程中运行的客户端尝试解组指针时,ppStm参数中返回的流将保证正确运行。

来自SHCreateMemStream CreateStreamOnHGlobal可以使用类似的信息:

由CreateStreamOnHGlobal创建的流是线程安全的。

这些保证通常不适用于所有的IStream实现。 如果要安全地使用它,即使不是绝对必要的,也可以使用CoMarshalInterThreadInterfaceInStream始终CoMarshalInterThreadInterfaceInStream跨越线程边界的接口。 以这种方式编组接口指针永远不会有害,因为如果封送处理不是必要的,COM足够聪明而不编组(或重新编组) 。 请记住,这是元帅一次 – 解组一次 。 如果你想从多个线程解组接口,你可以把接口放到全局接口表中 。

根据MSDN 编辑

线程安全。 由SHCreateMemStream创建的流在Windows 8之前是线程安全的。在较早的系统上,流不是线程安全的。 由CreateStreamOnHGlobal创建的流是线程安全的

我有两个相反的答案,所以我决定验证它。 它看起来像@HansPassant是正确的,而@Inspectable是错误的。 至少,在另一个线程上为原始IStream对象创建一个代理。

测试案例显示如下:

  • 即使两个线程都属于不同的公寓,仍然可以跨线程使用对IStream的直接引用。 它只是工作。

  • 如果两个线程都是MTA线程,则来自threadIStream将在thread2上解组, thread2完全相同的IUnknown指针,而不是代理。

  • 如果thread1是STA,并且thread2是MTA,则有一个代理。 但是在thread上创建的直接引用仍然可以在thread2thread2

请注意,在不同线程的严格循环内,如何在stream1同时执行读取和写入操作。 当然,这是没有意义的,通常有锁定同步读取/写入。 但是, 它证明了由CreateStreamOnHGlobal返回的IStream对象是真正的线程安全的。

我不确定这是否是对任何 IStream实现的官方COM要求,正如Dobb博士的文章所暗示的 – 很可能它只是针对CreateStreamOnHGlobal

 using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading; namespace ConsoleApplication1 { class Program { static void TestStream() { // start thread1 var thread1 = new Thread(() => { // create stream1 on thread1 System.Runtime.InteropServices.ComTypes.IStream stream1; CreateStreamOnHGlobal(IntPtr.Zero, true, out stream1); IntPtr unkStream1 = Marshal.GetIUnknownForObject(stream1); // marshal stream1, to be unmarshalled on thread2 Guid iid = typeof(System.Runtime.InteropServices.ComTypes.IStream).GUID; System.Runtime.InteropServices.ComTypes.IStream marshallerStream; CoMarshalInterThreadInterfaceInStream(ref iid, stream1, out marshallerStream); // write to stream1 var buf1 = new byte[] { 1, 2, 3, 4 }; stream1.Write(buf1, buf1.Length, IntPtr.Zero); // start thread2 var thread2 = new Thread(() => { // read from stream1 (the direct reference) on thread2 var buf2 = new byte[buf1.Length]; for (var i = 0; i < 10000; i++) { stream1.Seek(0, 0, IntPtr.Zero); stream1.Read(buf2, buf2.Length, IntPtr.Zero); // trule thread safe, this always works! for (var j = 0; j < buf2.Length; j++) Debug.Assert(buf1[j] == buf2[j]); } // Unmarshal and compare IUnknown pointers object stream2; CoGetInterfaceAndReleaseStream(marshallerStream, ref iid, out stream2); IntPtr unkStream2 = Marshal.GetIUnknownForObject(stream2); // Bangs if thread1 is STA, works OK if thread1 is MTA Debug.Assert(unkStream1 == unkStream2); Marshal.Release(unkStream2); }); for (var i = 0; i < 10000; i++) { stream1.Seek(0, 0, IntPtr.Zero); stream1.Write(buf1, buf1.Length, IntPtr.Zero); } thread2.SetApartmentState(ApartmentState.MTA); thread2.Start(); thread2.Join(); Marshal.Release(unkStream1); }); thread1.SetApartmentState(ApartmentState.STA); thread1.Start(); thread1.Join(); } static void Main(string[] args) { TestStream(); } [DllImport("ole32.dll", PreserveSig = false)] public static extern void CreateStreamOnHGlobal( IntPtr hGlobal, bool fDeleteOnRelease, [Out] out System.Runtime.InteropServices.ComTypes.IStream pStream); [DllImport("ole32.dll", PreserveSig = false)] public static extern void CoMarshalInterThreadInterfaceInStream( [In] ref Guid riid, [MarshalAs(UnmanagedType.IUnknown)] object unk, out System.Runtime.InteropServices.ComTypes.IStream stream); [DllImport("ole32.dll", PreserveSig = false)] public static extern void CoGetInterfaceAndReleaseStream( [In] System.Runtime.InteropServices.ComTypes.IStream stream, [In] ref Guid riid, [Out, MarshalAs(UnmanagedType.IUnknown)] out object unk); } }