【问题标题】:Do I need to marshal IStream returned by CreateStreamOnHGlobal, for use across threads?我是否需要编组 CreateStreamOnHGlobal 返回的 IStream 以供跨线程使用?
【发布时间】:2013-11-22 14:55:46
【问题描述】:

我有一个 COM 流对象 (IStream),使用 CreateStreamOnHGlobal 创建。

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

已编辑,读取/写入/搜索与我的代码中的锁正确同步。

【问题讨论】:

  • 请看我的评论。
  • @Ben,this 怎么样,刚刚发现:线程安全。从 Windows 8 开始,由 SHCreateMemStream 创建的流是线程安全的。在早期系统上,流不是线程安全的。 由 CreateStreamOnHGlobal 创建的流是线程安全的。
  • 奇怪的是,这不在 CreateStreamOnHGlobal 的文档中。我怀疑这可能是一个错误。

标签: windows multithreading winapi com marshalling


【解决方案1】:

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

更多信息可以在 Dobb 博士 2003 年的一篇文章中找到:Marshaling COM interfaces

更新:

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

文档分散且难以获取。 CoMarshalInterThreadInterfaceInStream 声明如下:

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

CreateStreamOnHGlobal 可从SHCreateMemStream 获得类似信息:

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

这些保证通常不适用于所有IStream 实现。如果您想安全起见,您始终可以使用CoMarshalInterThreadInterfaceInStream 跨线程边界编组接口,即使不是绝对必要的。它是never harmful to marshal an interface pointer in this way because COM is smart enough not to marshal (or remarshal) the pointer if marshaling isn't necessary。请记住,这是一次编组 - 一次解组。如果您想从多个线程解组接口,可以将接口放入Global Interface Table

【讨论】:

  • 看来你的答案不正确,看我贴的代码。
  • 我的立场是正确的,请参阅我的测试用例的the updated code。谢谢,+1(我已经撤消了我的 -1)。
  • 这是不正确的。 IStream 返回的实例 CoMarshalInterThreadInterfaceInStream 可以被另一个线程使用,根据文档“The当在接收线程中运行的客户端尝试解组指针时,ppStm 参数中返回的流保证行为正确。”对于所有IStream 接口,不是这种情况,这是对@987654343 存在only 的保证@。保证也不等同于完全线程安全。
  • @Ben,我问的问题是关于CreateStreamOnHGlobal,而不是关于CoMarshalInterThreadInterfaceInStream。难道我不应该依赖CreateStreamOnHGlobal的这种行为吗?
  • @IInspectable,在接受您的回答之前,我想征求更多反馈。我没想到这个问题会引起这么大的争议。
【解决方案2】:

已编辑,根据MSDN

线程安全。 SHCreateMemStream 创建的流是线程安全的 从 Windows 8 开始。在早期系统上,流不是线程安全的。 CreateStreamOnHGlobal 创建的流是线程安全的

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

测试用例显示如下:

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

  • 如果两个线程都是 MTA 线程,则来自 threadIStreamthread2 上解组为完全相同的 IUnknown 指针,没有代理。

  • 如果 thread1 是 STA,而 thread2 是 MTA,则存在代理。但是在thread 上创建的直接引用仍然适用于thread2

请注意如何在来自不同线程的紧密循环内同时对stream1 执行读取和写入。当然,这没什么意义,通常有锁来同步读/写。然而,证明CreateStreamOnHGlobal 返回的IStream 对象是真正的线程安全的。

我不确定这是否是任何 IStream 实现的官方 COM 要求,正如 that Dr. Dobb's article 所建议的那样 - 很可能它只是特定于 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);
    }
}

【讨论】:

  • 我认为您最初的问题不够清楚,因为这里有两个不同的线程安全问题。 IStream 线程是否安全,如“两个线程可以同时使用相同的流并获得可预测的结果?” - 答案是不。 IStream 线程是否安全,就像“在一个线程上创建的流是否可以被另一个线程使用而无需编组” - 答案是肯定的。您最初似乎在问第二个问题,@IInspectable 的回答是正确的。
  • @JonathanPotter,我想我很具体:CreateStreamOnHGlobal 返回的流,不是吗? IStream 的其他实现可以在没有编组的情况下工作。但是,现在我相信我不能在不同的线程中使用由CreateStreamOnHGlobal 返回的直接IStream 接口指针。 看起来它需要编组。
  • @JonathanPotter,我的立场是正确的,因为我已经更新了我的测试用例。尽管有封送代理,直接引用仍然有效。多么有趣。
  • @avo:接口指针不指向代理,而是指向实际的 COM 对象,“似乎”可以从不同的线程中使用。我们最近有这样一个案例,在主线程中创建了一些对象 A,产生 ptrA,而 ptrA 在工作线程中使用。虽然这是不允许的,但没有出现运行时错误。

    然而,当对象 A 开始使用 OutProc 日志服务器时,情况发生了巨大变化。现在工作线程将不再工作,并且返回了 Interface-marshaled-for-wrong-apartment 错误(抱歉,我懒得查找正确的错误消息)。
  • @StuartRedmann,您是否真的将对象 A 设计为线程安全的,使用互斥锁/关键部分/等来保护其状态免受并发访问?这似乎是我所指的IStream 对象的情况。此外,您还使用其他 COM 对象(OutProc 服务器)。虽然 IStream 只使用内存,但我想。
猜你喜欢
  • 1970-01-01
  • 2018-04-13
  • 1970-01-01
  • 2016-11-02
  • 1970-01-01
  • 2010-11-06
  • 1970-01-01
  • 2013-12-14
  • 1970-01-01
相关资源
最近更新 更多