Registrierungs- und Bitness-Fallen bei der COM/OCX/ActiveX-Entwicklung

· · COM, ActiveX, OCX, Visual Studio, Windows-Entwicklung, 32bit, 64bit, Interop

Bei COM-Komponenten sowie OCX-/ActiveX-Projekten liegen die Stolpersteine eher an den Grenzen von Laufzeitumgebung, Registrierung, Host und Berechtigungen als im Code selbst.

Typisch sind Symptome wie diese:

  • Der Build läuft durch, aber beim Start kommt 0x80040154.
  • Auf der eigenen Entwicklungsmaschine funktioniert es, auf anderen PCs nicht.
  • Zur Laufzeit funktioniert es, aber ausgerechnet der Designer von Visual Studio stirbt.
  • Als Administrator gestartet funktioniert es, mit normalen Rechten bricht es.
  • regsvr32 wird ausgeführt, aber irgendwie ist das Problem nicht behoben.

Das sind meist weniger einzelne Bugs als ein Zustand, in dem eine Grundannahme von COM irgendwo nicht mehr zutrifft.

Wer zuerst die Begriffe COM / ActiveX / OCX selbst einordnen möchte, bekommt mit „Was sind COM / ActiveX / OCX? – Die Unterschiede und Zusammenhänge erklärt“ leicht den Gesamtüberblick. Dieser Artikel behandelt die nächste Stufe: wo man in der Praxis tatsächlich hängen bleibt, einschließlich der Bitness von Visual Studio und der Themen rund um Administratorrechte.

1. Zuerst das Fazit

Vorab, in der für die Praxis relevanten Kurzform:

  1. Probleme mit COM / OCX / ActiveX entstehen häufiger durch eine Unstimmigkeit bei Bitness (32-Bit / 64-Bit), Registrierungsort, Host und Berechtigungen als durch fehlerhafte Codelogik.
  2. Visual Studio 2022 ist ein 64-Bit-Prozess, weshalb die früher auf 32-Bit ausgelegte Entwurfszeit-Integration so, wie sie war, nicht mehr funktioniert.12
  3. regsvr32 ist kein Zauberbefehl, der alles registriert. Es ist für native In-Proc-COM-Server (DLL / OCX) gedacht. Um eine .NET-Framework-Assembly für COM offenzulegen, nutzt man Regasm.exe; unter .NET 5+ / .NET 6+ / .NET 8+ wird die erzeugte .comhost.dll registriert.3456
  4. „Funktioniert es als Administrator, ist es in Ordnung“ ist gefährlich. Nicht selten ist die Komponente nur zufällig durch eine per-user-Registrierung sichtbar, oder eine eigentlich vom Installer zu erledigende Registrierung wurde nur auf dem Entwicklungsrechner von Hand nachgeholt.378

Bei COM / OCX / ActiveX ist es also sicherer, zunächst diese vier Achsen zu betrachten:

  • Welcher Prozess hostet die Komponente
  • Ist dieser Prozess 32-Bit oder 64-Bit
  • Wo ist sie registriert (HKCU / HKLM, 32-Bit-View / 64-Bit-View)
  • Erfordert der jeweilige Vorgang oder die Ausführung Administratorrechte

2. Rückwärts von den Symptomen betrachtet sieht es meist so aus

Symptom Zuerst zu vermuten Häufige tatsächliche Ursache
0x80040154 Class not registered Nicht registriert Tatsächlich häufig „nur auf der anderen Bitness-Seite registriert“ oder „nur für diesen einen Benutzer registriert“
DllRegisterServer failed: 0x80070005 Fehlende Rechte Registrierung als Standardbenutzer versucht, oder im Post-Build-Schritt ohne Erhöhung registriert
In VS2022 bricht nur der Designer Einschränkung des Designers Die 64-Bit-Seite von Visual Studio kann 32-Bit-COM/ActiveX nicht direkt laden
Funktioniert nur als Administrator gestartet Berechtigungsproblem Der eigentliche Kern liegt meist nicht in den Rechten selbst, sondern in einer Abweichung von Registrierungsumfang oder Installationsdesign
Eine 64-Bit-Anwendung kann kein 32-Bit-OCX aufrufen Einschränkung der COM-Funktionsweise Ein In-Proc-Server lässt sich nur in einen Host derselben Bitness laden
Funktioniert im UI-Thread, friert im Hintergrund-Thread ein Threading-Modell Verletzte Annahmen zu STA / MTA, CoInitializeEx und der Nachrichtenschleife

Offiziell ist 0x80040154 gleich REGDB_E_CLASSNOTREG, wörtlich Class not registered.9 Und der 0x80070005-Fehler von regsvr32 wird offiziell als der Fall beschrieben, in dem mangels Administratorrechten nicht in die Registry oder nach System32 geschrieben werden kann.10

Wichtig ist hier, dem Fehlernamen nicht blind zu vertrauen. So bedeutet Class not registered beispielsweise nicht zwangsläufig „vollständig unregistriert“ – es tritt auch dann auf, wenn die Registrierung in einer anderen Registry-View liegt oder nur an einem für diesen einen Benutzer sichtbaren Ort.117

3. Stolpersteine bei der Visual-Studio-Bitness

3.1 Visual Studio 2022 ist 64-Bit geworden

Das ist heute der Punkt, an dem man bei der COM-/ActiveX-Entwicklung am häufigsten hängen bleibt.

Bei Visual Studio 2022 ist devenv.exe ausschließlich 64-Bit.1 Deshalb kann Visual Studio in der WinForms-Entwurfszeit-Umgebung 32-Bit-Komponenten nicht direkt laden. Auch Microsoft hält ausdrücklich fest, dass Visual Studio 2022 als 64-Bit-Prozess keine 32-Bit-.NET-/COM-/ActiveX-Komponenten laden kann.2

Mit anderen Worten: Was früher unter diesen Annahmen irgendwie funktionierte,

  • das Projekt ist x86
  • das referenzierte ActiveX ist ebenfalls x86
  • Visual Studio selbst ist 32-Bit

