Trampas de registro y bitness en el desarrollo COM/OCX/ActiveX

· Actualizado el: · · 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 regsvr32 y, 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:

  1. 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.
  2. 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
  3. regsvr32 no 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 usa Regasm.exe, y en .NET 5+ / .NET 6+ / .NET 8+ el flujo consiste en registrar el .comhost.dll generado. 3456
  4. 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:

  • regsvr32 se ejecutó con éxito.
  • Pero la aplicación sigue mostrando 0x80040154.
  • Al mirar el registro, parece que está ahí.
  • Pero se está mirando otra vista.

1113

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 /codebase graba 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 /codebase lleven nombre seguro (strong name).
  • /regfile no se puede combinar con /unregister ni con /tlb. Además, lo que /regfile vuelca son solo las entradas de las clases administradas: no genera TypeLibID ni InterfaceID. Pensar que «con distribuir el .reg basta para el registro» se queda corto.

Cuando se mezclan estas diferencias, el patrón habitual es este:

  • Se ejecuta regsvr32 sobre 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:

  1. El OCX / ActiveX original en sí.
  2. La biblioteca de tipos.
  3. El interop / wrapper generado.
  4. 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.

Por qué se bloquea una llamada sin bucle de mensajes en un STADiagrama 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 indefinidamenteOCX / ActiveXSubproceso STACola de mensajes de la ventana ocultaLlamador desde otro subprocesoOCX / ActiveXSubproceso STACola de mensajes de la ventana ocultaLlamador desde otro subprocesoSi el bucle de mensajes está detenido,esta extracción no ocurre y el llamador queda esperandoLa llamada al método se apila como un mensajeEl bucle de mensajes la extraeEl procedimiento de ventana llama al método correspondienteSe devuelve el valor de retorno

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.Run o 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\Classes
  • HKLM\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 InprocServer32 existe 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 assemblyIdentity del lado dependiente debe coincidir exactamente con el assemblyIdentity del lado del componente. Si se desvía aunque sea uno solo de name, version o processorArchitecture, 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 comClass como ComClass, no se lee.
  • processorArchitecture es la bitness en sí misma. No se puede resolver un OCX x86 desde una aplicación amd64. 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 clave MiscStatus del registro debe transcribirse en el manifiesto como miscStatus / 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 0x80040154 o 0x80070005 en 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 regsvr32 manual 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:

  1. La bitness no encaja.
  2. El método de registro es incorrecto.
  3. El ámbito de registro (HKCU / HKLM, vista de 32 / 64 bits) está desajustado.
  4. 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

  1. Microsoft Learn, Visual Studio 2022 version 17.0 Release Notesdevenv.exe is now 64-bit only 2 3

  2. 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

  3. Microsoft Learn, Classes and Servers — Registro de COM, HKCU / HKCR, autorregistro (self-registration) y DllRegisterServer 2 3 4

  4. Microsoft Learn, regsvr32 — Sintaxis y función de regsvr32 2

  5. Microsoft Learn, Registro de ensamblados en COM — El registro COM de .NET Framework se hace con Regasm.exe 2

  6. Microsoft Learn, Exposición de componentes de .NET Core a COMEnableComHosting, el .comhost.dll generado, regsvr32, EnableRegFreeCom 2 3

  7. Microsoft Learn, Merged View of HKEY_CLASSES_ROOT — HKCR es la vista combinada (merged view) de HKLM y HKCU.  2 3 4 5

  8. 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

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

  10. 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

  11. 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\Software sea Wow6432Node, y que las aplicaciones no deben tocar directamente la ruta física.  2 3 4 5

  12. Microsoft Learn, File System Redirector%windir%\System32 en Windows x64 y la redirección del sistema de archivos de WOW64. 

  13. 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. 

  14. Microsoft Learn, Regasm.exe (Assembly Registration Tool) — Función de Regasm.exe y opciones como /tlb 2 3

  15. Microsoft Learn, Packaging a .NET Framework Assembly for COM — La biblioteca de tipos y Regasm.exe /tlb

  16. Microsoft Learn, Understanding Custom Build Steps and Build Events — Ejemplo de uso de regsvr32.exe en un post-build event. 

  17. Microsoft Learn, El registro de Windows para usuarios avanzadosHKCU\Software\Classes y HKLM\Software\Classes, y el comportamiento de HKCR. 

  18. Microsoft Learn, Find, install, and manage extensions for Visual Studio — Tratamiento de las extensiones per-user al ejecutar en modo elevado. 

  19. Microsoft Learn, MFC ActiveX Controls: Licensing an ActiveX Control — Licencia de tiempo de diseño / tiempo de ejecución, .LIC

  20. Microsoft Learn, Application Settings, MFC ActiveX Control Wizard — Generación de la licencia de tiempo de ejecución y el archivo .lic

  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) — Convierte ActiveX en un wrapper para WinForms. 

  23. Microsoft Learn, AxHost Class — El wrapper basado en AxHost que genera el ActiveX Control Importer. 

  24. Microsoft Learn, Inicialización de la biblioteca COMCoInitializeEx, la inicialización por cada subproceso, el bucle de mensajes de STA.  2 3

  25. Microsoft Learn, Apartamentos de un solo subproceso (Single-Threaded Apartments) — El bucle de mensajes de STA, el marshaling, ThreadingModel 2 3 4 5

  26. Microsoft Learn, Registry Keys Affected by WOW64 — Que HKLM\SOFTWARE\Classes\CLSID y HKCU\SOFTWARE\Classes\CLSID son objeto de redirección, y que HKCR es la vista combinada de ambas. 

  27. 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

  28. Microsoft Learn, Interoperabilidad COM sin necesidad de registro — La interoperabilidad COM sin registro (registration-free) de .NET Framework. 

  29. Microsoft Learn, Assembly Manifests — Los atributos de assembly / assemblyIdentity / dependency / file / comClass, la necesidad de que el assemblyIdentity del lado REF coincida con el del lado DEF, el tratamiento de mayúsculas y minúsculas en los nombres, y los atributos del tipo miscStatus 2

Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.

Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.

El artículo está directamente relacionado con los siguientes servicios.

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.

Volver al blog