RPC_E_ATTEMPTED_MULTITHREAD (0x80010102): Single-Threaded COM Call on Wrong Thread
You tried to call a COM object across threads when it's marked single-threaded. The fix is to use the same apartment or marshal the interface. I'll show you both.
This error is infuriating—I know
You wrote clean code, debugged for hours, and then this pops up: 0x80010102. The message says it all: you tried to make calls on more than one thread in single-threaded mode. But knowing what it means isn't the same as knowing how to fix it. I've been there. Let's get this sorted.
The Fix: Stay on the Same Apartment
Most COM objects that raise this error live in a Single-Threaded Apartment (STA). If you created the object on Thread A, you can't call it from Thread B without marshaling. The simplest fix—and the one I reach for first—is to always call the object from the thread that created it.
In a UI app (like a Windows Forms app), that's usually the main thread. Use Control.Invoke (C#) or Dispatcher.Invoke (WPF) to route calls back. Here's a C# example:
// Assume comObj was created on the main UI thread
// On a background thread:
this.Invoke((MethodInvoker)delegate {
comObj.SomeMethod();
});In native C++ with Win32, use PostMessage or SendMessage to a window on the same thread. If you're in a console app without a message pump, you'll need to create one with CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) on that thread and run a message loop.
If that's not practical, you need to marshal the interface. Here's how:
Marshaling the Interface (C++)
On the creating thread, call CoMarshalInterThreadInterfaceInStream to get an IStream that represents the interface. Pass that stream to the target thread, and there call CoGetInterfaceAndReleaseStream to unmarshal it. The new thread gets its own proxy that talks back to the original object safely.
// On the creating thread:
IStream* pStream = nullptr;
HRESULT hr = CoMarshalInterThreadInterfaceInStream(
IID_IUnknown, pInterface, &pStream);
// Pass pStream to the other thread
// On the target thread:
IUnknown* pUnmarshaled = nullptr;
HRESULT hr = CoGetInterfaceAndReleaseStream(
pStream, IID_IUnknown, (void**)&pUnmarshaled);This works every time for me. But don't use it if you don't have to—marshaling adds overhead and can leak if you forget to release.
Why This Happens
COM enforces apartment rules to protect objects that aren't thread-safe. An STA object expects all calls to come from the same thread. When you break that rule, COM throws 0x80010102—it's a safety mechanism, not a bug in your logic. The real problem is that you're crossing apartment boundaries without permission.
This error triggers when:
- You create a COM object on one thread (STA), then call it from another thread (MTA or a different STA).
- You're in a multithreaded apartment (MTA) and try to call an STA object directly.
- You initialize COM with
COINIT_MULTITHREADEDon the creating thread, then call from a thread initialized asCOINIT_APARTMENTTHREADED.
The fix I showed above—using Invoke or marshaling—avoids the cross-thread call entirely or makes it safe.
Less Common Variations
Sometimes the error shows up in automation scenarios. For example, you might be using Excel interop or Outlook automation from a background thread. Those COM objects are STA by default. The fix is the same: marshal the Application object from the main thread or use Invoke to touch it.
Another variation: you initialized COM on the thread as MTA (COINIT_MULTITHREADED), but the object itself is STA. In that case, COM creates a hidden STA thread for the object—but calls from your MTA thread still need marshaling. Use CoMarshalInterThreadInterfaceInStream as above, or change your thread's initialization to COINIT_APARTMENTTHREADED if you can.
I've also seen this in Windows Services where the service's main thread is MTA by default. If you create a COM object there and then call it from a worker thread, you hit the error. Solution: use CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) on the worker thread before creating the object, or marshal from the main thread.
Prevention
You can dodge this entirely by designing your code to keep COM objects on one thread. Here's my checklist:
- Know your apartment model. Check the object's documentation or assume STA unless told otherwise. Many UI-bound objects (Internet Explorer, Office, Windows Shell) are STA.
- Initialize COM consistently. On every thread that touches COM, call
CoInitializeExwith the same model. Don't mix STA and MTA on the same object. - Use the main thread for UI objects. In desktop apps, route all calls to STA objects through the UI thread's dispatcher or message loop.
- Test with real concurrency. Simulate background threads during development. This error loves to hide until you're under load.
One more thing: if you're using .NET and the System.Windows.Forms.Timer or BackgroundWorker, remember that their callbacks run on the UI thread. They won't trigger this error—but anything explicit like ThreadPool.QueueUserWorkItem will, unless you marshal.
I hope this saves you the hours it cost me the first time. You've got this.
Was this solution helpful?