verdreht sich unter VS2022 zu:

  • die Anwendung läuft zur Laufzeit weiterhin als x86
  • aber der Designer läuft auf der 64-Bit-Seite von Visual Studio

Das Ergebnis ist ein Zustand, bei dem sich die Ursache schwer erkennen lässt: die Laufzeit lebt, aber ausgerechnet der Designer stürzt ab. Der Host zur Laufzeit ist der Prozess der Anwendung, der Host zur Entwurfszeit der Prozess von Visual Studio – weil die Hosts unterschiedlich sind, unterscheidet sich auch die Bitness.2

3.2 AnyCPU löst das Problem nicht zwangsläufig

Auch das ist ein verbreitetes Missverständnis.

AnyCPU ist keine Magie, die auch die Abhängigkeiten neutral macht. Auch Microsofts eigene Erklärung besagt, dass eine Komponente, die wie AnyCPU aussieht, bei der Entwurfszeit von Visual Studio 2022 trotzdem zum Problem wird, wenn sie tiefer in der Kette auf fest an 32-Bit gebundenes COM/ActiveX verweist.2

Verschwindet der Fehler also nicht, wenn Sie auf AnyCPU umstellen, lohnt sich eher der Verdacht auf:

  • eine 32-Bit-native Abhängigkeit tiefer in dieser Assembly
  • ob das ActiveX/OCX fest an x86 gebunden ist
  • Code, der nur zur Entwurfszeit geladen wird

3.3 Die Falle von System32 und SysWOW64

Unter Windows x64 weichen Name und tatsächliches Verhalten voneinander ab, was leicht zu Verwirrung führt.

Microsoft Learn erklärt, dass %windir%\System32 auf x64-Windows für 64-Bit-Anwendungen gedacht ist. Die 32-Bit-Seite wird vom WOW64-Dateisystem-Umleiter an einen anderen Ort geleitet.12 Bei der Registry ist es ebenso: Der WOW64-Registrierungsumleiter zeigt 32-Bit und 64-Bit jeweils eine eigene logische View.11

Deshalb ist es bei der lokalen Fehlersuche sicherer, ausdrücklich anzugeben, welche Bitness von regsvr32 man verwendet.

# 64-Bit-DLL / -OCX registrieren
C:\Windows\System32\regsvr32.exe vendor.ocx

# 32-Bit-DLL / -OCX auf x64-Windows registrieren
C:\Windows\SysWOW64\regsvr32.exe vendor.ocx

Registriert man mit der falschen Seite von regsvr32, wird es unangenehm. Die Registrierung selbst gelingt, ist aber vom Zielprozess aus nicht sichtbar. Das Ergebnis:

  • regsvr32 meldet Erfolg
  • die Anwendung liefert trotzdem 0x80040154
  • in der Registry sieht es so aus, als sei der Eintrag vorhanden
  • tatsächlich betrachtet man aber die andere View

1113

3.4 regsvr32 registriert nicht einfach alles

Auch das ist ein Punkt mit vielen Missverständnissen.

Native In-Proc-COM-Server unterstützen die Selbstregistrierung normalerweise, indem sie DllRegisterServer / DllUnregisterServer exportieren.3 regsvr32 ist das Werkzeug für genau solche DLLs/OCXs.4

Soll dagegen eine .NET-Framework-Assembly von COM aus genutzt werden, ist im Regelfall Regasm.exe zuständig. Auch Microsoft Learn erklärt, dass zur Registrierung einer für COM genutzten Assembly Regasm.exe verwendet wird.514

Bei der COM-Offenlegung unter .NET 5+ / .NET 6+ / .NET 8+ ändert sich die Sache zudem etwas: Setzt man beim Build <EnableComHosting>true</EnableComHosting>, entsteht eine *.comhost.dll, die dann mit regsvr32 registriert wird.6

Grob zusammengefasst sieht es also so aus:

Was offengelegt werden soll Typisches Registrierungsmittel
Native C++-DLL / -OCX regsvr32
.NET-Framework-Assembly, COM-offengelegt Regasm.exe
COM-Offenlegung unter .NET 5+ / 6+ / 8+ Die erzeugte .comhost.dll mit regsvr32

Regasm.exe wird aus der Developer Command Prompt / Developer PowerShell heraus ausgeführt. Die drei gebräuchlichsten Varianten sind:14

:: 1) Die öffentlichen Klassen der Assembly registrieren
regasm myTest.dll

:: 2) Zusätzlich eine Typbibliothek erzeugen und registrieren (für VBA / VB6 u. Ä., die eine TLB brauchen)
regasm myTest.dll /tlb:myTest.tlb

:: 3) Die Registry nicht direkt ändern, sondern den Inhalt zur Kontrolle in eine .reg-Datei schreiben
regasm myTest.dll /regfile:myTest.reg

Das Entfernen erfolgt mit /unregister. Eine mit /tlb registrierte Typbibliothek wird entfernt, indem /tlb und /unregister gemeinsam angegeben werden.

regasm myTest.dll /tlb:myTest.tlb /unregister

Hier lauern zwei leicht übersehene Einschränkungen.14

  • Assemblys, die nicht in den GAC kommen, benötigen /codebase. /codebase schreibt den Dateipfad zum Zeitpunkt der Registrierung in die Registry, weshalb ein späteres Verschieben der Assembly den Start scheitern lässt. Zudem empfiehlt Microsoft nachdrücklich, Assemblys, die /codebase erhalten, mit einem starken Namen (strong name) zu versehen.
  • /regfile lässt sich nicht mit /unregister oder /tlb kombinieren. Zudem schreibt /regfile nur die Einträge der verwalteten Klassen; TypeLibID und InterfaceID werden nicht ausgegeben. Wer denkt, „eine verteilte .reg-Datei erledigt die Registrierung“, kommt damit nicht aus.

Werden diese Unterschiede vermischt, entsteht häufig folgendes Muster:

  • regsvr32 wird auf eine verwaltete DLL angesetzt
  • naturgemäß wird DllRegisterServer nicht gefunden
  • daraus wird fälschlich geschlossen, „die DLL ist beschädigt“

