简体   繁体   English

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

[英]Do I need to marshal IStream returned by CreateStreamOnHGlobal, for use across threads?

I have a COM stream object ( IStream ), created with CreateStreamOnHGlobal . 我有一个COM流对象( IStream ),使用CreateStreamOnHGlobal创建。

I want to use it across different threads within the same process. 我想在同一个进程中的不同线程中使用它。 Do I need to marshal the stream object itself (with CoMarshalInterface etc)? 我是否需要编组流对象本身(使用CoMarshalInterface等)? Or is it thread-safe already? 或者它已经是线程安全的吗?

EDITED , reads/writes/seeks are properly synchronized with locks in my codes. EDITED ,读/写/搜索与我的代码中的锁正确同步。

COM treats IStream as a special type of interface, which can be safely used across threads. COM将IStream视为一种特殊类型的接口,可以跨线程安全地使用。 This is necessary so that other interfaces can be marshaled across thread boundaries in an IStream using CoMarshalInterThreadInterfaceInStream . 这是必要的,以便可以使用CoMarshalInterThreadInterfaceInStreamIStream线程边界封送其他接口。

Additional information can be found in a 2003 article of Dr. Dobb's: Marshaling COM interfaces . 更多信息可以在2003年Dobb博士的文章中找到: Marshaling COM接口

Update: 更新:

The answer, as initially posted, is not entirely correct. 最初发布的答案并不完全正确。 The OLE-provided implementation of the IStream interface, as returned by CreateStreamOnHGlobal and indirectly created through CoMarshalInterThreadInterfaceInStream can be safely accessed across threads in the same process. CreateStreamOnHGlobal返回并通过CoMarshalInterThreadInterfaceInStream间接创建的OLE提供的IStream接口实现可以在同一进程中的线程之间安全地访问。

Documentation is scattered and hard to come by. 文档分散且难以获得。 CoMarshalInterThreadInterfaceInStream states the following: CoMarshalInterThreadInterfaceInStream指出以下内容:

The stream returned in the ppStm parameter is guaranteed to behave correctly when a client running in the receiving thread attempts to unmarshal the pointer. 当在接收线程中运行的客户端尝试解组指针时,ppStm参数中返回的流保证正常运行。

Similar information is available for CreateStreamOnHGlobal from SHCreateMemStream : 来自SHCreateMemStream CreateStreamOnHGlobal也有类似的信息:

The stream created by CreateStreamOnHGlobal is thread-safe. CreateStreamOnHGlobal创建的流是线程安全的。

The guarantees do not generally hold true for all IStream implementations. 对于所有IStream实现,保证通常不适用。 If you want to play it safe, you can always marshal interfaces across thread boundaries using CoMarshalInterThreadInterfaceInStream , even if not strictly necessary. 如果您想要安全地使用它,您总是可以使用CoMarshalInterThreadInterfaceInStream跨越线程边界编组接口,即使不是绝对必要的。 It is 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 . 以这种方式编组接口指针永远不会有害,因为如果不需要编组,COM足够聪明,不会编组(或重新编组)指针 Keep in mind though that this is marshal once - unmarshal once . 请记住,这只是一次元帅 - 一次解散 If you want to unmarshal the interface from multiple threads you can place the interface into the Global Interface Table . 如果要从多个线程解组接口,可以将接口放入全局接口表

EDITED , according to MSDN : 根据MSDN 编辑

Thread safety. 线程安全。 The stream created by SHCreateMemStream is thread-safe as of Windows 8. On earlier systems, the stream is not thread-safe. 从Windows 8开始,SHCreateMemStream创建的流是线程安全的。在早期的系统上,该流不是线程安全的。 The stream created by CreateStreamOnHGlobal is thread-safe . CreateStreamOnHGlobal创建的流是线程安全的

I've got two opposite answers, so I decided to verify it. 我有两个相反的答案,所以我决定验证它。 It looks like @HansPassant is right, and @IInspectable is wrong. 看起来@HansPassant是对的,而且@IInspectable是错的。 At least, a proxy is created on another thread for the original IStream object. 至少,在原始 IStream对象的另一个线程上创建代理。

The test case shows the following: 测试用例显示以下内容:

  • Even if both threads belong to different apartments, a direct reference to IStream can still be used across threads. 即使两个线程都属于不同的公寓,也可以跨线程使用对IStream的直接引用。 It just works. 它只是有效。

  • If both threads are MTA threads, the IStream from thread gets unmarshaled on thread2 to exactly the same IUnknown pointer, no proxy. 如果两个线程都是MTA线程,则来自threadIStreamthread2上被解thread2为完全相同的IUnknown指针,没有代理。

  • If thread1 is STA, and the thread2 is MTA, there is a proxy. 如果thread1是STA,而thread2是MTA,则有一个代理。 But the direct reference, created on thread , still works on thread2 . 但是在thread上创建的直接引用仍然适用于thread2

Note how reads and writes are performed on stream1 concurrently, inside tight loops from different threads. 注意如何在不同线程的紧密循环内同时对stream1执行读写操作。 Of course, it makes very little sense, normally there are locks to synchronize reads/writes. 当然,它没什么意义,通常有锁来同步读/写。 Yet, it proves that IStream object as returned by CreateStreamOnHGlobal is truly thread-safe. 然而, 它证明了CreateStreamOnHGlobal返回的IStream对象是真正的线程安全的。

I am not sure if that is an official COM requirement to any IStream implementation, as that Dr. Dobb's article suggests - most likely it is just specific to 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);
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM