views:

62

answers:

4

I am getting following error:

"COM object that has been separated from its underlying RCW cannot be used."

I am sure the problem is because COM object is being called not on the thread it has been created - STA. I tried to implement IDisposable but it has not worked for me.

There is a couple of posts dealing with similar problem but which still do not solve my issue:

http://stackoverflow.com/questions/1573977/is-it-safe-to-call-an-rcw-from-a-finalizer http://stackoverflow.com/questions/2085972/release-excel-object-in-my-destructor

Could anyone post an example/explain how COM object should be correctly accessed from another thread?

Here is minimal code which shows the problem:

using System;
using System.Threading;

namespace Test.ComInterop
{
    public class Program
    {
        MyCom _myCom;

        [STAThread]
        static void Main( string[] args )
        {
            new Program();
        }

        public Program()
        {
            _myCom = new MyCom();

            // this method call works
            string version = _myCom.ComMethod();

            StartThread();
        }

        private void StartThread()
        {
            Thread t = new Thread( UIRun );
            t.SetApartmentState( ApartmentState.STA );
            t.Start();
        }

        void UIRun()
        {
            TestUI window = new TestUI();
            window.Show();

            // this method call fails
            window.Title = _myCom.ComMethod();

            window.Closed += ( sender2, e2 ) 
                => window.Dispatcher.InvokeShutdown();

            System.Windows.Threading.Dispatcher.Run();
        }
    }

    class MyCom
    {
        private dynamic _com;
        public MyCom()
        {
            _com = Activator.CreateInstance(
                Type.GetTypeFromProgID( "Excel.Application" ) );
        }

        public string ComMethod()
        {
            return (string) _com.Version;
        }
    }    
}
+2  A: 

Usually this is because the underlying COM object has been released from its wrapper - this happens when you manually release it via Marshal.Release or the managed wrapper is disposed. Using it on the wrong thread will simply cause any calls to the COM object to actually occur on the thread it was created on - I was stung by this in the past, it has thread affinity for execution.

You don't appear to be disposing of the wrapper, but I'm not sure what the affect of the dynamic variable will be.

Have you tried changing your thread apartment state to MTA?

Adam
I have to use STA because of the UI.What should I do to prevent COM from being disposed?
Mikhail
Ah, Hans has spotted that it is in-fact being disposed beacuse of the flow of execution in your application.
Adam
+4  A: 

The problem is your program's startup thread. It creates the COM object, starts a thread, then exits. As part of the cleanup of that main thread, .NET calls CoUninitialize() and that's the end of the COM object. Getting that error is the expected result.

There's just no point in letting your main startup thread exit like that. Let it do the work now done by your own thread, problem solved.

Hans Passant
+1 good spot. I suspected the underlying object being released but didn't immediately spot that in the code.
Adam
Thank you very much! I copy-pasted UI threading related stuff without understanding it. I do not need starting a new thread.
Mikhail
+1  A: 

Sorry for maybe not answering directly your question. This is merely an advice for handling it in a different way. Hope it helps.

With COM interop with Excel there are quite a few pitfalls - not directly related to COM I think, but to how Excel COM is implemented.

I struggled a lot with COM interop with Excel (and also MsProject). For Excel the only good solution was a dedicated thread for handling the whole Excel communication from creation until termination. There are a few design flaws in the Excel API. Some method calls are not stateless, meaning two threads will have a hard time to make the stuff work. It would be safer to delegate all the communication to one thread and handle the communication with other threads yourself.

Beside this, the thread you are using for communication MUST also have the en/US culture (LCID issues). This usually results in an other message:

Old format or invalid type library

but might be useful to you to know.

jdehaan
+1  A: 

Try making your MyCom class inherit for DispatcherObject. After you start up your other thread, do a _myCom.Dispatcher.Run(). When you want to talk to your COM object, just do a _myCom.Dispatcher.BeginInvoke/Invoke.

Jeremiah Morrill
Thanks, it seems to be a an interesting approach. Can you recommend a link where it would be explained more in-depth?
Mikhail
I don't have a link, but I have done this approach when dealing with STA COM objects that run on a different thread than the UI (STA) thread. The Dispatcher is basically it's own message loop (IIRC, it even processes Win32 messages along with .NET delegates).Basically you just have two Dispatchers running so you can easily marshal between the two apartments.
Jeremiah Morrill