In der Welt, in der Typbibliotheken gebraucht werden (VBA / VB6 / manche Early-Binding-Gegenstellen), wird zusätzlich die Erzeugung und Registrierung der TLB zu einem eigenen Thema. Im .NET Framework lässt sich mit Regasm.exe /tlb eine Typbibliothek erzeugen und registrieren, und auch Microsoft erklärt, dass „die Registrierung von Typen“ und „die Registrierung der Typbibliothek“ getrennte Vorgänge sind.15

4. Stolpersteine bei Administratorrechten

4.1 Registrierung im Post-Build-Schritt, die nur als Administrator gelingt

Im C++-Build von Visual Studio lässt sich regsvr32.exe ganz normal aus einem Build-Event oder einem Custom Build Step heraus aufrufen. Auch Microsoft Learn führt die Registrierung mit regsvr32.exe als Beispiel für ein Post-Build-Event auf.16

Allerdings sind was möglich ist und was sicher ist zweierlei.

Führt ein Standardbenutzer regsvr32 aus, kann es wegen fehlenden Schreibzugriffs auf die Registry oder System32 zu 0x80070005 kommen. Auch die Microsoft-KB ordnet die Ursache dafür als fehlende Administratorrechte ein.10

Hier passiert leicht Folgendes:

  • Visual Studio wird mit normalen Rechten gestartet, der Build läuft durch
  • aber nur die Registrierung im Post-Build-Schritt schlägt fehl
  • das Fehlschlagen im Log wird übersehen
  • eine ältere, noch vorhandene Registrierung bleibt bestehen, sodass es lokal zufällig funktioniert
  • in einer sauberen Umgebung funktioniert es natürlich nicht

Bei dieser Art von Projekt ist es grundlegend, Build und Registrierung zu trennen.

  • Der Build erzeugt nur die Binärdateien
  • Die Registrierung erfolgt über einen expliziten Install-Schritt / ein Skript / einen Installer
  • In der CI wird der „Schritt, der eine Registrierung erfordert“ in einen eigenen Job vom Build getrennt

Allein diese Trennung verhindert eine ganze Reihe von Zwischenfällen.

4.2 Werden per-user- und per-machine-Registrierung vermischt, geht es kaputt

Wer sich nur merkt, „COM schaut in HKCR nach“, bleibt an dieser Stelle hängen.

Tatsächlich schaut COM, wie Microsoft Learn beschreibt, zuerst in HKEY_CURRENT_USER\Software\Classes nach und wertet erst danach die maschinenweiten Informationen aus.3 Zudem ist HKEY_CLASSES_ROOT eine merged view aus HKLM\Software\Classes und HKCU\Software\Classes.717

Mit anderen Worten, es kommt ganz normal zu Situationen wie dieser:

  • Entwickler A hat unter seinem Benutzerkonto manuell registriert
  • unter As Konto funktioniert es
  • bei Entwickler B funktioniert es nicht
  • auch unter dem Dienstkonto funktioniert es nicht
  • wird es als Administrator ausgeführt, ändert sich das Verhalten

Darüber hinaus vertritt Microsoft die Position, dass Anwendungen, die Administratorrechte benötigen, ihre abhängigen COM-Objekte bei der Installation in den maschinenweiten COM-Konfigurationsspeicher registrieren sollten.87

Deshalb sollte man in der Entwicklung diese Unterscheidung klar treffen:

  • Handelt es sich um eine Registrierung nur für die Entwicklung, die ausschließlich der eigene Benutzer nutzt?
  • Handelt es sich um die Produktionsregistrierung, die alle Benutzer dieser Maschine nutzen?
  • Handelt es sich um eine Registrierung, auf die Dienste oder erhöhte Anwendungen zugreifen?

Bleibt das unklar, entstehen Geschichten wie „warum funktioniert es nur als Administrator“ oder „in der Explorer-Erweiterung funktioniert es, im Dienst aber nicht“.

4.3 Visual Studio dauerhaft „als Administrator ausführen“ zu lassen, ist keine Lösung

Es gibt tatsächlich Situationen, in denen das nötig ist. Läuft Visual Studio jedoch dauerhaft erhöht, kann das Probleme verdecken, die eigentlich durch einen Installer oder ein Registrierungsskript gelöst werden sollten, indem die IDE-Erhöhung sie überdeckt.

Zudem behandelt Visual Studio selbst per-user-Erweiterungen bei Ausführung mit Erhöhung anders. Auch Microsoft Learn beschreibt eine Einstellung, nach der beim erhöhten Ausführen von Visual Studio per-user-Erweiterungen deaktiviert werden.18

Als Vorgehen empfiehlt sich daher:

  • die alltägliche Entwicklung mit normalen Rechten
  • nur die Schritte, die eine Registrierung benötigen, explizit über eine erhöhte Developer Command Prompt / PowerShell / einen Installer ausführen
  • lässt sich etwas „nur als Administrator reproduzieren“, diese Annahme selbst als Spezifikation festhalten und einordnen

So erspart man sich später Ärger.

5. Stolpersteine, die spezifisch für ActiveX / OCX sind

5.1 Entwurfszeit- und Laufzeitlizenz sind getrennt

Ein unauffällig lästiger Punkt bei OCX / ActiveX ist die Lizenzierung.

Besonders bei älteren ActiveX-Steuerelementen können Design-Time-Lizenz und Run-Time-Lizenz getrennt sein. Auch die MFC-ActiveX-Dokumentation beschreibt den Mechanismus, Entwurfszeit und Laufzeit über Lizenzdateien und Lizenzschlüssel zu trennen.1920

In dieser Welt kommt es zu Dingen wie:

  • zur Laufzeit lässt es sich verwenden
  • versucht man es aber auf ein Formular zu ziehen, heißt es „keine Lizenz“
  • auf Entwicklungsrechner A lässt es sich platzieren
  • auf Entwicklungsrechner B nicht

