none
c# Using the GlobalInterfaceTable to marshall a pointer to a COM object across threads RRS feed

  • Question

  • I am building a c# application which makes use of third party COM objects which have an ApartmentState of 'Both'.

    One of the objects I need to use on both the UI thread and another thread which is created as a new thread. I create the object on the UI thread and then pass it as a parameter to the other thread using  ParameterizedThreadStart but I then find that I am unable to access the object in the new thread. I have also tried creating the new thread by passing a parameter which is the Windows Control on the UI and then using Control.Invoke to get the COM object from a function on the UI control that returns the COM object, but I still cannot use the COM object on the new thread. I have tried changing the code round a bit and sometimes I get a Corrupted Memory exception and other times an error stating that "COM object that has been separated from its underlying RCW cannot be used.." . 

    I have tried with the new thread as STA and MTA but neither work. I have also set the UI to MTA ( which I don't want to do because of problems with drag & drop etc ) and with the new thread also set as MTA it will work but very slowly - about 30 seconds to do something that should be done in a split second.

    I've read alot of articles about this problem but not found a real answer. There seems to be some doubt as to whether the runtime should handle this automatically or whether it is necessary to use the GlobalInterfaceTable to pass the necessary pointers.  If it is necessary to use the GIT then how do you do it please, as I cannot find anything about how you use it.

    Many Thanks.





    Monday, January 11, 2010 12:24 PM

All replies

  • Hello Rod,

    >> One of the objects I need to use on both the UI thread and another thread which is created as a new thread. I create the object on the UI thread and then pass it as a parameter to the other thread using  ParameterizedThreadStart but I then find that I am unable to access the object in the new thread. I have also tried creating the new thread by passing a parameter which is the Windows Control on the UI and then using Control.Invoke to get the COM object from a function on the UI control that returns the COM object, but I still cannot use the COM object on the new thread. I have tried changing the code round a bit and sometimes I get a Corrupted Memory exception and other times an error stating that "COM object that has been separated from its underlying RCW cannot be used.." . 

     

    1 In order to successfully marshal a COM interface from one thread to another (be it an unmanaged application or a managed one) one of the following 2 points must hold :

    1.1 The proxy/stub DLL of the interface is registered.

    - This applies to IUnknown-based interfaces. In this situation, the proxy/stub DLL is used during the marshaling process.

    1.2 The Type Library which contains the definition of the interface is registered.

    - This applies to interfaces where all methods and properties use OLE automation-compatible parameters and the interface has been marked with the IDL oleautomation attribute. This will cause the marshaling to be performed by the standard OLE Automation Marshaler.

     

     

    2. Note that the term "marshaling" used in the above points refers to COM marshaling and not Interop Marshaling. COM marshaling applies wherever COM objects are used whether in unmanaged code or managed code.

    2.1 In an unmanaged language, particularly C++, the marshaling process must be performed by hand by the developer (via calls to the CoMarshalInterface(), CoUnmarshalInterface() APIs or via the Global Interface Table).

    2.2 In managed language, e.g. C#, the marshaling is done transparently and no explicit coding is required to indicate marshaling. Hence the use of ParameterizedThreadStart to pass the object from one thread to another is perfectly fine as long as the proxy/stub DLL or the type library has been registered.

    2.3 Note further that all managed code eventually reduces to native code and so managed threads eventually execute in native threads. Hence all the apartment and marshaling rules of COM applies to managed code.

     

     

    3. >> I have tried with the new thread as STA and MTA but neither work. I have also set the UI to MTA ( which I don't want to do because of problems with drag & drop etc ) and with the new thread also set as MTA it will work but very slowly - about 30 seconds to do something that should be done in a split second.

    3.1 Noting that the 3rd party COM object is of apartment type "Both", pls note the following :

    3.2 The object will live in the same apartment as the thread which first instantiates it. Since your UI thread is an STA thread, the object will live in this apartment. The second thread, even if marked as STA, is a separate apartment, hence COM marshaling is involved and this has not been successful so far.

    3.3 After marking both the UI thread and the second thread as MTA, then the COM object need not be marshaled from the UI thread to the second thread and so the COM object appears to be usable from the second thread albeit the mysterious poor performance (which may be a problem unrelated to marshaling).

     

     

    4. Hence check to see if the type library (which contains the definition of the object's interface) has been registered properly. If the interface happens to be IUnknown-based, be sure to create the proxy/stub DLL for the interface and register it.

     

    - Bio.

     

     

     

    Saturday, January 1, 2011 6:10 AM
  • Hello Rod,

    1. >> I've read alot of articles about this problem but not found a real answer. There seems to be some doubt as to whether the runtime should handle this automatically or whether it is necessary to use the GlobalInterfaceTable to pass the necessary pointers.  If it is necessary to use the GIT then how do you do it please, as I cannot find anything about how you use it.

    1.1 As mentioned in a previous post, it is not necessary to use the GIT to marshal COM interfaces from one thread to another.
    1.2 However, it is possible to use the GIT to share COM interfaces pointers between managed threads. The sections below demonstrates this.
    2. For the demonstration, I have created the following :
    2.1 A COM interface named IRodMatInterface01.
    2.2 An implementation of this interface with progID "RodMatImpl01.RodMatITF01Impl".
    2.3 A simple C# form-based application with a form named "FormMain" and a thread function named "Thread1".
    2.4 There is also a button on FormMain named "buttonTest".
    3. The code below demonstrates this :
    3.1 First define the IGlobalInterfaceTable interface in the managed source codes, e.g. C# :
        [
            ComImport(),
            InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
            Guid("00000146-0000-0000-C000-000000000046")
        ]
        interface IGlobalInterfaceTable
        {
            int RegisterInterfaceInGlobal(
            [MarshalAs(UnmanagedType.IUnknown)] object pUnk,
            [In()] ref Guid riid);
            void RevokeInterfaceFromGlobal(
            int dwCookie);
            [return: MarshalAs(UnmanagedType.IUnknown)]
            object GetInterfaceFromGlobal(
            int dwCookie,
            [In()] ref Guid riid);
        }
    3.2 The FormMain class definition is given below :
        public partial class FormMain : Form
        {
            public FormMain()
            {
                m_iIRodMatGITCookie = 0;
                InitializeComponent();
            }
            private void FormMain_Load(object sender, EventArgs e)
            {
                Object newObj = Activator.CreateInstance(Type.GetTypeFromProgID("RodMatImpl01.RodMatITF01Impl"));
                IRodMatInterface01 IRodMat = (IRodMatInterface01)newObj;
                Guid CLSID_StdGlobalInterfaceTable = new Guid("00000323-0000-0000-c000-000000000046");
                Object StdGIT = Activator.CreateInstance(Type.GetTypeFromCLSID(CLSID_StdGlobalInterfaceTable));
                IGlobalInterfaceTable IGIT = (IGlobalInterfaceTable)StdGIT;
                Guid guid_IRodMatInterface01 = typeof(IRodMatInterface01).GUID;
                m_iIRodMatGITCookie = IGIT.RegisterInterfaceInGlobal(IRodMat, ref guid_IRodMatInterface01);
                Marshal.ReleaseComObject(IGIT);
            }
            private void buttonTest_Click(object sender, EventArgs e)
            {
                Thread t1 = new Thread(Threads.Thread1);
                t1.ApartmentState = ApartmentState.STA;
                t1.Start(m_iIRodMatGITCookie);
            }
            private int m_iIRodMatGITCookie;
        }
    3.2.1 FormMain::m_iIRodMatGITCookie is used to contain the GIT cookie of the IRodMatInterface01 interface. It is initialized to 0 in the constructor.
    3.2.2 In FormMain_Load(), an instance of the COM object with progID "RodMatImpl01.RodMatITF01Impl" is created and then a reference to its IRodMatInterface01 interface is obtained.
    3.2.3 Next, an instance of the Global Interface Table is created via its class ID :
                Guid CLSID_StdGlobalInterfaceTable = new Guid("00000323-0000-0000-c000-000000000046");
                Object StdGIT = Activator.CreateInstance(Type.GetTypeFromCLSID(CLSID_StdGlobalInterfaceTable));
                IGlobalInterfaceTable IGIT = (IGlobalInterfaceTable)StdGIT;
    3.2.4 Then we register the IRodMatInterface01 interface to the GIT and obtain the cookie :
                Guid guid_IRodMatInterface01 = typeof(IRodMatInterface01).GUID;
                m_iIRodMatGITCookie = IGIT.RegisterInterfaceInGlobal(IRodMat, ref guid_IRodMatInterface01);
                Marshal.ReleaseComObject(IGIT);
    3.2.5 The call to release the reference to the GIT interface is v important :
                Marshal.ReleaseComObject(IGIT);
    This is an anecdotal advise : whenever a reference to the GIT is first acquired in an STA thread (the UI thread is an STA thread), then Marshal.ReleaseComObject() must be called to release it before another thread tries to acquire it. You can verify this by commenting out the call to Marshal.ReleaseComObject() and see what happens later.
    3.2.6 The call to buttonTest_Click() will launch the Thread1() function. The GIT cookie m_iIRodMatGITCookie is passed as a parameter to the thread.
    3.3 The Thread1() function is listed below :
        class Threads
        {
            [STAThread]
            public static void Thread1(object data)
            {
                Guid CLSID_StdGlobalInterfaceTable = new Guid("00000323-0000-0000-c000-000000000046");
                Object StdGIT = Activator.CreateInstance(Type.GetTypeFromCLSID(CLSID_StdGlobalInterfaceTable));
                IGlobalInterfaceTable IGIT = (IGlobalInterfaceTable)StdGIT;
                int iIRodMatGITCookie = (int)data;
                Guid guid_IRodMatInterface01 = typeof(IRodMatInterface01).GUID;
                Object obj_IRodMat = null;
                obj_IRodMat = IGIT.GetInterfaceFromGlobal(iIRodMatGITCookie, ref guid_IRodMatInterface01);
                Marshal.ReleaseComObject(IGIT);
                IRodMatInterface01 IRodMat = (IRodMatInterface01)obj_IRodMat;
                // make method calls using IRodMat.
                ...
                ...
                ...
            }
        }
    3.3.1 A reference to the GIT is acquired in the same way as it was done in FormMain_Load().
    3.3.2 Thread1() then transforms the "data" parameter into the GIT cookie and then uses the cookie to obtain the IRodMatInterface01 interface from the COM object which was stored in the GIT.
    3.3.3 Method calls may now be performed using the IRodMatInterface01 interface reference obtained from the GIT.
    4. As mentioned, it is not necessary to use the GIT to share COM interface references in managed code, but it is possible. Also, please note that it is best to quickly call Marshal.ReleaseComObject() on the GIT reference as soon as it is no longer needed.

    - Bio.
    Sunday, January 2, 2011 11:33 AM