Trampas de registro y bitness en el desarrollo COM/OCX/ActiveX
· Actualizado el: · Go Komura · COM, ActiveX, OCX, Visual Studio, Desarrollo en Windows, 32 bits, 64 bits, Interop
En los proyectos de componentes COM y OCX / ActiveX, los problemas surgen con más frecuencia en los límites entre entorno de ejecución, registro, host y permisos que en el código en sí.
Los síntomas típicos son estos:
- La compilación pasa, pero al iniciar aparece
0x80040154. - Funciona en el equipo de desarrollo propio, pero no en otros PC.
- Funciona en tiempo de ejecución, pero solo el Diseñador (Designer) de Visual Studio falla.
- Funciona al iniciar como administrador, pero falla con permisos normales.
- Se ejecuta
regsvr32y, sin razón aparente, el problema no se soluciona.
Esto no suele ser un error puntual, sino un estado en el que alguna premisa de COM deja de encajar en algún punto.
Si primero quiere ordenar los propios términos COM / ActiveX / OCX, le resultará más fácil captar el panorama general leyendo antes Qué son COM, ActiveX y OCX - diferencias y relaciones explicadas. En este artículo, como paso siguiente, se ordena dónde se suele atascar realmente el trabajo, incluyendo lo relativo a la bitness de Visual Studio y a los permisos de administrador.
1. Conclusión inicial
Por delante, dicho en términos que sirven en la práctica:
- Los problemas de COM / OCX / ActiveX suelen originarse más por una falta de coincidencia en bitness (32 bits / 64 bits), destino de registro, host y permisos que por la lógica del código.
- Como Visual Studio 2022 es un proceso de 64 bits, la integración en tiempo de diseño que antes funcionaba bajo la premisa de 32 bits se rompe tal cual. 12
regsvr32no es un comando mágico que registre cualquier cosa. Está pensado para servidores COM in-proc nativos (DLL / OCX). Para exponer .NET Framework como COM se usaRegasm.exe, y en .NET 5+ / .NET 6+ / .NET 8+ el flujo consiste en registrar el.comhost.dllgenerado. 3456- Pensar que «si funciona como administrador, está bien» es peligroso. No es raro que se vea por casualidad gracias a un registro per-user, o que el registro que debería hacer el instalador se haya resuelto a mano solo en el equipo de desarrollo. 378
En definitiva, al trabajar con COM / OCX / ActiveX, lo más seguro es mirar primero estos cuatro ejes:
- Qué proceso actúa como host.
- Si ese proceso es de 32 bits o de 64 bits.
- Dónde está registrado (HKCU / HKLM, vista de 32 bits / vista de 64 bits).
- Si esa operación o ejecución requiere permisos de administrador.
2. Visto al revés, desde los síntomas, esto es lo que suele verse
| Síntoma | Primera sospecha | Causa real habitual |
|---|---|---|
0x80040154 Class not registered |
No está registrado | En la práctica también ocurre cuando «solo está registrado en el otro bitness» o «solo está registrado para ese usuario» |
DllRegisterServer failed: 0x80070005 |
Permisos insuficientes | Se intenta registrar con un usuario estándar, o se registra en el post-build sin elevación |
| Solo el Diseñador falla en VS2022 | Limitación del Diseñador | El Visual Studio de 64 bits no puede leer directamente el COM / ActiveX de 32 bits |
| Solo funciona al iniciar como administrador | Problema de permisos | En el fondo suele ser un desajuste en el ámbito de registro o en el diseño de la instalación, más que los permisos en sí |
| No se puede llamar a un OCX de 32 bits desde una app de 64 bits | Limitación del propio mecanismo de COM | Un servidor in-proc no puede cargarse si su bitness no coincide con la del host |
| Funciona en el hilo de UI pero se congela en un hilo en segundo plano | Modelo de hilos | Incumplimiento de las premisas de STA / MTA, CoInitializeEx y el bucle de mensajes |
0x80040154 es, oficialmente, REGDB_E_CLASSNOTREG, literalmente Class not registered (clase no registrada). 9
Del mismo modo, el 0x80070005 de regsvr32 está documentado oficialmente como el caso en que no hay permisos de administrador y no se puede escribir en el registro o en System32. 10
Lo importante aquí es no confiar demasiado en el nombre literal del error.
Por ejemplo, Class not registered no siempre significa «completamente sin registrar»: también aparece solo porque está registrado en una vista de registro distinta o registrado en un lugar visible únicamente para ese usuario. 117
3. Problemas con la bitness de Visual Studio
3.1 Visual Studio 2022 pasó a ser de 64 bits
Este es el punto donde más se atasca hoy en día el desarrollo COM / ActiveX.
En Visual Studio 2022, devenv.exe es exclusivamente de 64 bits. 1
Por eso, en la experiencia de diseño de WinForms, Visual Studio no puede cargar directamente componentes de 32 bits. Microsoft también deja explícito que, como Visual Studio 2022 es un proceso de 64 bits, no puede cargar componentes .NET / COM / ActiveX de 32 bits. 2
Es decir, lo que antes funcionaba más o menos bajo estas premisas:
- El proyecto es x86.
- El ActiveX referenciado también es x86.
- El propio Visual Studio es de 32 bits.
en VS2022 se convierte en esta contradicción:
- La app en tiempo de ejecución puede funcionar en x86.
- Pero el Diseñador se ejecuta en el Visual Studio de 64 bits.
El resultado es un estado difícil de diagnosticar en el que la ejecución sigue viva pero solo el Diseñador falla. Esto ocurre porque el host en tiempo de ejecución es el proceso de la aplicación, mientras que el host en tiempo de diseño es el proceso de Visual Studio: como los hosts son distintos, también lo es la bitness. 2
3.2 Cambiar a AnyCPU no siempre resuelve el problema
Este es otro malentendido frecuente.
AnyCPU no es magia que vuelva neutrales también las dependencias.
Según explica Microsoft, incluso un componente que parece AnyCPU genera problemas en el tiempo de diseño de Visual Studio 2022 si, más adelante en la cadena, referencia un COM / ActiveX fijado a 32 bits. 2
Por eso, si el error no desaparece al pasar a AnyCPU, conviene sospechar más rápido de:
- Si hay alguna dependencia nativa de 32 bits más adelante en ese ensamblado.
- Si el ActiveX / OCX está fijado a x86.
- Si hay código que solo se carga durante el Diseñador.
3.3 La trampa de System32 y SysWOW64
En Windows x64, el nombre da una impresión que no coincide con la realidad, lo que suele generar confusión.
Microsoft Learn explica que, en Windows x64, %windir%\System32 está pensado para aplicaciones de 64 bits. El lado de 32 bits es redirigido a otra ubicación mediante el redirector del sistema de archivos de WOW64. 12
Con el registro ocurre algo similar: el redirector de registro de WOW64 muestra vistas lógicas distintas para 32 bits y para 64 bits. 11
Por eso, al diagnosticar en su propio equipo, es más seguro dejar explícito qué bitness de regsvr32 se está usando.
# Para registrar un DLL / OCX de 64 bits
C:\Windows\System32\regsvr32.exe vendor.ocx
# Para registrar un DLL / OCX de 32 bits en Windows x64
C:\Windows\SysWOW64\regsvr32.exe vendor.ocx
Lo problemático es cuando se registra con el regsvr32 del lado equivocado. El registro en sí tiene éxito, pero no es visible desde el proceso objetivo.
El resultado es una situación como esta:
regsvr32se ejecutó con éxito.- Pero la aplicación sigue mostrando
0x80040154. - Al mirar el registro, parece que está ahí.
- Pero se está mirando otra vista.
3.4 regsvr32 no permite registrar cualquier cosa
Este es otro punto donde abundan los malentendidos.
Los servidores COM in-proc nativos normalmente exportan DllRegisterServer / DllUnregisterServer para admitir el autorregistro. 3
regsvr32 es la herramienta que se usa con ese tipo de DLL / OCX. 4
Por otro lado, cuando se quiere usar un ensamblado de .NET Framework desde COM, lo habitual es Regasm.exe. Microsoft Learn también explica que para registrar un ensamblado que se usará con COM se debe usar Regasm.exe. 514
Además, en la exposición COM de .NET 5+ / .NET 6+ / .NET 8+ el panorama cambia un poco: al compilar con <EnableComHosting>true</EnableComHosting> se genera un *.comhost.dll, y ese archivo es el que se registra con regsvr32. 6
En resumen, a grandes rasgos queda así:
| Qué se quiere exponer | Medio de registro habitual |
|---|---|
| DLL / OCX nativo en C++ | regsvr32 |
| Ensamblado de .NET Framework expuesto como COM | Regasm.exe |
| Exposición COM en .NET 5+ / 6+ / 8+ | El .comhost.dll generado, con regsvr32 |
Regasm.exe se ejecuta desde el Developer Command Prompt / Developer PowerShell. Las tres formas más habituales son estas: 14
:: 1) Registrar las clases públicas del ensamblado
regasm myTest.dll
:: 2) Generar también la biblioteca de tipos y registrarla (para VBA / VB6 u otros que requieran un TLB)
regasm myTest.dll /tlb:myTest.tlb
:: 3) Sin modificar el registro directamente, volcar el contenido a un archivo .reg para revisarlo
regasm myTest.dll /regfile:myTest.reg
Para anular el registro se usa /unregister. Una biblioteca de tipos registrada con /tlb se anula especificando /tlb y /unregister juntos.
regasm myTest.dll /tlb:myTest.tlb /unregister
Hay dos restricciones en las que es fácil caer aquí. 14
- Un ensamblado que no se coloca en la GAC necesita
/codebase. Como/codebasegraba en el registro la ruta del archivo en el momento del registro, si más adelante se mueve el ensamblado, el inicio fallará. Microsoft también recomienda encarecidamente que los ensamblados registrados con/codebaselleven nombre seguro (strong name). /regfileno se puede combinar con/unregisterni con/tlb. Además, lo que/regfilevuelca son solo las entradas de las clases administradas: no generaTypeLibIDniInterfaceID. Pensar que «con distribuir el.regbasta para el registro» se queda corto.
Cuando se mezclan estas diferencias, el patrón habitual es este:
- Se ejecuta
regsvr32sobre un DLL administrado. - Como es lógico, no se encuentra
DllRegisterServer. - Y entonces se malinterpreta que «el DLL está roto».
En el mundo donde se necesita biblioteca de tipos (VBA / VB6 / algunos escenarios de enlace anticipado), la generación y el registro del TLB son además un problema aparte. En .NET Framework se puede generar y registrar la biblioteca de tipos con Regasm.exe /tlb, y Microsoft también explica que «registrar los tipos» y «registrar la biblioteca de tipos» son actividades distintas. 15
4. Problemas con los permisos de administrador
4.1 Registrar en el post-build y que solo funcione como administrador
En una compilación C++ de Visual Studio, es perfectamente normal invocar regsvr32.exe desde un build event o un custom build step. Microsoft Learn incluso presenta el registro con regsvr32.exe como ejemplo de post-build event. 16
Ahora bien, que se pueda hacer y que sea seguro hacerlo son cosas distintas.
Al ejecutar regsvr32 con un usuario estándar, puede no ser posible escribir en el registro o en System32, lo que produce 0x80070005. El KB de Microsoft también identifica la causa como la falta de permisos de administrador. 10
Lo que suele ocurrir aquí es este tipo de accidente:
- Al iniciar Visual Studio con permisos normales, la compilación pasa.
- Pero solo falla el registro del post-build.
- Se pasa por alto el registro de error en el log.
- Como queda el registro antiguo de una vez anterior, en el equipo propio funciona por casualidad.
- En un entorno limpio, evidentemente, no funciona.
En este tipo de proyectos, lo básico es separar la compilación del registro.
- La compilación solo genera los binarios.
- El registro se hace en un install step / script / instalador explícito.
- En CI, el «paso que requiere registro» va en un job separado del de compilación.
Solo con esta separación se reducen bastante los accidentes.
4.2 Mezclar registro per-user y per-machine rompe las cosas
Si se recuerda solo que «COM mira HKCR», aquí es donde uno se atasca.
En realidad, tal como indica Microsoft Learn, COM mira primero HKEY_CURRENT_USER\Software\Classes y solo después maneja la información de todo el equipo. 3
Además, HKEY_CLASSES_ROOT es la vista combinada (merged view) de HKLM\Software\Classes y HKCU\Software\Classes. 717
Es decir, es normal que ocurra esto:
- Se registró manualmente con el usuario del desarrollador A.
- Funciona con la cuenta de A.
- No funciona con el desarrollador B.
- Tampoco funciona con una cuenta de servicio.
- El comportamiento cambia al ejecutarlo como administrador.
Microsoft también indica que las aplicaciones que requieren permisos de administrador deberían registrar los objetos COM de los que dependen, durante la instalación, en el almacén de configuración COM per-machine. 87
Por eso, en el trabajo de desarrollo conviene dejar clara esta distinción:
- ¿Es un registro de desarrollo que solo usa el propio usuario?
- ¿Es un registro de producción que usan todos los usuarios de esa máquina?
- ¿Es un registro al que hacen referencia un servicio o una aplicación elevada?
Si esto queda ambiguo, se acaba en situaciones como «por alguna razón solo funciona como administrador» o «funciona en la extensión de Explorer pero no en el servicio».
4.3 Ejecutar Visual Studio siempre «como administrador» no es una solución
Es cierto que hay situaciones en las que resulta necesario. Sin embargo, mantener Visual Studio siempre iniciado como administrador puede acabar ocultando, mediante la elevación del propio IDE, un problema que en realidad debería resolver el instalador o un script de registro.
Es más, el propio Visual Studio cambia el tratamiento de las extensiones per-user cuando se ejecuta elevado. Microsoft Learn explica que existe una configuración por la cual, al ejecutar Visual Studio elevado, se deshabilitan las extensiones per-user. 18
Por eso, como práctica operativa, resulta menos problemático a la larga:
- Hacer el desarrollo habitual con permisos normales.
- Realizar solo los pasos que requieren registro en un Developer Command Prompt / PowerShell / instalador explícitamente elevado.
- Si «solo se reproduce como administrador», documentar esa premisa como parte de la especificación.
5. Problemas específicos de ActiveX / OCX
5.1 La licencia de tiempo de diseño y la de tiempo de ejecución son distintas
Un problema discretamente molesto en OCX / ActiveX es el de las licencias.
En especial, en los controles ActiveX antiguos, la licencia de tiempo de diseño (design-time license) y la licencia de tiempo de ejecución (run-time license) pueden estar separadas. La documentación de ActiveX de MFC también explica el mecanismo que distingue diseño y ejecución mediante archivos de licencia y claves de licencia. 1920
En este terreno ocurre lo siguiente:
- Se puede usar en tiempo de ejecución.
- Pero al intentar colocarlo en un formulario, aparece «no hay licencia».
- Se puede colocar en el equipo de desarrollo A.
- No se puede colocar en el equipo de desarrollo B.
Errores como «no se encuentra la licencia de este componente» o «no hay una licencia adecuada» no son raros en los ActiveX antiguos. 21
5.2 Al montarlo en WinForms, ya hay un contenedor de por medio
Al usar ActiveX en WinForms, Windows Forms no hospeda el ActiveX tal cual.
Tal como indica la documentación de Aximp.exe en Microsoft Learn, el ActiveX Control Importer genera un contenedor (wrapper) para WinForms a partir de la biblioteca de tipos COM, y lo trata como un control basado en AxHost. 2223
Es decir, el problema no está en una sola capa, sino repartido en varias:
- El OCX / ActiveX original en sí.
- La biblioteca de tipos.
- El interop / wrapper generado.
- El Diseñador / tiempo de ejecución de WinForms.
Por eso ocurren cosas como estas:
- Al sustituir el OCX del proveedor, cambió la firma de los eventos.
- Al volver a añadir la referencia, se regeneró el wrapper y aparecieron muchísimas diferencias.
- El resultado de la generación del interop varía ligeramente entre equipos de desarrollo.
Que aparezca en Choose Toolbox Items no significa que todo esté bien.
Es más seguro verificar por separado si el Diseñador puede colocarlo, si los eventos llegan en tiempo de ejecución y si, en el destino de distribución, el conjunto incluido el wrapper funciona.
5.3 Subestimar STA / MTA y el bucle de mensajes provoca bloqueos
Antes de nada, se definen solo tres términos.
| Término | Significado |
|---|---|
| Apartamento (apartment) | Un grupo lógico que reúne objetos COM y subprocesos (hilos) bajo las mismas reglas de simultaneidad. Un objeto solo puede pertenecer a un apartamento |
| STA (single-threaded apartment) | Un apartamento al que pertenece un único subproceso. Las llamadas a ese objeto siempre se ejecutan en ese único subproceso |
| Marshaling | El mecanismo con el que COM hace de puente en las llamadas al pasar punteros de interfaz entre apartamentos, pasando por un proxy y un stub |
COM debe inicializarse con CoInitializeEx en cada subproceso que lo use. Microsoft Learn también deja explícito que cada subproceso que use COM debe llamar individualmente a CoInitializeEx. 24
Además, en un STA (single-threaded apartment) se necesita un bucle de mensajes. 2425
Por qué se bloquea todo si no hay bucle de mensajes
Si aquí solo se memoriza la conclusión, luego no se puede aplicar el conocimiento, así que conviene abrir un nivel más el mecanismo.
COM crea, por cada STA, una ventana oculta de la clase de ventana OleMainThreadWndClass. Cuando se llama a ese objeto desde otro apartamento, la llamada no se convierte en una llamada a función directa, sino que llega como un mensaje de ventana dirigido a esa ventana oculta. Cuando el subproceso del STA extrae el mensaje y lo despacha, el procedimiento de ventana correspondiente llama al método de la interfaz asociado. 25
Es decir, el bucle de mensajes no es «una buena práctica que conviene tener», sino el propio mecanismo que entrega las llamadas.
sequenceDiagram
accTitle: Por qué se bloquea una llamada sin bucle de mensajes en un STA
accDescr: Diagrama de secuencia que muestra cómo una llamada desde otro subproceso se convierte en un mensaje de ventana, cómo el subproceso STA la extrae mediante el bucle de mensajes y la entrega al objeto, y cómo, si el bucle se detiene, el llamador queda esperando indefinidamente
participant Caller as Llamador desde otro subproceso
participant Queue as Cola de mensajes de la ventana oculta
participant STA as Subproceso STA
participant Obj as OCX / ActiveX
Caller->>Queue: La llamada al método se apila como un mensaje
STA->>Queue: El bucle de mensajes la extrae
Queue->>Obj: El procedimiento de ventana llama al método correspondiente
Obj-->>Caller: Se devuelve el valor de retorno
Note over STA,Obj: Si el bucle de mensajes está detenido,<br/>esta extracción no ocurre y el llamador queda esperando
Por eso, bloquear el subproceso STA provoca un bloqueo total. Escribir código como usar Task.Wait() o Task.Result en el subproceso de UI, o esperar con WaitOne, detiene la extracción de mensajes, de modo que dejan de entregarse los callbacks de COM y las llamadas entre apartamentos, y se produce un interbloqueo (deadlock). 25
Por la misma razón, no se debe copiar tal cual a otro subproceso el puntero de interfaz de un objeto STA. Si se copia, la llamada que debería entregarse al subproceso STA se ejecuta directamente en el otro subproceso, y se produce una concurrencia que el objeto no espera. Al cruzar apartamentos, hay que hacer marshaling con CoMarshalInterThreadInterfaceInStream y CoGetInterfaceAndReleaseStream. 25
En particular, los OCX / ActiveX orientados a UI suelen asumir STA, así que se convierte en un fallo molesto de este tipo:
- Funciona en el subproceso de UI.
- Se bloquea al enviarlo a
Task.Runo a un ThreadPool. - Los eventos no vuelven.
- Solo se reproduce a veces.
Además, en un STA rige también la premisa de que no se debe copiar el puntero de interfaz tal cual a otro subproceso, y de que, si hace falta, hay que hacer marshaling. 2425
Este tipo de fallo no es tan «amable» como 0x80040154: solo se manifiesta como se bloquea, no vuelve, a veces se cae, así que consume tanto tiempo como los problemas de registro.
6. Orden de diagnóstico que funciona en la práctica
En la práctica, es más rápido diagnosticar en este orden que profundizar desde el principio.
6.1 Primero, fijar «qué bitness tiene cada proceso»
Lo primero que hay que mirar es esto:
- Cuál es el host (Diseñador de Visual Studio / aplicación propia / Office / Access / Explorer / entorno compatible con navegador).
- Si ese host es de 32 bits o de 64 bits.
- Si el DLL / OCX en cuestión es de 32 bits o de 64 bits.
- Si es in-proc o out-of-proc.
Si se empieza a mirar el registro sin haber fijado estos cuatro puntos, no queda claro qué vista hay que consultar y la investigación gira en el vacío. Fije esto primero.
6.2 A continuación, confirmar «el tipo de registro»
Lo siguiente que hay que verificar es con qué se debe registrar cada cosa correctamente:
- DLL / OCX nativo →
regsvr32 - Exposición COM de .NET Framework →
Regasm.exe - Exposición COM de .NET 5+ / 6+ / 8+ →
.comhost.dll - Un DLL que directamente no tiene self-registration → no es
regsvr32
Solo con esta clasificación se evitan bastantes intentos fallidos.
6.3 Después, comprobar «dónde quedó registrado»
No basta con mirar solo HKCR. Hay que revisar:
HKCU\Software\ClassesHKLM\Software\Classes- Si hace falta, la vista de registro de 32 bits / 64 bits
- El ProgID / CLSID / TypeLib en cuestión
InprocServer32/LocalServer32- El ThreadingModel
- La ruta física del DLL al que se hace referencia
Con «está en HKCR» no basta: solo tiene sentido cuando además se sabe desde quién, con qué bitness y qué vista se está mirando. 711
Las rutas de clave que hay que revisar
Al abrir HKEY_CLASSES_ROOT\CLSID\{...}, solo se muestra lo visible según la bitness de la herramienta que se está ejecutando y el usuario actual. Para el diagnóstico, conviene mirar por separado las cuatro ubicaciones reales previas a la fusión. Todo lo que cuelga de CLSID es objeto de redirección WOW64, y la ubicación física del lado de 32 bits es Wow6432Node, debajo de Classes. 26
| Qué se quiere ver | Ruta de registro |
|---|---|
| Toda la máquina / vista de 64 bits | HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{CLSID} |
| Toda la máquina / vista de 32 bits | HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Wow6432Node\CLSID\{CLSID} |
| Solo ese usuario / vista de 64 bits | HKEY_CURRENT_USER\SOFTWARE\Classes\CLSID\{CLSID} |
| Solo ese usuario / vista de 32 bits | HKEY_CURRENT_USER\SOFTWARE\Classes\Wow6432Node\CLSID\{CLSID} |
| Obtener el CLSID a partir del ProgID | Valor predeterminado de HKEY_CLASSES_ROOT\{ProgID}\CLSID |
El regedit.exe de Windows 10 / 11 es un proceso de 64 bits, así que con introducir directamente las rutas anteriores se pueden abrir ambas vistas sin más. Pegando la ruta en la barra de direcciones se llega directamente a esa posición.
Cabe señalar que la ruta que incluye Wow6432Node es una ubicación física, y Microsoft recomienda no tocarla directamente desde el código de una aplicación. No hay problema en consultarla durante una investigación manual, pero desde un script o una aplicación es más seguro usar la especificación de vista que se describe más adelante. 11
Comprobación mediante comandos
Con reg.exe se puede indicar explícitamente la vista mediante /reg:32 y /reg:64.
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
Si se quiere ver las cuatro ubicaciones a la vez desde PowerShell, se pasa la vista a RegistryKey.OpenBaseKey. Como el resultado no depende de la bitness del propio PowerShell, este método es más fiable para el diagnóstico.
# Se indica en un solo lugar el ProgID que se quiere investigar
$progId = 'Vendor.Control.1'
# 1) Obtener el CLSID a partir del ProgID (HKCR es la vista combinada de HKLM y HKCU)
$clsid = (Get-ItemProperty -Path "Registry::HKEY_CLASSES_ROOT\$progId\CLSID" -Name '(default)' -ErrorAction Stop).'(default)'
"ProgID $progId -> CLSID $clsid"
# 2) Comprobar de forma exhaustiva las 4 ubicaciones (2 hives x 2 views)
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} : sin registrar' -f $hive, $view
}
else {
'{0,-12} {1,-11} : {2} (ThreadingModel={3})' -f $hive, $view, $key.GetValue(''), $key.GetValue('ThreadingModel')
$key.Dispose()
}
$base.Dispose()
}
}
El resultado de estas 4 líneas es, directamente, la respuesta del diagnóstico.
- Las 4 ubicaciones dan «sin registrar» → realmente no está registrado. Revise si el medio de registro coincide con la clasificación del apartado 6.2.
- Solo aparece en
Registry32→ solo está registrado en el lado de 32 bits. Desde un proceso de 64 bits dará0x80040154. - Solo aparece en
CurrentUser→ solo es visible para ese usuario. No se ve desde otro usuario, una cuenta de servicio o un proceso elevado. - Aparece la ruta pero no funciona → compruebe si el archivo al que apunta el valor predeterminado de
InprocServer32existe realmente y si su bitness coincide con la del host.
6.4 Por último, comprobar «si algo queda oculto por los permisos»
Por último, se confirma si el problema es realmente de permisos, o si los permisos solo están dejando ver un registro distinto.
- ¿Cambia el comportamiento entre usuario estándar y administrador?
- ¿Qué cambia al elevar Visual Studio?
- ¿Se reproduce también con una cuenta de servicio o con otro usuario?
- ¿Se cumple también en un entorno limpio instalado mediante el instalador?
Si solo se ha comprobado en un único equipo de desarrollo, es muy fácil equivocarse en este punto.
7. Prácticas operativas que, decididas de antemano, reducen los accidentes
En el desarrollo y mantenimiento de COM / ActiveX / OCX, a veces resulta más eficaz cómo se decide la operativa que las técnicas de implementación.
7.1 Decidir de antemano la política de bitness
Lo primero es decidir esto:
- ¿Se va a mantener x86 fijo?
- ¿Se toma x64 como estándar?
- ¿Se dará soporte a ambos?
- ¿Ese componente tiene que ser obligatoriamente in-proc?
En particular, si el OCX del proveedor está fijado a x86, ignorar ese hecho y convertir solo la aplicación a x64 acaba causando problemas más adelante. Este tema se relaciona, en una configuración más amplia, con Caso práctico de puente COM: llamar a un DLL de 64 bits desde una aplicación de 32 bits.
7.2 Decidir la estrategia de registro
Tampoco conviene improvisar el registro sobre la marcha.
- Se usa en toda la máquina → registro per-machine mediante el instalador.
- Solo lo usa ese usuario → usar per-user de forma deliberada.
- Se queda cerrado dentro de la propia aplicación → considerar registration-free COM.
- Solo hace falta para desarrollo → confinarlo en un script de configuración de desarrollo explícito.
Registration-free COM permite mantener la información de activación en un manifiesto en lugar de en el registro, por lo que es un medio eficaz para reducir el infierno del registro. Tanto el registration-free COM del lado Win32 como el RegFree COM del lado .NET cuentan con documentación oficial. 27628
Como mecanismo, cuando COM procesa CoCreateInstance u otras llamadas similares, el orden es: primero busca en el contexto de activación, y si no hay información allí, consulta el registro. El manifiesto es el archivo que declara el contenido de ese contexto de activación. 27
Se necesitan dos manifiestos, y ambos se colocan en la misma carpeta que el exe.
El primero es el manifiesto de ensamblado del lado del componente (VendorCtl.manifest). En él se escribe qué archivo provee qué CLSID. 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>
El segundo es el manifiesto de aplicación del lado de la app (MyApp.exe.manifest). En él solo se escribe la dependencia respecto al ensamblado anterior.
<?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>
Los puntos donde es fácil equivocarse al escribirlos son estos: 29
- El
assemblyIdentitydel lado dependiente debe coincidir exactamente con elassemblyIdentitydel lado del componente. Si se desvía aunque sea uno solo dename,versionoprocessorArchitecture, no se resuelve. - El nombre del ensamblado y el nombre del DLL deben ser distintos. Cuando el manifiesto se coloca como archivo separado, el nombre del ensamblado y el del manifiesto deben ser distintos del nombre del DLL.
- Los nombres de elementos y atributos distinguen mayúsculas de minúsculas. Si se escribe
comClasscomoComClass, no se lee. processorArchitecturees la bitness en sí misma. No se puede resolver un OCX x86 desde una aplicaciónamd64. Asegúrese de que coincida siempre con la política de bitness del apartado 7.1.- Si se incrusta un OCX, puede hacer falta el conjunto de atributos
miscStatus. La información equivalente a la claveMiscStatusdel registro debe transcribirse en el manifiesto comomiscStatus/miscStatusContent, entre otros.
7.3 No dejar los artefactos generados fuera del control de versiones
Algo habitual en el mundo OCX / ActiveX es la dispersión de las dependencias:
- El OCX en sí.
- Los DLL de los que depende.
- El TLB.
- El
.lic. - El DLL de interop.
- El wrapper de AxHost.
- Los scripts de registro.
- El host de ejemplo.
Si todo esto existe solo en el equipo local de una persona, con toda seguridad habrá un accidente unos meses después.
Como mínimo, es más seguro dejar registrado en el mismo lugar que el código:
- Qué versión se está asumiendo.
- Qué se instala y en qué orden.
- Con qué comando se registra.
- Para cuál de x86 / x64 está destinado.
8. Consultas con las que este enfoque encaja bien
En este tema, incluso solo el diagnóstico y el orden de criterios, antes de entrar de lleno en una reforma integral, suelen aportar valor por sí solos.
Por ejemplo, encaja especialmente bien con consultas como estas:
- Quiero desglosar la causa de
0x80040154o0x80070005en bitness, registro y permisos. - Al pasar a Visual Studio 2022 se rompió el Diseñador, y quiero ver hasta dónde se puede rescatar.
- Quiero conservar el OCX del proveedor y acercar lo que lo rodea a .NET o C#.
- Quiero decidir hasta dónde alargar la vida de los activos fijados en x86, y a partir de qué punto aplicar bridge / wrap / replace.
- Quiero dejar de depender de
regsvr32manual y rediseñar la instalación / el despliegue.
Para la decisión entre reemplazar, envolver o conservar, también puede servir de referencia Cómo tratar hoy ActiveX / OCX - tabla de decisión entre conservar, envolver y reemplazar.
9. Resumen
Cuando surgen problemas en el desarrollo de componentes COM u OCX / ActiveX, la causa suele reducirse a estas cuatro:
- La bitness no encaja.
- El método de registro es incorrecto.
- El ámbito de registro (HKCU / HKLM, vista de 32 / 64 bits) está desajustado.
- Se da por normal un estado que solo se ve por casualidad gracias a los permisos.
Con el paso a 64 bits de Visual Studio 2022, los diseños antiguos que «funcionaban de algún modo» se han vuelto mucho más propensos a salir a la superficie. 12 Precisamente por eso, al trabajar con COM / OCX / ActiveX, el atajo consiste en alinear las premisas del entorno antes de escribir código.
Más que cuántas veces se ejecute regsvr32, ordenar estos puntos resuelve el problema mucho más rápido:
- Qué proceso actúa como host.
- Qué bitness tiene ese proceso.
- Dónde debería registrarse.
- Si ese registro realmente exige permisos de administrador.
- Si se está distinguiendo entre el Diseñador y el tiempo de ejecución.
Referencias
-
Microsoft Learn, Visual Studio 2022 version 17.0 Release Notes —
devenv.exe is now 64-bit only. ↩ ↩2 ↩3 -
Microsoft Learn, Troubleshoot 32-bit problems - Windows Forms — Visual Studio 2022 es un proceso de 64 bits y no puede cargar directamente .NET / COM / ActiveX de 32 bits; limitaciones del out-of-process designer. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Classes and Servers — Registro de COM, HKCU / HKCR, autorregistro (self-registration) y
DllRegisterServer. ↩ ↩2 ↩3 ↩4 -
Microsoft Learn, regsvr32 — Sintaxis y función de
regsvr32. ↩ ↩2 -
Microsoft Learn, Registro de ensamblados en COM — El registro COM de .NET Framework se hace con
Regasm.exe. ↩ ↩2 -
Microsoft Learn, Exposición de componentes de .NET Core a COM —
EnableComHosting, el.comhost.dllgenerado,regsvr32,EnableRegFreeCom. ↩ ↩2 ↩3 -
Microsoft Learn, Merged View of HKEY_CLASSES_ROOT — HKCR es la vista combinada (merged view) de HKLM y HKCU. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, HKEY_CLASSES_ROOT Key — Se recomienda que las aplicaciones que requieren permisos de administrador se registren en la configuración COM per-machine. ↩ ↩2
-
Microsoft Learn, COM Error Codes (Generic) (Winerror.h) —
REGDB_E_CLASSNOTREG (0x80040154), entre otros. ↩ -
Microsoft Learn, You receive 0x80070005 error when you try to register a DLL by using Regsvr32.exe — Ejemplo típico de fallo en el registro de un DLL por falta de permisos. ↩ ↩2
-
Microsoft Learn, Registry Redirector — Las vistas de registro de 32 y 64 bits en WOW64, el hecho de que la ubicación física de
HKLM\SoftwareseaWow6432Node, y que las aplicaciones no deben tocar directamente la ruta física. ↩ ↩2 ↩3 ↩4 ↩5 -
Microsoft Learn, File System Redirector —
%windir%\System32en Windows x64 y la redirección del sistema de archivos de WOW64. ↩ -
Microsoft Learn, Introducción a las consideraciones de compatibilidad de programas de 32 bits en versiones de 64 bits de Windows — Redirección de archivos y de registro mediante WOW64. ↩
-
Microsoft Learn, Regasm.exe (Assembly Registration Tool) — Función de
Regasm.exey opciones como/tlb. ↩ ↩2 ↩3 -
Microsoft Learn, Packaging a .NET Framework Assembly for COM — La biblioteca de tipos y
Regasm.exe /tlb. ↩ -
Microsoft Learn, Understanding Custom Build Steps and Build Events — Ejemplo de uso de
regsvr32.exeen un post-build event. ↩ -
Microsoft Learn, El registro de Windows para usuarios avanzados —
HKCU\Software\ClassesyHKLM\Software\Classes, y el comportamiento de HKCR. ↩ -
Microsoft Learn, Find, install, and manage extensions for Visual Studio — Tratamiento de las extensiones per-user al ejecutar en modo elevado. ↩
-
Microsoft Learn, MFC ActiveX Controls: Licensing an ActiveX Control — Licencia de tiempo de diseño / tiempo de ejecución,
.LIC. ↩ -
Microsoft Learn, Application Settings, MFC ActiveX Control Wizard — Generación de la licencia de tiempo de ejecución y el archivo
.lic. ↩ -
Microsoft Learn, License information for this component not found. You don’t have an appropriate license to use this functionality in the design environment. ↩
-
Microsoft Learn, Aximp.exe (Windows Forms ActiveX Control Importer) — Convierte ActiveX en un wrapper para WinForms. ↩
-
Microsoft Learn, AxHost Class — El wrapper basado en AxHost que genera el ActiveX Control Importer. ↩
-
Microsoft Learn, Inicialización de la biblioteca COM —
CoInitializeEx, la inicialización por cada subproceso, el bucle de mensajes de STA. ↩ ↩2 ↩3 -
Microsoft Learn, Apartamentos de un solo subproceso (Single-Threaded Apartments) — El bucle de mensajes de STA, el marshaling,
ThreadingModel. ↩ ↩2 ↩3 ↩4 ↩5 -
Microsoft Learn, Creación de objetos COM sin registro (Registration-Free COM) — COM sin necesidad de registro mediante el contexto de activación (activation context). ↩ ↩2
-
Microsoft Learn, Interoperabilidad COM sin necesidad de registro — La interoperabilidad COM sin registro (registration-free) de .NET Framework. ↩
-
Microsoft Learn, Assembly Manifests — Los atributos de
assembly/assemblyIdentity/dependency/file/comClass, la necesidad de que elassemblyIdentitydel lado REF coincida con el del lado DEF, el tratamiento de mayúsculas y minúsculas en los nombres, y los atributos del tipomiscStatus. ↩ ↩2
Artículos relacionados
Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.
Qué son COM, ActiveX y OCX - Diferencias y relación explicadas
Explicamos qué son COM, ActiveX y OCX: diferencias, relación, vínculo con OLE, dónde se usan y cómo abordarlos hoy desde una perspectiva ...
ActiveX / OCX: cómo tratarlos hoy — tabla de decisión para mantener, envolver o reemplazar
Al encontrar un ActiveX / OCX, cómo decidir entre mantenerlo, envolverlo o reemplazarlo, considerando 32 bits / 64 bits, registro, depend...
Qué es COM — por qué el diseño de Windows COM sigue siendo hermoso hoy
Explica qué es COM: el diseño de interfaces de Windows COM, IUnknown, GUID y la compatibilidad binaria, y por qué sigue vigente hoy.
Cómo funcionan el portapapeles y arrastrar y soltar — Gestionar correctamente la transferencia de datos OLE en aplicaciones empresariales
Una tabla de Excel se deforma al pegarla y deja de poder pegarse si cierra el origen: es el portapapeles colocando el mismo contenido en ...
Externalización y desarrollo por encargo de una app Windows: qué aclarar antes de solicitarlo
Antes de encargar la externalización o el desarrollo por encargo de una app Windows, estos son los puntos a aclarar: revisión de software...
Temas relacionados
Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.
Temas técnicos de Windows
Portal sobre desarrollo de Windows, investigación de fallos y aprovechamiento de activos existentes.
Migración de ActiveX
Decisiones para conservar, encapsular o sustituir componentes COM / ActiveX / OCX.
Servicios relacionados con este tema
El artículo está directamente relacionado con los siguientes servicios.
Reutilización y migración de activos existentes
Reutilización y migración de activos COM / ActiveX / OCX y dependencias de 32 o 64 bits.
Preguntas frecuentes
Preguntas habituales en las consultas sobre el tema del artículo.
- ¿Cómo se investiga el error 0x80040154 (Class not registered)?
- Oficialmente es REGDB_E_CLASSNOTREG, literalmente un error de «clase no registrada», pero eso no siempre significa que esté completamente sin registrar. También ocurre cuando solo está registrado en la vista de registro del otro bitness, o solo en el lado HKCU, visible únicamente para ese usuario. El camino más rápido es fijar primero si el proceso host es de 32 o 64 bits, después comprobar si el medio de registro es el correcto, y por último verificar en qué combinación de HKCU / HKLM y vista de 32 / 64 bits quedó registrado.
- ¿Por qué no funciona aunque se registre con regsvr32?
- regsvr32 está pensado para servidores COM in-proc nativos (DLL / OCX) que exportan DllRegisterServer, y no es un comando mágico que registre cualquier cosa. La exposición COM de un ensamblado de .NET Framework se hace con Regasm.exe, y en .NET 5 en adelante el flujo consiste en registrar con regsvr32 el .comhost.dll generado mediante EnableComHosting. Además, en Windows x64 hay que usar el regsvr32 de System32 para el lado de 64 bits y el de SysWOW64 para el de 32 bits; si se registra en el lado equivocado, el registro tiene éxito pero queda invisible para el proceso objetivo.
- ¿Por qué en Visual Studio 2022 solo falla el Diseñador?
- Porque devenv.exe, en Visual Studio 2022, es un proceso de 64 bits y no puede cargar directamente componentes COM / ActiveX de 32 bits. Aunque la aplicación en tiempo de ejecución pueda funcionar en x86, el Diseñador se ejecuta en el proceso de 64 bits de Visual Studio, lo que produce la contradicción de que la ejecución sigue viva pero solo el Diseñador falla. Aunque se cambie a AnyCPU, el problema persiste si, más adelante en la cadena, se depende de un COM / ActiveX fijado a 32 bits.
- ¿Por qué funciona al ejecutarlo como administrador pero no con permisos normales?
- En el fondo, esto suele ser un desajuste en el ámbito de registro o en el diseño de la instalación, más que un problema de permisos en sí. COM mira primero HKCU\Software\Classes, y HKEY_CLASSES_ROOT es la vista combinada (merged view) de HKLM y HKCU. Cuando se registra manualmente con el usuario del desarrollador A, es normal que funcione con la cuenta de A pero no con otros usuarios ni con cuentas de servicio. Lo básico es distinguir entre el registro per-user de desarrollo y el registro per-machine de producción que hace el instalador, y separar la compilación del registro.
Perfil del autor
Página de presentación del autor del artículo.
Go Komura
Representante de KomuraSoft LLC
Especializado en desarrollo de software para Windows, consultoría técnica e investigación de fallos, sobre todo en proyectos con sistemas existentes y errores difíciles de reproducir.