Fehler wie „Lizenzinformationen für diese Komponente wurden nicht gefunden“ oder „keine geeignete Lizenz vorhanden“ sind bei altem ActiveX nicht selten.21

5.2 Sobald es auf WinForms liegt, ist bereits ein Wrapper im Spiel

Nutzt man ActiveX aus WinForms heraus, hostet Windows Forms das ActiveX-Element nicht direkt. Wie die Microsoft-Learn-Dokumentation zu Aximp.exe beschreibt, erzeugt der ActiveX Control Importer aus der COM-Typbibliothek einen Wrapper für WinForms und behandelt ihn als AxHost-basiertes Steuerelement.2223

Das Problem liegt also nicht in einer, sondern in mehreren Schichten:

  1. das ursprüngliche OCX-/ActiveX-Element selbst
  2. die Typbibliothek
  3. der erzeugte Interop-/Wrapper-Code
  4. der WinForms-Designer / die Laufzeit

Dadurch kommt es zu Dingen wie:

  • der Austausch des Hersteller-OCX hat die Ereignissignaturen verändert
  • das erneute Setzen der Referenz hat den Wrapper neu erzeugt und einen riesigen Diff produziert
  • das Ergebnis der Interop-Erzeugung unterscheidet sich von Entwicklungsrechner zu Entwicklungsrechner leicht

Dass es unter Choose Toolbox Items erscheint, ist noch keine Sicherheit. Ob der Designer es platzieren kann, ob zur Laufzeit Ereignisse ankommen und ob der Wrapper beim Ziel der Auslieferung insgesamt funktioniert, sollte man getrennt betrachten.

5.3 Wer STA / MTA und die Nachrichtenschleife auf die leichte Schulter nimmt, erlebt ein Einfrieren

Vorab drei Begriffe kurz definiert:

Begriff Bedeutung
Apartment Eine logische Gruppierung, die COM-Objekte und Threads unter derselben Nebenläufigkeitsregel zusammenfasst. Ein Objekt gehört immer nur zu genau einem Apartment
STA (Single-Threaded Apartment) Ein Apartment, dem nur ein einziger Thread angehört. Aufrufe an dessen Objekte werden immer auf genau diesem einen Thread ausgeführt
Marshalling Der Mechanismus, mit dem COM einen Aufruf über Apartment-Grenzen hinweg überbrückt, wenn ein Schnittstellenzeiger weitergegeben wird. Er läuft über Proxy und Stub

COM muss auf jedem verwendeten Thread mit CoInitializeEx initialisiert werden. Auch Microsoft Learn hält ausdrücklich fest, dass jeder Thread, der COM nutzt, CoInitializeEx einzeln aufrufen muss.24

Zudem benötigt ein STA (Single-Threaded Apartment) eine Nachrichtenschleife.2425

Warum es ohne Nachrichtenschleife einfriert

Bleibt man hier nur beim Ergebnis, lässt sich das nicht auf andere Fälle übertragen – deshalb ein Blick eine Ebene tiefer in den Mechanismus.

COM erzeugt für jedes STA ein verstecktes Fenster der Fensterklasse OleMainThreadWndClass. Ruft man das Objekt aus einem anderen Apartment auf, wird der Aufruf nicht zu einem direkten Funktionsaufruf, sondern trifft als Fensternachricht an dieses versteckte Fenster ein. Entnimmt der STA-Thread die Nachricht und verteilt sie, ruft dessen Fensterprozedur die entsprechende Methode der Schnittstelle auf.25

Die Nachrichtenschleife ist also keine „gute Praxis, die man haben sollte“, sondern der Mechanismus, der den Aufruf überhaupt erst zustellt.

OCX / ActiveXSTA-ThreadNachrichtenwarteschlange des versteckten FenstersAufrufer auf anderem ThreadOCX / ActiveXSTA-ThreadNachrichtenwarteschlange des versteckten FenstersAufrufer auf anderem ThreadSteht die Nachrichtenschleife still,findet diese Entnahme nicht statt und der Aufrufer wartet weiterMethodenaufruf wird als Nachricht eingereihtDie Nachrichtenschleife entnimmt sieDie Fensterprozedur ruft die entsprechende Methode aufDer Rückgabewert kommt zurück

Deshalb friert alles ein, wenn der STA-Thread blockiert wird. Schreibweisen wie Task.Wait() oder Task.Result im UI-Thread, oder das Warten mit WaitOne, stoppen die Entnahme von Nachrichten – dadurch werden COM-Rückrufe und Aufrufe über Apartment-Grenzen hinweg nicht mehr zugestellt, und es kommt zum Deadlock.25

Aus demselben Grund darf man den Schnittstellenzeiger eines STA-Objekts nicht einfach unverändert in einen anderen Thread kopieren. Kopiert man ihn, wird der Aufruf, der eigentlich an den STA-Thread zugestellt werden sollte, direkt auf dem anderen Thread ausgeführt, wodurch eine vom Objekt nicht vorgesehene Nebenläufigkeit entsteht. Beim Überschreiten von Apartment-Grenzen wird mit CoMarshalInterThreadInterfaceInStream und CoGetInterfaceAndReleaseStream marshalliert.25

Besonders UI-nahe OCX-/ActiveX-Elemente setzen häufig STA voraus, sodass es zu einer unangenehmen Fehlerklasse kommt:

  • es funktioniert im UI-Thread
  • wird es an Task.Run oder den ThreadPool übergeben, friert es ein
  • Ereignisse kommen nicht zurück
  • es reproduziert sich nur gelegentlich

Zudem gilt bei STA auch, dass man Schnittstellenzeiger nicht einfach unverändert in einen anderen Thread kopieren darf, sondern sie bei Bedarf marshallieren muss.2425

Diese Art von Fehlern ist nicht so freundlich wie 0x80040154: Man sieht nur ein Einfrieren, Ausbleiben von Antworten, gelegentliche Abstürze, was ebenso viel Zeit kostet wie Registry-Probleme.

6. Eine in der Praxis bewährte Reihenfolge der Eingrenzung

