REGDB_E_BADTHREADINGMODEL (0x80040156) — Threading model fix
COM threading model mismatch in the registry. Usually a missing or malformed ThreadingModel value on an InprocServer32 key. Fix by correcting the registry entry.
Cause #1: Missing or wrong ThreadingModel value in the registry
This error shows up when a COM client calls CoInitialize(NULL) (STA mode) and then tries to instantiate an object from a DLL that's registered with a different threading model — or no threading model at all. The registry key responsible lives under HKEY_CLASSES_ROOT\CLSID\{your-clsid}\InprocServer32.
What's actually happening here is: COM checks the ThreadingModel string value of that key when the DLL is loaded. If the value doesn't match the apartment state of the calling thread, COM refuses to create the object and returns 0x80040156. The fix is straightforward — set the correct model.
The fix
- Open regedit and navigate to
HKEY_CLASSES_ROOT\CLSID\{CLSID-of-your-object}\InprocServer32. If you don't know the CLSID, run the failing program and check Event Viewer for the CLSID in the error details, or use Process Monitor to find it. - Look for a string value named
ThreadingModel. If it's missing, create it (type REG_SZ). - Set it to one of these valid values:
Apartment— for STA clients. This is the safest choice. Each object runs on the thread that created it.Free— for MTA clients. Objects can be called from any thread in the process's multithreaded apartment.Both— handles both correctly. Best choice if the DLL supports cross-apartment calls.Neutral— for neutral apartment. Rarely used, mainly for objects that don't interact with the UI.
- If you're dealing with a third-party DLL and you don't control the code, pick
Apartment. Worst case, the object will work but with marshaling overhead. If it still fails, tryBoth. - After editing, re-register the DLL from an admin command prompt:
regsvr32 /u yourdll.dllthenregsvr32 yourdll.dll. The unregister/re-register step forces COM to re-read the registry.
The reason setting it to Apartment often works: most Windows COM objects expect STA, and the calling thread is almost always STA unless you explicitly called CoInitializeEx(NULL, COINIT_MULTITHREADED). If you're writing your own DLL, never leave ThreadingModel empty — COM treats a missing value as "neutral" in older docs, but in practice it behaves like Apartment and can still cause 0x80040156 if the client uses MTA.
Cause #2: CoInitialize called with wrong flags
Sometimes the registry is fine but the calling code initializes COM with the wrong apartment model. If your app calls CoInitialize(NULL) (STA) but the DLL's ThreadingModel is Free or Both, COM will reject the call with 0x80040156. This is less common but still trips people up.
The fix
- Find the line in your source code that calls either
CoInitializeorCoInitializeEx. It's usually in the main thread's entry point or in a library init function. - If it's
CoInitialize(NULL), change it toCoInitializeEx(NULL, COINIT_MULTITHREADED)only if the DLL'sThreadingModelisFreeorBoth. Otherwise leave it as STA. - If your app creates multiple threads that call COM objects, each thread must call
CoInitializeExwith the same model. Mixing STA and MTA in the same process is allowed, but each thread's apartment type must match what the objects it creates expect.
Real-world scenario: I saw this on a Windows 10 IoT device where a background service used CoInitialize(NULL) then tried to load a DirectX component registered with ThreadingModel = Free. The fix wasn't changing the registry — the component was from Microsoft and the model was intentional. We switched the service to CoInitializeEx(NULL, COINIT_MULTITHREADED) and the error vanished.
Cause #3: 32-bit DLL registered under 64-bit system
On 64-bit Windows, COM looks for DLL registrations in two places: the 64-bit view (under HKEY_CLASSES_ROOT\CLSID) and the 32-bit view (under HKEY_CLASSES_ROOT\Wow6432Node\CLSID). If your DLL is 32-bit but registered in the 64-bit location (or vice versa), the InprocServer32 path points to the wrong DLL, and when COM loads it, the threading model may mismatch or the DLL fails to initialize, triggering 0x80040156.
The fix
- Check the
InprocServer32path. If the DLL is 32-bit, the entry must be underWow6432Node\CLSID. If it's 64-bit, it must be under the mainCLSIDnode. - Open regedit. Find the CLSID under both locations. Compare the
InprocServer32default value (the DLL path). - If the path is wrong, delete the incorrect entry and re-register using the correct regsvr32 version. Use
%SystemRoot%\SysWOW64\regsvr32.exefor 32-bit DLLs, and%SystemRoot%\System32\regsvr32.exefor 64-bit. - After re-registration, verify the
ThreadingModelvalue exists under both the 64-bit and 32-bit keys (if the DLL is registered for both architectures).
The nuance: Some installers register a DLL once, but if the CLSID exists under both nodes (e.g., a COM component that ships both architectures), the installer might set ThreadingModel only on one. Then a 32-bit client finds the 32-bit key with a missing ThreadingModel and boom — 0x80040156. The fix is to copy the ThreadingModel value from the working node to the broken one.
Quick-reference summary table
| Cause | Diagnosis | Fix | Tools |
|---|---|---|---|
| Missing/wrong ThreadingModel | Check InprocServer32 key — value absent or invalid | Set to Apartment, Free, Both, or Neutral | regedit, regsvr32 |
| CoInitialize flag mismatch | App uses STA but DLL registered as Free | Change CoInitialize to CoInitializeEx with matching flag | Source code, dependency walker |
| 32/64-bit registration mismatch | DLL path under wrong CLSID node | Re-register with correct regsvr32 version | regedit, SysWOW64 regsvr32 |
If none of these fix it, run Process Monitor filtered on RegQueryValue for ThreadingModel. Watch which key COM actually reads — sometimes it's from a user-specific hive (HKCU\Software\Classes\CLSID) that overrides HKCR. Delete or fix that override. That's rare but it happens.
Was this solution helpful?