4

我有一个 COM 流对象 ( IStream),用CreateStreamOnHGlobal.

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

EDITED,读/写/搜索与我的代码中的锁正确同步。

4

2 回答 2

3

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

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

更新:

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

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

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

类似的信息可CreateStreamOnHGlobalSHCreateMemStream

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

这些保证通常不适用于所有IStream实现。如果你想安全起见,你总是可以使用 跨线程边界编组接口CoMarshalInterThreadInterfaceInStream,即使不是绝对必要的。以这种方式编组接口指针永远不会有害,因为如果不需要编组,COM 足够聪明,不会编组(或重新编组)指针。请记住,这是一次编组 - 解组一次。如果要从多个线程解组接口,可以将接口放入Global Interface Table

于 2013-11-10T22:23:07.920 回答
1

根据MSDN编辑

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

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

测试用例显示以下内容:

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

  • 如果两个线程都是 MTA 线程,则IStreamfromthread将解组thread2到完全相同的IUnknown指针,没有代理。

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

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

我不确定这是否是任何 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);
    }
}
于 2013-11-10T23:43:49.950 回答