In der Praxis kommt man schneller voran, wenn man nicht sofort in die Tiefe geht, sondern in dieser Reihenfolge eingrenzt.

6.1 Zuerst festlegen, „welcher Prozess welche Bitness hat“

Das ist der erste Blick.

  • Was ist der Host (Visual-Studio-Designer / eigene Anwendung / Office / Access / Explorer / eine browserkompatible Umgebung)
  • Ist dieser Host 32-Bit oder 64-Bit
  • Ist die betroffene DLL / das OCX 32-Bit oder 64-Bit
  • Ist es In-Proc oder Out-of-Proc

Sind diese vier Punkte nicht geklärt, bevor man in der Registry zu suchen beginnt, ist unklar, welche View überhaupt zu betrachten ist, und die Untersuchung dreht sich im Kreis. Legen Sie das zuerst fest.

6.2 Als Nächstes die „Art der Registrierung“ prüfen

Als Nächstes ist zu klären, was mit welchem Mittel richtig registriert wird.

  • Native DLL / OCX → regsvr32
  • COM-Offenlegung einer .NET-Framework-Assembly → Regasm.exe
  • COM-Offenlegung unter .NET 5+ / 6+ / 8+ → .comhost.dll
  • Eine DLL ganz ohne Selbstregistrierung → nicht regsvr32

Allein diese Einordnung verhindert schon einen großen Teil der Fehlschüsse.

6.3 Danach prüfen, „wo registriert wurde“

Der bloße Blick auf HKCR reicht nicht aus. Zu prüfen sind:

  • HKCU\Software\Classes
  • HKLM\Software\Classes
  • bei Bedarf die 32-Bit-/64-Bit-Registry-View
  • die betroffene ProgID / CLSID / TypeLib
  • InprocServer32 / LocalServer32
  • ThreadingModel
  • der tatsächliche Pfad der referenzierten DLL

Ein bloßes „HKCR hat einen Eintrag“ reicht nicht – erst wenn zusätzlich feststeht, von wem, mit welcher Bitness und über welche View zugegriffen wird, hat der Befund eine Aussage.711

Die zu prüfenden Registrierungspfade

Öffnet man HKEY_CLASSES_ROOT\CLSID\{...}, wird nur das angezeigt, was mit der Bitness und dem Benutzer des gerade ausgeführten Werkzeugs sichtbar ist. Bei der Eingrenzung betrachtet man die tatsächlichen Orte vor der Zusammenführung getrennt an vier Stellen. Der Bereich unter CLSID ist Ziel der WOW64-Umleitung; der physische Ablageort der 32-Bit-Seite liegt unter Wow6432Node innerhalb von Classes.26

Was Sie sehen möchten Registrierungspfad
Maschinenweit / 64-Bit-View HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{CLSID}
Maschinenweit / 32-Bit-View HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Wow6432Node\CLSID\{CLSID}
Nur dieser Benutzer / 64-Bit-View HKEY_CURRENT_USER\SOFTWARE\Classes\CLSID\{CLSID}
Nur dieser Benutzer / 32-Bit-View HKEY_CURRENT_USER\SOFTWARE\Classes\Wow6432Node\CLSID\{CLSID}
Von ProgID zur CLSID Standardwert von HKEY_CLASSES_ROOT\{ProgID}\CLSID

Da regedit.exe unter Windows 10/11 ein 64-Bit-Prozess ist, lassen sich die obigen Pfade direkt eingeben, um beide Views unmittelbar zu öffnen. Fügt man den Pfad in die Adressleiste ein, springt man direkt an diese Stelle.

Pfade mit Wow6432Node sind der physische Ablageort; Microsoft rät davon ab, sie aus Anwendungscode direkt anzusprechen. Für die manuelle Untersuchung ist das unproblematisch, aber aus Skripten oder Anwendungen heraus ist es sicherer, die weiter unten beschriebene View-Angabe zu verwenden.11

Mit Befehlen prüfen

Mit reg.exe lässt sich die View über /reg:32 und /reg:64 ausdrücklich angeben.

reg query "HKLM\SOFTWARE\Classes\CLSID\{00000000-0000-0000-0000-000000000000}\InprocServer32" /reg:32
reg query "HKLM\SOFTWARE\Classes\CLSID\{00000000-0000-0000-0000-000000000000}\InprocServer32" /reg:64

Um alle vier Stellen in PowerShell auf einmal zu prüfen, übergeben Sie die View an RegistryKey.OpenBaseKey. Das Ergebnis wird dann nicht von der Bitness von PowerShell selbst verfälscht, weshalb sich dieser Weg für die Eingrenzung anbietet.

# Die zu untersuchende ProgID an einer Stelle angeben
$progId = 'Vendor.Control.1'

# 1) Von der ProgID zur CLSID (HKCR ist die merged view aus HKLM und HKCU)
$clsid = (Get-ItemProperty -Path "Registry::HKEY_CLASSES_ROOT\$progId\CLSID" -Name '(default)' -ErrorAction Stop).'(default)'
"ProgID $progId -> CLSID $clsid"

# 2) Alle 4 Kombinationen aus 2 Hives x 2 Views der Reihe nach prüfen
foreach ($hive in [Microsoft.Win32.RegistryHive]::LocalMachine, [Microsoft.Win32.RegistryHive]::CurrentUser) {
    foreach ($view in [Microsoft.Win32.RegistryView]::Registry64, [Microsoft.Win32.RegistryView]::Registry32) {
        $base = [Microsoft.Win32.RegistryKey]::OpenBaseKey($hive, $view)
        $key  = $base.OpenSubKey("SOFTWARE\Classes\CLSID\$clsid\InprocServer32")
        if ($null -eq $key) {
            '{0,-12} {1,-11} : keine Registrierung' -f $hive, $view
        }
        else {
            '{0,-12} {1,-11} : {2} (ThreadingModel={3})' -f $hive, $view, $key.GetValue(''), $key.GetValue('ThreadingModel')
            $key.Dispose()
        }
        $base.Dispose()
    }
}

