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

时间:2013-11-10 21:48:50

标签: windows multithreading winapi com marshalling

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

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

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

2 个答案:

答案 0 :(得分:3)

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

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

<强>更新

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

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

  

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

来自SHCreateMemStreamCreateStreamOnHGlobal提供了类似的信息:

  

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。请记住,这只是 marshal一次 - unmarshal once 。如果要从多个线程解组接口,可以将接口放入Global Interface Table

答案 1 :(得分:1)

已编辑,根据MSDN

  

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

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

测试用例显示以下内容:

  • 即使两个线程都属于不同的公寓,也可以跨线程直接引用IStream。它只是有效。

  • 如果两个线程都是MTA线程,则IStream中的thread会在thread2上被解组为完全相同的IUnknown指针,而不是代理。

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

注意在stream1内同时执行读写操作,来自不同线程的紧密循环内部。当然,它没什么意义,通常有锁来同步读/写。然而,证明IStream返回的CreateStreamOnHGlobal对象确实是线程安全的。

我不确定这是否是任何 IStream实施的正式COM要求,正如that Dr. Dobb's article所暗示的那样 - 最有可能只是{{} 3}}

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