Dieses Ergebnis von vier Zeilen ist unmittelbar die Antwort für die Eingrenzung.

  • Alle vier Stellen „keine Registrierung“ → tatsächlich nicht registriert. Prüfen Sie, ob das Registrierungsmittel zur Einordnung aus 6.2 passt.
  • Erscheint nur unter Registry32 → nur auf der 32-Bit-Seite registriert. Aus einem 64-Bit-Prozess kommt dann 0x80040154.
  • Erscheint nur unter CurrentUser → nur für diesen Benutzer sichtbar. Für andere Benutzer, Dienstkonten oder erhöhte Prozesse nicht sichtbar.
  • Der Pfad erscheint, aber es funktioniert trotzdem nicht → prüfen Sie, ob die Datei existiert, auf die der Standardwert von InprocServer32 verweist, und ob deren Bitness zum Host passt.

6.4 Zum Schluss prüfen, ob sich etwas hinter Berechtigungen verbirgt

Zuletzt ist zu klären, ob das Problem wirklich an Berechtigungen liegt, oder ob durch die Berechtigungen lediglich eine andere Registrierung sichtbar wird.

  • Ändert sich das Verhalten zwischen Standardbenutzer und Administrator
  • Was ändert sich, wenn Visual Studio erhöht wird
  • Reproduziert es sich auch unter einem Dienstkonto oder einem anderen Benutzer
  • Besteht es auch in einer sauberen, über den Installer eingerichteten Umgebung

Betrachtet man nur einen einzigen Entwicklungsrechner, wird man sich an dieser Stelle wirklich täuschen.

7. Betriebsentscheidungen, die vorab getroffen weniger Unfälle verursachen

Bei der Entwicklung und Pflege von COM / ActiveX / OCX wirkt manchmal die Art, wie man den Betrieb festlegt, stärker als jede Implementierungstechnik.

7.1 Zuerst die Bitness-Strategie festlegen

Legen Sie das zuerst fest:

  • Bleiben Sie fest bei x86
  • Ist x64 die maßgebliche Zielarchitektur
  • Unterstützen Sie beides
  • Muss diese Komponente zwingend In-Proc sein

Ist insbesondere ein Hersteller-OCX fest an x86 gebunden, führt das Ignorieren dieser Tatsache und die alleinige x64-Umstellung der Anwendung später zu einer Sackgasse. Dieses Thema hängt, als größerer Architekturzusammenhang betrachtet, auch mit „Eine COM-Bridge zum Aufruf einer 64-Bit-DLL aus einer 32-Bit-Anwendung – ein Praxisbeispiel“ zusammen.

7.2 Die Registrierungsstrategie festlegen

Auch die Registrierung sollte nicht ad hoc erfolgen.

  • Maschinenweite Nutzung → per-machine-Registrierung über den Installer
  • Nutzung nur durch diesen einen Benutzer → per-user bewusst einsetzen
  • Nutzung nur innerhalb der eigenen Anwendung → registration-free COM erwägen
  • Nur für die Entwicklung nötig → in ein explizites Dev-Setup-Skript einschließen

Registration-free COM kann die Aktivierungsinformationen statt in der Registry in einem Manifest führen und ist damit ein wirksames Mittel, um die Registrierungshölle zu verringern. Sowohl das registration-free COM auf Win32-Seite als auch das RegFree COM auf .NET-Seite sind offiziell dokumentiert.27628

Vom Mechanismus her läuft es so: Wenn COM z. B. CoCreateInstance verarbeitet, wird zuerst nach einem Aktivierungskontext gesucht, und nur wenn dort keine Information vorliegt, in die Registry geschaut. Das Manifest ist die Datei, die den Inhalt dieses Aktivierungskontexts deklariert.27

Nötig sind zwei Manifeste, beide im selben Ordner wie die EXE.

Das erste ist das Assembly-Manifest der Komponente (VendorCtl.manifest). Es legt fest, welche Datei welche CLSID bereitstellt.29

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity type="win32"
                    name="MyCompany.VendorCtl"
                    version="1.0.0.0"
                    processorArchitecture="x86" />
  <file name="vendor.ocx">
    <comClass description="Vendor Control"
              clsid="{00000000-0000-0000-0000-000000000000}"
              threadingModel="Apartment"
              progid="Vendor.Control.1" />
  </file>
</assembly>

Das zweite ist das Anwendungsmanifest (MyApp.exe.manifest) auf Seiten der Anwendung. Es legt lediglich die Abhängigkeit vom obigen Assembly-Manifest fest.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity type="win32"
                    name="MyCompany.MyApp"
                    version="1.0.0.0"
                    processorArchitecture="x86" />
  <dependency>
    <dependentAssembly>
      <assemblyIdentity type="win32"
                        name="MyCompany.VendorCtl"
                        version="1.0.0.0"
                        processorArchitecture="x86" />
    </dependentAssembly>
  </dependency>
</assembly>

Beim Schreiben werden häufig folgende Punkte übersehen:29

  • Die assemblyIdentity auf der abhängigen Seite muss vollständig mit der assemblyIdentity auf der Komponentenseite übereinstimmen. Weicht auch nur eines von name, version oder processorArchitecture ab, wird die Auflösung nicht gefunden.
  • Assemblyname und DLL-Name müssen unterschiedlich sein. Legt man das Manifest als separate Datei ab, müssen sich Assemblyname und Manifestname vom DLL-Namen unterscheiden.
  • Element- und Attributnamen unterscheiden Groß-/Kleinschreibung. Schreibt man comClass als ComClass, wird es nicht gelesen.
  • processorArchitecture ist die Bitness selbst. Für ein x86-OCX lässt sich aus einer amd64-Anwendung heraus keine Auflösung finden. Das muss unbedingt zur Bitness-Strategie aus 7.1 passen.
  • Bindet man ein OCX ein, können Attribute der miscStatus-Gruppe nötig sein. Was in der Registry dem Schlüssel MiscStatus entspricht, muss auf Manifest-Seite als miscStatus / miscStatusContent und Ähnliches nachgebildet werden.

7.3 Erzeugte Artefakte nicht zu sehr außerhalb der Versionsverwaltung belassen

Bei OCX/ActiveX kommt es häufig zur Zersplitterung der Abhängigkeiten.

  • das OCX selbst
  • abhängige DLLs
  • die TLB
  • .lic-Dateien
  • Interop-DLLs
  • AxHost-Wrapper
  • Registrierungsskripte
  • ein Beispiel-Host

Liegt das alles nur lokal bei einer Person, kommt es einige Monate später mit Sicherheit zu einem Zwischenfall.

Mindestens Folgendes sollte zusammen mit dem Code aufbewahrt werden:

  • welche Version vorausgesetzt wird
  • was in welcher Reihenfolge installiert wird
  • mit welchem Befehl registriert wird
  • ob es für x86 oder x64 gedacht ist

8. Für diese Art von Anliegen passt eine Beratung gut

Bei diesem Thema entsteht der Wert oft schon durch Eingrenzung und Klärung der Vorgehensweise allein, bevor man in eine vollständige Überarbeitung einsteigt.

Besonders gut passt es beispielsweise zu Anliegen wie diesen:

  • Die Ursachen von 0x80040154 oder 0x80070005 sollen nach Bitness / Registrierung / Berechtigungen sortiert eingeordnet werden
  • Nach dem Umstieg auf Visual Studio 2022 ist der Designer kaputtgegangen, und Sie möchten sehen, wie viel sich davon retten lässt
  • Ein Hersteller-OCX soll erhalten bleiben, während das Umfeld auf .NET und C# umgestellt wird
  • Sie möchten entscheiden, wie lange x86-fest gebundene Bestände weiterleben und ab wo überbrückt / gekapselt / ersetzt wird
  • Die Abhängigkeit von manuellem regsvr32 soll aufgegeben und Installation/Auslieferung neu entworfen werden

Für die Entscheidung zwischen Behalten, Kapseln und Ersetzen ist auch „ActiveX / OCX heute behandeln - Entscheidungstabelle: Behalten, Kapseln, Ersetzen“ hilfreich.

9. Zusammenfassung

Bleibt man bei der Entwicklung von COM-Komponenten oder OCX/ActiveX hängen, führt die Ursache meist auf diese vier Punkte zurück:

  1. Die Bitness stimmt nicht überein
  2. Das Registrierungsverfahren ist falsch
  3. Der Registrierungsumfang (HKCU / HKLM, 32-Bit-/64-Bit-View) ist verschoben
  4. Ein Zustand, der nur zufällig durch Berechtigungen sichtbar ist, wird für normal gehalten

Mit der 64-Bit-Umstellung von Visual Studio 2022 treten Designs, die früher „irgendwie funktionierten“, deutlich leichter zutage.12 Gerade deshalb ist es beim Umgang mit COM / OCX / ActiveX der schnellere Weg, die Annahmen über die Umgebung abzugleichen, bevor man Code schreibt.

Wichtiger, als zu zählen, wie oft man regsvr32 ausführt, ist zu klären:

  • welcher Prozess hostet
  • welche Bitness dieser Prozess hat
  • wo registriert werden sollte
  • ob diese Registrierung wirklich Administratorrechte voraussetzt
  • ob Designer und Laufzeit getrennt betrachtet werden

Das führt weit schneller zur Lösung.


Quellen

  1. Microsoft Learn, Visual Studio 2022 version 17.0 Release Notes — „devenv.exe is now 64-bit only“.  2 3

  2. Microsoft Learn, Troubleshoot 32-bit problems - Windows Forms — Visual Studio 2022 ist ein 64-Bit-Prozess und kann 32-Bit-.NET-/COM-/ActiveX-Komponenten nicht direkt laden; Einschränkungen des Out-of-Process-Designers.  2 3 4 5

  3. Microsoft Learn, Classes and Servers — COM-Registrierung, HKCU / HKCR, Selbstregistrierung und DllRegisterServer 2 3 4

  4. Microsoft Learn, regsvr32 — Syntax und Rolle von regsvr32 2

  5. Microsoft Learn, Registrieren von Assemblys bei COM — Die COM-Registrierung für .NET Framework erfolgt über Regasm.exe 2

  6. Microsoft Learn, Offenlegen von .NET Core-Komponenten für COMEnableComHosting, die erzeugte .comhost.dll, regsvr32, EnableRegFreeCom 2 3

  7. Microsoft Learn, Merged View of HKEY_CLASSES_ROOT — HKCR ist eine merged view aus HKLM und HKCU.  2 3 4 5

  8. Microsoft Learn, HKEY_CLASSES_ROOT Key — Anwendungen, die Administratorrechte benötigen, wird die Registrierung in die maschinenweite COM-Konfiguration empfohlen.  2

  9. Microsoft Learn, COM Error Codes (Generic) (Winerror.h)REGDB_E_CLASSNOTREG (0x80040154) und weitere. 

  10. Microsoft Learn, You receive 0x80070005 error when you try to register a DLL by using Regsvr32.exe — typischer Fall eines an fehlenden Rechten scheiternden DLL-Registrierungsversuchs.  2

  11. Microsoft Learn, Registry Redirector — die 32-Bit-/64-Bit-Registry-Views unter WOW64, der physische Ablageort von HKLM\Software unter Wow6432Node, keine direkte Verwendung des physischen Pfads aus Anwendungen.  2 3 4 5

  12. Microsoft Learn, File System Redirector%windir%\System32 auf x64-Windows und die WOW64-Dateisystemumleitung. 

  13. Microsoft Learn, Übersicht über Kompatibilitätsaspekte für 32-Bit-Programme unter 64-Bit-Versionen von Windows — Datei-/Registry-Umleitung unter WOW64. 

  14. Microsoft Learn, Regasm.exe (Assembly Registration Tool) — Rolle von Regasm.exe und Optionen wie /tlb 2 3

  15. Microsoft Learn, Packaging a .NET Framework Assembly for COM — Typbibliotheken und Regasm.exe /tlb

  16. Microsoft Learn, Understanding Custom Build Steps and Build Events — Beispiel für die Verwendung von regsvr32.exe in einem Post-Build-Event. 

  17. Microsoft Learn, Windows-Registrierung für fortgeschrittene BenutzerHKCU\Software\Classes und HKLM\Software\Classes, Verhalten von HKCR. 

  18. Microsoft Learn, Find, install, and manage extensions for Visual Studio — Umgang mit per-user-Erweiterungen bei erhöhter Ausführung. 

  19. Microsoft Learn, MFC ActiveX Controls: Licensing an ActiveX Control — Entwurfszeit-/Laufzeitlizenz, .LIC

  20. Microsoft Learn, Application Settings, MFC ActiveX Control Wizard — Erzeugung der Laufzeitlizenz und die .lic-Datei. 

  21. Microsoft Learn, License information for this component not found. You don’t have an appropriate license to use this functionality in the design environment. 

  22. Microsoft Learn, Aximp.exe (Windows Forms ActiveX Control Importer) — Umwandlung von ActiveX in einen WinForms-Wrapper. 

  23. Microsoft Learn, AxHost Class — der vom ActiveX Control Importer erzeugte AxHost-basierte Wrapper. 

  24. Microsoft Learn, Initialisieren der COM-BibliothekCoInitializeEx, Initialisierung je Thread, die Nachrichtenschleife von STA.  2 3

  25. Microsoft Learn, Single-Threaded-Apartments — die Nachrichtenschleife von STA, Marshalling, ThreadingModel 2 3 4 5

  26. Microsoft Learn, Registry Keys Affected by WOW64HKLM\SOFTWARE\Classes\CLSID und HKCU\SOFTWARE\Classes\CLSID sind Ziel der Umleitung; HKCR ist die merged view aus beiden. 

  27. Microsoft Learn, Erstellen von registrierungsfreien COM-Objekten — registrierungsfreies COM über Aktivierungskontexte.  2

  28. Microsoft Learn, Registrierungsfreie COM Interop — registrierungsfreie COM-Interop im .NET Framework. 

  29. Microsoft Learn, Assembly Manifests — die Attribute von assembly / assemblyIdentity / dependency / file / comClass, die Notwendigkeit, dass die assemblyIdentity auf REF- und DEF-Seite übereinstimmt, Groß-/Kleinschreibung der Namen, Attribute der miscStatus-Gruppe.  2

Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.

Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.

Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.

Häufige Fragen

Fragen, die in Beratungen zu diesem Artikelthema häufig gestellt werden.

Wie untersucht man 0x80040154 (Class not registered)?
Offiziell ist es REGDB_E_CLASSNOTREG, wörtlich der Fehler „Klasse nicht registriert“ – das bedeutet aber nicht zwangsläufig, dass gar keine Registrierung existiert. Derselbe Fehler tritt auch auf, wenn die Registrierung nur in der Registry-View der anderen Bitness liegt oder nur auf der HKCU-Seite, die ausschließlich für diesen einen Benutzer sichtbar ist. Am schnellsten kommt man voran, wenn man zuerst festlegt, ob der Host-Prozess 32-Bit oder 64-Bit ist, dann prüft, ob das Registrierungsmittel korrekt ist, und schließlich der Reihe nach nachsieht, wo unter HKCU/HKLM und in welcher 32-Bit-/64-Bit-View registriert wurde.
Warum funktioniert es nicht, obwohl mit regsvr32 registriert wurde?
regsvr32 ist für native In-Proc-COM-Server (DLL / OCX) gedacht, die DllRegisterServer exportieren – es ist kein Zauberbefehl, der alles registrieren kann. Die COM-Offenlegung einer .NET-Framework-Assembly läuft über Regasm.exe, und ab .NET 5 wird die durch EnableComHosting erzeugte .comhost.dll mit regsvr32 registriert. Zudem muss man auf x64-Windows zwischen dem regsvr32 in System32 (für 64-Bit) und dem in SysWOW64 (für 32-Bit) unterscheiden – registriert man auf der falschen Seite, meldet der Vorgang Erfolg, ist aber vom Zielprozess aus nicht sichtbar.
Warum bricht in Visual Studio 2022 ausgerechnet der Designer?
Weil devenv.exe von Visual Studio 2022 ein 64-Bit-Prozess ist und deshalb 32-Bit-COM-/ActiveX-Komponenten nicht direkt laden kann. Die Anwendung selbst kann zur Laufzeit weiterhin als x86 laufen, aber der Designer läuft im 64-Bit-Prozess von Visual Studio – daraus ergibt sich die verwirrende Situation, dass die Laufzeit funktioniert, aber ausschließlich der Designer abstürzt. Auch mit AnyCPU bleibt das Problem bestehen, wenn eine referenzierte Abhängigkeit weiterhin fest an 32-Bit-COM/ActiveX gebunden ist.
Warum funktioniert es als Administrator, aber nicht mit normalen Rechten?
Der eigentliche Kern liegt meist nicht in den Rechten selbst, sondern in einer Abweichung beim Registrierungsumfang oder im Installationsdesign. COM sieht zuerst in HKCU\Software\Classes nach, und HKEY_CLASSES_ROOT ist eine merged view aus HKLM und HKCU. Hat Entwickler A manuell unter seinem eigenen Benutzerkonto registriert, funktioniert es ganz normal nur bei A, nicht aber bei anderen Benutzern oder Dienstkonten. Grundlegend ist es, die per-user-Registrierung für die Entwicklung von der per-machine-Registrierung der Produktion, die der Installer übernimmt, zu trennen und Build und Registrierung getrennt zu halten.

Autorenprofil

Profilseite des Artikelautors.

Go Komura

Geschäftsführer von KomuraSoft LLC

Spezialisiert auf Windows-Softwareentwicklung, technische Beratung und Fehleranalyse, insbesondere bei bestehenden Systemen und schwer reproduzierbaren Störungen.

Zurück zum Blog