How to Handle ActiveX / OCX Today - A Keep / Wrap / Replace Decision Table

· Updated: · · COM, ActiveX, OCX, .NET, Windows Development, Modernization

Revision history (2 updates, last updated Sep 1, 2026)

A log of the changes made to this article. Where a pre-update version was archived, it stays readable at a permanent DOI link.

Retranslated as a full translation of the Japanese original. The previous English version was an abridgement that carried only part of the source, so sections, tables, Mermaid diagrams, figure captions and FAQ entries were missing. All of them have been restored to match the Japanese original, and the technical claims are the same as in the Japanese version. Read the version before this update (DOI: 10.5281/zenodo.22170270)
Added DllSurrogate to 5.2 as an option worth weighing before writing a helper EXE of your own. If the component is an in-proc COM server, adding an AppID to its CLSID and an empty DllSurrogate under that AppID makes it out-of-process without a line of your own code. A surrogate does not erase the bitness difference, though: only the same-process constraint goes away, and calls become out-of-process, carrying marshaling and inter-process communication cost. A new table draws the line between the cases a surrogate covers and the cases that still need a helper EXE of your own, and two references were added. The registration steps themselves live in another article, so this one only links to them. Read the version before this update (DOI: 10.5281/zenodo.21614475)
First published
Cite this article(DOI: 10.5281/zenodo.21614474)

This article is archived on Zenodo. Below are both the DOI that always resolves to the latest version and the DOI pinned to the version you are reading.

Go Komura (2026). How to Handle ActiveX / OCX Today - A Keep / Wrap / Replace Decision Table. KomuraSoft LLC. https://doi.org/10.5281/zenodo.21614474 https://comcomponent.com/en/blog/2026/03/12/001-activex-ocx-keep-wrap-replace-decision-table/

DOI (latest version)
10.5281/zenodo.21614474
DOI (this version)
10.5281/zenodo.22217131

Projects where the words ActiveX / OCX come up usually have a slightly heavy air about them.

  • VB6 or old C++ / MFC apps are still in active service
  • The SDK for industrial equipment or measurement instruments ships only as an OCX
  • The internal web app assumes ActiveX and cannot escape IE mode
  • You want to consolidate on 64-bit, but a single OCX is shaking its head

That said, both “it is old, so throw it all away” and “it works, so preserve it forever” are sloppy. What matters is telling apart whether that ActiveX / OCX is a mere UI component, or a boundary surface carrying business rules and device specifications.

This article organizes, in an order that makes the decision easier, which of keep, wrap, or replace you should choose when you find ActiveX / OCX.

The targets are cases like these, for example.

  • Existing desktop apps in the VB6 / MFC / WinForms family
  • Phased migration to C# / .NET
  • Legacy screens involving WebBrowser / IE mode
  • Windows apps containing vendor-supplied ActiveX controls

Table of Contents

  1. The Conclusion First (In One Line)
  2. What This Article Means by ActiveX / OCX
  3. The Decision Table to Look at First
    • 3.1. The Big Picture
    • 3.2. The Case for Keeping
    • 3.3. The Case for Wrapping
    • 3.4. The Case for Replacing
    • 3.5. Browser Dependencies Are a Separate Category
  4. Points That Easily Distort the Decision
    • 4.1. Is It a UI Component, or a Component Carrying Specifications?
    • 4.2. 32-bit / 64-bit and Process Boundaries
    • 4.3. Registration, Distribution, Privileges, Licensing
    • 4.4. STA / Message Loops / Callbacks
    • 4.5. Do Tests Exist? Can You Observe It?
  5. Recommendations by Typical Pattern
    • 5.1. An Internal Desktop App That Still Runs Stably
    • 5.2. You Want to Bring a 32-bit OCX to the 64-bit Side
    • 5.3. Screens Built on IE / WebBrowser
    • 5.4. ActiveX Carrying Device Control or Proprietary Specifications
  6. Common Anti-Patterns
  7. Checklist for Starting a Migration
  8. A Rough Decision Guide
  9. Summary
  10. Consultations That Fit Well
  11. References

In the diagram a solid line marks a relation that always holds and a dashed line marks a conditional one (the conditions are given per relation on the detail page). The full list of relations (24 in total, with evidence and certainty) and the definitions of the main concepts are collected on the knowledge map detail page (in Japanese). Data: JSON-LD / Turtle

1. The Conclusion First (In One Line)

  • When you see ActiveX / OCX, the first thing to judge is not whether it is old but what that component has taken on
  • If it is a mere UI component, replacement is comparatively easy
  • If it carries device control, report generation, proprietary file formats, or years of operational habits, wrapping it first is safer than jumping straight into reimplementation
  • If it runs stably on the desktop and the scope of change is small, keeping it is a perfectly valid decision
  • Browser-hosted ActiveX dependencies can be kept on life support, but their long-term prospects are limited, so view them with replacement as the priority
  • You cannot load a 32-bit OCX directly into a 64-bit process. No amount of willpower crosses this line
  • Registration, dependent DLLs, administrator privileges, licensing, STA / MTA - the friction outside the implementation tends to be where things get hard
  • Both “just rewrite everything” and “freeze it forever out of fear” are high-risk choices

In short, the order of judgment is this.

  1. What does that OCX hold?
  2. Does it need to run in the same process?
  3. Will 32-bit / 64-bit, registration, or browser dependencies get you stuck?
  4. Should you build a testable boundary before replacing?

Viewed in this order, things become considerably easier to sort out.

Order of judgmentLook in this order - what that OCX holds, whether it has to run in the same process, whether bitness or registration or browser dependencies will block you, and whether to build a testable boundary before replacing.What does that OCX holdIs the same process requiredBlocked by bitness, registration, or the browserBuild the boundary before replacing

Figure 1: Not whether it is old - looking in this order makes keep, wrap, and replace far easier to sort out.

2. What This Article Means by ActiveX / OCX

First, let us fix how the words are used in this article.

Term Meaning in this article
COM Windows’ binary-compatible component model. It is the foundation for public interfaces, registration, the Apartment Model, and so on
ActiveX / OCX In practice, the term tends to be used collectively for COM-based controls and their surrounding assets. It often covers .ocx UI controls in particular, and components embedded in IE or in a container
WebBrowser / IE-family dependencies Even when it is not ActiveX itself, this covers embedded browsers and integrations premised on the IE worldview. For decision purposes it is a very similar problem

Strictly speaking, ActiveX and COM are not the same thing. But the points where they hurt in practice are quite similar.

  • Do the 32-bit / 64-bit pieces fit together?
  • How do you distribute registration and dependent DLLs?
  • Which host or container does it run in?
  • Will STA, message loops, or callbacks get you stuck?
  • Are browser dependencies still lurking?

This article handles these practical decision points together.

Where the pain shows up in practiceActiveX and COM are strictly speaking not the same thing, but the places where they hurt in practice are very similar - whether the bitness fits together, how registration and dependent DLLs are distributed, which host it runs in, the STA and callback assumptions, and browser dependencies.ActiveX / OCX projectDoes the bitness fit togetherDistributing registration and dependent DLLsWhich host it runs inSTA and callback assumptionsAre browser dependencies still there

Figure 2: Even though ActiveX and COM are strictly different things, the places where practice gets stuck cluster right around here.

Before going further, here is a summary of the abbreviations that appear from here on as if they were obvious. Being told in the chapter 7 checklist to identify every ProgID and CLSID is useless if those terms are not clear.

Term Expansion / full name Meaning
CLSID Class ID The GUID that uniquely identifies a COM component’s implementation (its class). Registry registration is keyed on this value as well
ProgID Programmatic Identifier A human-readable name attached to a CLSID. A string such as Excel.Application
IID Interface ID The GUID that uniquely identifies a COM interface. It is a different thing from a CLSID
TLB Type Library A file holding type information such as interfaces, methods, and argument types in binary form. It is what lets VB6 and .NET call the component with full typing
RegAsm Assembly Registration Tool A tool that ships with the .NET Framework. It registers a .NET assembly in the registry so that COM can use it
AxHost - The base class for hosting an ActiveX control on Windows Forms
AxImp ActiveX Control Importer A tool that generates a Windows Forms wrapper assembly from an OCX
in-proc / out-of-proc In-process / out-of-process Whether the component runs in the same process as the caller (a DLL or OCX) or in a separate process (an EXE server)
LocalServer - The form in which a COM server runs as an EXE in a separate process. It can cross the bitness wall and isolate crashes
Reg-Free COM / side-by-side Registration-free COM A mechanism that resolves COM from information written in the application’s manifest, with no registry registration
design-time / runtime licenses Development time / execution time With vendor controls, the licensing terms are sometimes split between placing the control on a form on a development machine and running it at the deployment site
adapter / facade - A design shape that replaces an existing fine-grained API with a coarse one that suits your own needs
STA / MTA Single / Multi Threaded Apartment COM’s threading models. They change the rules about which thread may call what

3. The Decision Table to Look at First

3.1. The Big Picture

Start with this table, and the rough policy mostly settles itself.

Situation First choice Reason
It depends on browser-hosted ActiveX Lean toward replacing Edge itself does not support ActiveX, and IE mode is positioned as a life-support measure
An OCX runs stably in a desktop app and the scope of change is small Lean toward keeping The cost of breaking it now is usually higher
You want to modernize only the surroundings to .NET, but the control’s behavior is unreadable Lean toward wrapping Sorting out the boundary first is safer
You want to put a 32-bit OCX directly into a 64-bit process Wrap / change the configuration It is a boundary that cannot be crossed in-proc
It is used only as a UI component, and an alternative exists Lean toward replacing A surface-level swap is usually enough
The vendor is gone, and signing, registration, or dependent DLLs break every single time Lean toward replacing The operational cost has surfaced as technical debt
It embeds device control, reporting, or a proprietary protocol Lean toward wrapping Until the behavior is pinned down, the replacement cost is unreadable
YesNoYesYesNoNoYesNoYesNoYou have ActiveX / OCXBrowser dependency?Prioritize replacementIE mode is life supportMainly a UI component?Is an equivalent alternative available?Consider replacingWrap first and sort out the boundaryCarries device control / proprietary specs / report logic?Wrap firstassemble tests, then replace in stagesRegistration / bitness / distribution painful?Rethink the configurationconsider out-of-proc / separate-process bridging / Reg-Free COMKeeping it is also realistic

Figure 3: The first branch is whether there is a browser dependency; after that the policy follows from whether it is a UI component, whether an alternative exists, and whether it carries specifications.

Below, we look at each pattern in turn.

3.2. The Case for Keeping

Being ActiveX / OCX does not automatically make something a replacement target. When conditions like these line up, keeping it is quite often simply the cheapest option.

  • The usage scope is closed, and the operating environment is fixed, such as internal distribution or bundling with equipment
  • That control still runs stably, and the change requests are not large
  • The vendor is still in business, or your own team can do the minimum maintenance
  • It is not browser-dependent, and it is self-contained inside an existing desktop host
  • The 32-bit / 64-bit premise does not have to change for the foreseeable future

What matters here is that keeping is not the same as neglecting. If you keep it, you want at least this much in place.

  • Document the supported OS, the bitness, the required dependent DLLs, and the registration steps
  • Move installation, registration, and unregistration into scripts or an installer instead of hand-written memos
  • Prepare a smoke test on a clean environment
  • Funnel calls into the control into a single place as far as possible, instead of scattering them across the app

The worst outcome is continuing “it works, so do not touch it” for ten years until nobody can explain the premises anymore. The more you lean toward keeping, the more important making the premises visible becomes.

Visibility that comes with the decision to keepKeeping is not neglecting - it comes as a set with documenting the premises, turning the registration steps into scripts, preparing a smoke test on a clean environment, and consolidating the call sites.Decision to keepDocument the premisesTurn registration steps into scriptsPrepare a smoke testConsolidate calls into one place

Figure 4: Keeping is not neglecting - the more you lean toward keeping, the more the work of making the premises visible has to come with it.

3.3. The Case for Wrapping

In practice, this choice is where most of the work is.

“Wrapping” here means confining the ActiveX / OCX inside a narrow boundary, and presenting it to the surroundings as a new API or a new UI component.

This is quite effective. The reason is that entering a full reimplementation while the old component’s behavior still cannot be read in full tends to become a double burden of specification archaeology and defect reproduction. It is safer to isolate the old component first and tidy only the boundary.

The shape of the wrapping choiceConfining the ActiveX / OCX inside a narrow boundary and presenting it to the surroundings as a new API or UI component avoids the double burden of specification archaeology and defect reproduction that a full reimplementation brings while the behavior still cannot be read in full.ActiveX / OCXConfine it inside a narrow boundaryPresent it as a new APIThe surroundings see only the new entry pointAvoid the double burden of archaeology and reproduction

Figure 5: Wrapping is isolating the old component and building a new entry point, and it pays off as the stage before a full reimplementation.

There are several established shapes for wrapping.

Wrapping approach Suited for What to watch
WinForms host + AxHost / Aximp Embedding in an existing desktop screen; keeping only a few screens STA, events, design-time dependencies, licensing
32-bit helper EXE / COM LocalServer / separate-process bridging Moving toward 64-bit; isolating crashes Inter-process communication, startup order, monitoring, deployment
A COM-compatible facade on the .NET side Updating the internals while keeping the existing COM callers IID / CLSID / TLB / registration method / bitness

Once the approach is settled, here is where the first step lies.

Wrapping approach What to do first Detailed steps
WinForms host + AxHost In Visual Studio, right-click the toolbox, open Choose Toolbox Items, and pick the target from the COM Components tab. On the command line, use aximp Registration and Bitness Pitfalls in COM/OCX/ActiveX Development
32-bit helper EXE / LocalServer Register the 32-bit EXE as a COM server and call it out-of-proc from the 64-bit side A Worked Example of a COM Bridge for Calling a 64-bit DLL from a 32-bit App
Reg-Free COM Write file and comClass into the application’s manifest and let COM resolve without registry registration What Is Reg-Free COM - Using COM Without Registration
A COM-compatible facade on the .NET side Expose the .NET side to COM and, if needed, generate a TLB with dscom Using a .NET 8 DLL from VBA with Full Typing - COM Exposure and dscom TLB

Run aximp from the Visual Studio Developer Command Prompt.

aximp C:\path\to\MyControl.ocx

This generates two things: a runtime callable wrapper for the COM types, and a Windows Forms wrapper derived from AxHost. Note that the file names come from the ProgID, not from the original file name. In the Microsoft documentation’s example, msdxm.ocx produces MediaPlayer.dll and AxMediaPlayer.dll. You add the latter as a reference and place AxMediaPlayer on the form.

What aximp generatesPassing an OCX to aximp generates two assemblies - a runtime callable wrapper for the COM types and an AxHost-derived Windows Forms wrapper - and the latter is added as a reference and placed on the form. The file names come from the ProgID, not from the original file name.The target OCXRun aximpWrapper DLL for the COM typesAxHost-derived wrapper DLLAdd as a reference and place on the formFile names come from the ProgID

Figure 6: aximp emits two DLLs, and the one you place on the form is the AxHost-derived wrapper.

For Reg-Free COM, the minimal application manifest looks like this.

<?xml version="1.0" encoding="utf-8"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity type="win32" name="MyApp" version="1.0.0.0" />
  <file name="MyControl.ocx">
    <comClass
      clsid="{01234567-89AB-CDEF-0123-456789ABCDEF}"
      threadingModel="Apartment"
      progid="MyCompany.MyControl.1" />
  </file>
</assembly>

With the LocalServer approach, the registry holds the EXE path under HKEY_CLASSES_ROOT\CLSID\{CLSID}\LocalServer32. An EXE server built with ATL or MFC conventionally supports self-registration and unregistration through MyServer.exe /regserver and /unregserver. Because the registry view is split per bitness, though, always keep in mind that a 32-bit EXE is registered in the 32-bit view of the registry.

LocalServer registration and the registry viewWith the LocalServer approach the EXE path goes under LocalServer32 beneath the CLSID and the regserver convention allows self-registration, but the registry view is split per bitness and a 32-bit EXE is registered in the 32-bit view.32-bit64-bitEXE serverRegister the path in LocalServer32What bitness is the EXE?Registered in the 32-bit viewRegistered in the 64-bit view

Figure 7: Where a LocalServer gets registered is decided by the per-bitness split of the registry views.

What matters most is not copying 200 of the old APIs verbatim when you wrap. Doing that merely imports the old component’s quirks straight into the new code.

When wrapping, keeping these in mind improves things considerably.

  • Use coarse-grained methods
  • Do not let screen code touch the OCX directly
  • Capture the logs you need on failure at the boundary
  • Decide the responsibility for timeouts, retries, and exception translation at the boundary
  • Make the future replacement swappable behind the same interface

Sometimes you want to keep only the COM entry point on the new .NET side. In that case, the realistic configuration is to update the internals while maintaining only the COM contract. The .NET Framework-era instinct that “RegAsm will do” does not necessarily hold, though. How the modern .NET COM host, TLBs, bitness, and Registration-Free COM are handled is much easier later if you design it up front. Using a .NET 8 DLL from VBA with Full Typing - COM Exposure and dscom TLB and What Is Reg-Free COM - Using COM Without Registration each cover this ground down to the steps.

Responsibilities decided at the boundaryWhen wrapping, use coarse-grained methods, keep screen code from touching the OCX directly, decide at the boundary who is responsible for logging and timeouts and exception translation, and make a future replacement swappable behind the same interface.The wrapping boundaryCoarse-grained methodsCapture logs at the boundaryTimeouts and exception translationSwap a future replacement behind the same entry pointDo not copy 200 old APIs

Figure 8: The value of wrapping lies in gathering responsibilities at the boundary, and copying the old API verbatim throws that value away.

3.4. The Case for Replacing

Replacement suits cases where the problem is mainly surface-level age.

In situations like these, view replacement as the priority.

  • That ActiveX is used only as a UI component
  • The vendor ships a successor for .NET / WPF / WebView2
  • Browser dependence or the IE premise is holding you back
  • Registration, signing, administrator privileges, or security settings trip you up every time
  • Tests or business scenarios exist that can validate an alternative implementation

Conversely, going all in on discarding a component that also carries device control or report logic, purely because it looks old, usually turns into a mess.

If you do replace, start with the UI.

  • Grids
  • Calendars
  • Trees
  • Browser display areas
  • Simple input helpers

These are comparatively easy to replace. On the other hand, some things look like UI but are dense inside.

  • Vendor-supplied device-control ActiveX
  • Controls fused with printing or report generation
  • Controls that embed reading and writing of a proprietary file format
  • Controls carrying COM callbacks or threading assumptions

Misjudge this difference, and the effort estimate falls apart at once.

Telling replacement candidates apartA component used only as a UI part with an alternative available is easy to replace, but a component that looks like UI while carrying device control, reporting, proprietary formats, or threading assumptions is dense inside, and discarding it all at once turns into a mess.Only surface-level ageCarries a mass of specificationsWhat is behind the appearanceEasy to replaceDiscarding it at once turns into a messMove to the wrapping decision first

Figure 9: Whether something is a replacement candidate comes down to telling surface-level age apart from density inside.

3.5. Browser Dependencies Are a Separate Category

This really is a separate category.

Browser-hosted ActiveX, unlike desktop OCX, has a very weak case for further investment going forward.

The reason is simple: modern browser platforms no longer treat this as their main battleground. Microsoft Edge itself does not support ActiveX. IE mode, on the other hand, uses the IE-family engine for configured sites and can serve as a compatibility layer for running some IE features, including ActiveX.

In other words:

  • Life support to keep it running now is possible
  • But as a long-term design, the prospects are not broad

That is the situation.

Where browser-hosted ActiveX standsMicrosoft Edge itself does not support ActiveX and IE mode is a compatibility layer that uses the IE-family engine for configured sites, so life support to keep things running now is possible but the long-term design prospects are not broad.ActiveX in the browserDoes not run in Edge itselfIE mode can keep it aliveLong-term prospects are limitedView it with replacement as the priority

Figure 10: For browser-dependent ActiveX, keep life support and permanent design separate, and treat replacement as the priority.

The same thing happens with the WebBrowser control embedded in a Windows app. WebBrowser drags the IE worldview along, so if you simply want to display HTML, making WebView2 the first candidate for any new work from now on is the natural move.

What to watch out for, though, is that WebView2 is not a complete drop-in replacement for WebBrowser.

  • Scripts premised on the IE DOM
  • ActiveX dependencies
  • Assumptions around window.external
  • Behavior premised on security zones and the intranet

None of this carries over as it is. If you replace, you have to redesign not just the rendering engine but the connection surface between the browser and native code.

What does not carry over to WebView2WebView2 is not a complete drop-in replacement for the WebBrowser control, and scripts premised on the IE DOM, ActiveX dependencies, assumptions around window.external, and behavior premised on security zones do not carry over as they are.From WebBrowser to WebView2First candidate if it is only HTML displayWhat does not carry overScripts premised on the IE DOMActiveX dependenciesAssumptions around window.externalSecurity zone behavior

Figure 11: WebView2 is a replacement for the rendering engine, but it does not inherit the connection surface of the IE worldview.

4. Points That Easily Distort the Decision

4.1. Is It a UI Component, or a Component Carrying Specifications?

This is the most important one.

For an old grid or calendar, checking visual and event compatibility carries the conversation a long way. ActiveX carrying device control, reporting, or a proprietary format, on the other hand, has a mass of specifications behind its appearance.

Even things that look like the same control on a screen actually span this much range.

  • A plain list-display component
  • A component firing commands at equipment over a proprietary protocol
  • A component that internally handles timeouts, reconnection, retransmission, and even swallowing exceptions
  • A component shouldering compatibility of printing and export formats

Reimplementing the latter from scratch usually turns into a specification-archaeology project. Wrapping first is safer here.

UI component or a component carrying specificationsEven when they look like the same control on a screen, there is a wide range between a plain list-display component and one that carries device commands, reconnection, and report compatibility, and reimplementing the latter straight away becomes a specification-archaeology project.A control on the screenA plain display componentA component carrying a mass of specificationsChecking compatibility moves things alongReimplementing straight away becomes archaeologyWrapping first is safer

Figure 12: The most important distinction. Even when the appearance is identical, whether a mass of specifications sits behind it changes how you proceed.

4.2. 32-bit / 64-bit and Process Boundaries

This is often overlooked, but it is quite fundamental.

An in-proc OCX has to match the bitness of the process that loads it. That is, you cannot load a 32-bit OCX directly into a 64-bit app.

The realistic options at that point come down to roughly these three.

  • Keep the host app 32-bit as well for the time being
  • Confine it in a separate 32-bit process and connect it to the 64-bit side through IPC or out-of-proc COM
  • Replace the OCX dependency starting wherever it can be removed first

“It is Any CPU, so it will work out somehow” mostly does not help here. Even when you build a COM-compatible facade on the new .NET side, how the managed code looks and what bitness the actual COM host runs at are separate matters. Start sloppily here and you get the nasty case where the build passes but nothing runs at the deployment site.

Three options for taking a 32-bit OCX to 64-bitA 32-bit OCX cannot be loaded in-proc into a 64-bit process, so the options are to keep the host 32-bit, to confine the OCX in a separate 32-bit process and connect through IPC or out-of-proc COM, or to replace the dependency starting where it can be removed.A 32-bit OCX cannot go in-procKeep the host 32-bitConfine it in a separate 32-bit processReplace where it can be removedConnect through IPC or out-of-proc COM

Figure 13: The bitness wall cannot be crossed by willpower, and the realistic options come down to these three.

4.3. Registration, Distribution, Privileges, Licensing

Technically callable, yet dead on distribution. This is quite common with ActiveX / OCX.

The likely trouble spots are these.

  • The prerequisites for regsvr32 live in someone’s head
  • The placement of dependent DLLs is implicit
  • Administrator privileges are required, but that never made it into the operational procedures
  • A vendor control splits its design-time and runtime licenses
  • It works on the development machine but not in a clean environment

Any of these can halt a project without a single line of code being touched.

Registration-free configurations and side-by-side placement do make some cases easier, but they are not magic dust. Compatibility with the container side and with the distribution method still has to be checked.

In short, an ActiveX / OCX migration is distribution design, not just implementation. Put this part off, and you fall over spectacularly at the end.

Where distribution stallsregsvr32 prerequisites living in someone's head, implicit placement of dependent DLLs, administrator privileges missing from the operational procedures, and licenses split between design time and runtime can halt a project without a single line of code being touched.Distribution designregsvr32 lives in someone's headDependent DLL placement is implicitAdmin privileges are outside the procedureLicensing is split in twoStalls without touching any code

Figure 14: Technically callable yet dead on distribution - design for this trouble spot separately from the implementation.

4.4. STA / Message Loops / Callbacks

ActiveX / OCX is not merely a DLL call. It can carry assumptions about COM’s threading model and about message loops.

The cases to watch out for in particular are these.

  • It is stable only on the UI thread
  • It assumes STA, but is being called carelessly from MTA
  • A callback comes back in the middle of a synchronous call
  • The threading assumption for receiving events is vague

These first show up wearing the face of a ghost story: “it freezes now and then”, “events sometimes do not arrive”. The substance, though, is usually a violated assumption.

So whether you wrap or replace, pin down first which thread creates it, which thread calls it, and where events are received.

Pin the threading assumptions down firstUnless you pin down first which thread creates the control, which thread calls it, and where events are received, you end up with ghost stories about occasional freezes and missing events that are really violated assumptions.Three things to pin down firstWhich thread creates itWhich thread calls itWhere events are receivedVagueness turns into violated-assumption ghost stories

Figure 15: What is behind “it freezes now and then” is usually a violated assumption, prevented by pinning the threading contract down first.

4.5. Do Tests Exist? Can You Observe It?

Replacement is hard not only because the code is old. It is hard because there is nothing that says what counts as it behaved the same.

Having even these makes a substantial difference.

  • Smoke tests per operation scenario
  • Input and output samples
  • Screen captures and report samples
  • Error patterns and expected behavior
  • Logs from timeouts and from the device not being connected

Especially where equipment or reports are involved, the odd situation arises that the live behavior is more truthful than the specification documents. Without a means of observation here, replacement turns into an excavation.

Observation is what supports replacementWithout smoke tests, input and output samples, report samples, and error patterns with expected behavior, there is nothing that says what counts as it behaved the same, and replacement turns into an excavation.YesNoIs there a means of observationYou can say it behaved the sameReplacement turns into an excavationSmoke tests and sample material

Figure 16: The difficulty of replacement is decided less by the age of the code than by whether you can observe enough to say it behaved the same.

5. Recommendations by Typical Pattern

5.1. An Internal Desktop App That Still Runs Stably

The recommendation is to lean toward keeping.

Under conditions like these, not forcibly tearing it out is often the better move.

  • It is used internally only
  • The target machines and OS are reasonably fixed
  • That OCX is used on only a few screens
  • Change requests are small and the lifespan is predictable

Rather than leaving it bare, though, consolidating just the call sites pays off later.

So the policy goes like this.

  • Keep it for now
  • But tidy the boundary
  • Shape things so that when replacement becomes necessary, you can start from there

This three-step stance is the straightforward one.

5.2. You Want to Bring a 32-bit OCX to the 64-bit Side

The recommendation is to wrap or change the configuration.

Going at this head-on is checkmate. A 32-bit OCX cannot be loaded in-proc into a 64-bit process.

Realistically, the manageable configuration is to confine it in a 32-bit helper process or a LocalServer and communicate with the 64-bit app through a coarse API.

32-bit OCX32-bit helper / LocalServer64-bit .NET app32-bit OCX32-bit helper / LocalServer64-bit .NET appRequest through a coarse APIIn-proc callResults / eventsTranslated results

Figure 17: A configuration where the OCX is confined in a 32-bit helper and used from the 64-bit app across a coarse API.

The point here is not to relay every fine-grained method as it is. A process boundary turns painful fast when you push large volumes of fine-grained calls through it.

  • Aim for a granularity of roughly one operation to one request
  • Shape return values and errors into meaningful units
  • Capture logs at the boundary

With this shape in place, actually replacing the internals later is easier as well.

Everything above assumes a helper EXE of your own, but there is one more option worth checking before that.

If that OCX or DLL is an in-proc COM server, that is, something registrable through InprocServer32, you can put it into the surrogate process that ships with Windows and expose it as an out-of-proc Local Server. Attaching an AppID to the CLSID and writing an empty-string DllSurrogate into that AppID key is all it takes, with not a single line of code on your side. The registration steps are covered in 3.5 of Registration and Bitness Pitfalls in COM/OCX/ActiveX Development.

A surrogate does not erase the bitness difference, though. What goes away is only the constraint that it has to sit in the same process; the calls become out-of-proc, and the cost of marshaling and inter-process communication is carried in full.

Which of the two to choose is mostly settled by this table.

Situation What to choose Reason
An automation object that is complete with method calls and events Surrogate first It can be made out-of-proc by registration alone, and there is no code to write
The types you exchange can be marshaled through IDispatch or a registered proxy / stub Surrogate first The means of crossing the boundary is already in place
That CLSID already has an EXE registration such as LocalServer32 No place for a surrogate Starting the EXE server or the service always takes precedence
You want to use it as a visual control that you place on a form and have it draw Consider a different configuration Its window is premised on living inside the host’s process
Fine-grained calls are frequent, and relaying them as they are is heavy A helper EXE of your own You need a layer of your own that bundles them into a coarse API
A proprietary interface has no marshaler A helper EXE of your own Preparing a proxy / stub, or deciding the boundary types yourself, is quicker
You want initialization order, reconnection, timeouts, and logging in your own hands A helper EXE of your own A surrogate’s process lifetime is left to COM

In short, a surrogate is the minimal move worth trying first, and an EXE of your own is the move for when you want to design the boundary yourself. The 3.1 table’s “You want to put a 32-bit OCX directly into a 64-bit process, so wrap or change the configuration” does not change. Read it as there being two steps inside that “wrap”.

5.3. Screens Built on IE / WebBrowser

The recommendation is to prioritize replacement.

This is an area where “works now” and “stays easy to maintain” rarely coincide. IE mode helps a great deal with compatibility, but the premise remains the IE family.

So the clearest way to think about it splits like this.

  • Use IE mode to keep internal operations from stopping
  • But do not confuse life support with a permanent design
  • Choose the replacement target from WebView2, a pure web app, a native UI plus web hybrid, and so on

Here are the concrete measures on the life-support side as well. IE mode is not something that “just works once Edge is installed”: a site opens in the IE-family engine only after it is designated by policy. There are three ways in.

Approach Setting Notes
Enumerate the sites In the Microsoft Edge 78 and later group policy “Configure the Enterprise Mode Site List”, specify the location of the Enterprise Mode Site List XML This is the most basic form
Reuse the list from the old IE side The Internet Explorer policy “Use the Enterprise Mode IE website list” If the Edge-side policy is present, it takes precedence
Route the whole intranet Enable the Microsoft Edge 77 and later group policy “Send all intranet sites to Internet Explorer” The scope becomes broad, so it is no substitute for taking stock of the sites

As prerequisites, Windows and Edge must have the latest updates, the Microsoft Edge administrative templates must be installed, and Internet Explorer 11 must be enabled as a Windows feature. IE mode fails when any of these is missing.

Note that ActiveX controls and Browser Helper Objects do run in IE mode. In other words, life support genuinely works. That is exactly why continuing to use it without setting exit criteria makes it impossible to get out. How to peel it off is covered in A Guide to Breaking Free from IE Mode Dependence.

How to think about IE mode life supportIE mode takes effect only after the target sites are designated by policy and ActiveX does run in it, so life support genuinely works, but life support must not be confused with permanent design and exit criteria have to be set or there is no way out.Designate the target sites by policyThe site opens in IE modeLife support works, ActiveX includedUse it with exit criteria setWithout them, there is no way out

Figure 18: Because IE mode life support genuinely works, use it only together with exit criteria.

If the WebBrowser control is being used merely as an HTML viewer, the replacement priority is especially high.

On the other hand, if the in-browser ActiveX also carries roles such as local files, devices, signing, or proprietary add-ons, then this is not a rendering-engine swap but a redesign of native integration. That conversation gets somewhat heavier.

5.4. ActiveX Carrying Device Control or Proprietary Specifications

The recommendation is to wrap it first.

This type is denser inside than it looks. Even where the SDK documentation is thin, years of running in the field can mean behaviors like these have piled up implicitly.

  • How it waits after a connection failure
  • Retries after a timeout
  • Event ordering
  • Workarounds that absorb the quirks of the actual device
  • The interpretation of exceptions and error codes

Rebuild this kind of component on the grounds that it is old anyway, and field testing will blow up with fairly high probability.

So it is safest to start here.

  1. Confine the existing component inside a boundary
  2. Add logging so you can see what is happening
  3. Collect test scenarios and real-device patterns
  4. Then carve out the ranges that can be replaced

It is not flashy, but in practice this is what works best.

How to proceed with ActiveX that carries specificationsProceed in this order - confine the existing component inside a boundary, add logging so you can see what is happening, collect test scenarios and real-device patterns, and then carve out the ranges that can be replaced.Confine it inside a boundaryAdd logging for visibilityCollect scenarios and real-device patternsCarve out the replaceable ranges

Figure 19: For a component carrying device control or proprietary specifications, this order keeps field testing from blowing up.

6. Common Anti-Patterns

Anti-pattern Why it hurts First fix
A full rewrite because ActiveX is present Specification gaps and effort explosions are likely Take stock first, then carve out the boundary
Trying to load a 32-bit OCX into a 64-bit app as it is Impossible in principle Isolate it on the 32-bit side, or change the configuration
Calling the control’s API directly from all over the screens It tends to become impossible to replace Consolidate into an adapter / facade
Running regsvr32 procedures by hand Environment differences trip it up every time Consider an installer, scripts, or manifests
Relaxing because IE mode exists Life support and a permanent fix are easily confused Set a replacement plan and exit criteria
Not recording the behavior before replacing Completion cannot be judged Assemble smoke tests, sample data, and logs

Of these, three show up especially often in practice.

  1. Rushing into a full rewrite
  2. Underestimating the bitness wall
  3. Scattering the API across the entire app

Avoiding just these three already means far less goes wrong.

The three anti-patterns seen most oftenSimply avoiding the three of rushing into a full rewrite, underestimating the bitness wall, and scattering the control API across the entire app already means far fewer things go wrong.Rushing into a full rewriteAvoid these threeUnderestimating the bitness wallScattering the API everywhereFar less goes wrong

Figure 20: Among the anti-patterns, these three are the ones you meet most often, and simply avoiding them has a large effect.

7. Checklist for Starting a Migration

ActiveX / OCX projects go better when you take stock first rather than diving into implementation. The order runs roughly like this.

  1. Identify the OCX / DLLs in use
    • File names, versions, ProgIDs, CLSIDs, vendors, whether licenses are involved
  2. Identify where they are used
    • Screens, features, reports, equipment, batch jobs, Office integration, and so on
  3. Check the bitness and the host conditions
    • 32-bit / 64-bit, in-proc / out-of-proc, STA assumptions, browser dependence
  4. Check the distribution conditions
    • Registration method, dependent DLLs, administrator privileges, silent install, clean-environment reproduction
  5. Build smoke tests
    • Include not only the happy path but failures, the device not being connected, and timeouts
  6. Build the boundary
    • Adapter, service, facade, separate-process bridging, and so on
  7. Try it in small units, such as one screen, one feature, or one device
  8. From the boundaries that worked, expand keep / wrap / replace in order

Skip these steps, and later it becomes hard even to explain what was difficult about it.

8. A Rough Decision Guide

Situation What to choose first
Internal-only, running stably, changes are small Keep
You want to modernize only the surroundings to .NET Wrap
32-bit and 64-bit collide Wrap / change the configuration
Dependence on IE / WebBrowser / browser ActiveX Replace
A mere UI component with an alternative available Replace
It carries device control, reporting, or proprietary specifications Wrap
Registration or distribution trips you up every time Wrap or replace

When in doubt, distinguishing first between a UI component and a boundary surface carrying specifications makes it much harder to get this wrong.

9. Summary

How to handle ActiveX / OCX is not a question to be settled by disliking it for being legacy.

There are four points to look at first.

  1. Is the component mere UI, or a boundary surface carrying specifications?
  2. Does it need to run in the same process?
  3. Will 32-bit / 64-bit, registration, browser dependence, or licensing get you stuck?
  4. Can you observe the behavior before replacing?

Once these four are visible, things mostly sort out like this.

  • If it runs stably and the lifespan is predictable, keep it
  • If you only want to modernize the surroundings, wrap it
  • If it is a UI component or browser-dependent, replace it
  • If it carries a mass of specifications, wrap it first and then replace in stages

Legacy technology is not something to laugh at. It is a living artifact packed with history and contracts. Living with that artifact does require boundary design, though.

Once you can think about keeping, wrapping, and replacing as a blend, ActiveX / OCX projects suddenly turn into tractable problems.

Summary of the arrangementKeep it if it runs stably with a predictable lifespan, wrap it if you only want to modernize the surroundings, replace it if it is a UI component or browser-dependent, and wrap first then replace in stages if it carries a mass of specifications.Handling ActiveX / OCXKeep it if it runs stablyWrap it to modernize the surroundingsReplace UI and browser dependenciesWrap a mass of specifications, then replace in stages

Figure 21: Once the four points to look at are visible, keep, wrap, and replace sort out into this shape.

10. Consultations That Fit Well

This theme tends to deliver value from clarifying the policy alone, before any development starts.

For example, consultations like these fit quite well.

  • You want to take stock of which OCX really should be replaced
  • You want the 32-bit / 64-bit sticking points sorted out first
  • You want to move to .NET but keep only the COM entry point
  • You want to compare life-support measures against an exit strategy for an ActiveX whose vendor is gone
  • You want to see where IE / WebBrowser dependence can be peeled off first
  • You want to safely separate just one screen or one feature to start

In ActiveX / OCX projects, how you cut the boundaries often decides the outcome before the implementation does. As a preliminary to a full overhaul, even starting from a current-state assessment, a comparison of configurations, and the design of a migration order is well worth it.

11. References

  • Microsoft Learn: AxHost Class (System.Windows.Forms)
    • https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.axhost
  • Microsoft Learn: Aximp.exe (Windows Forms ActiveX Control Importer)
    • https://learn.microsoft.com/en-us/dotnet/framework/tools/aximp-exe-windows-forms-activex-control-importer
  • Microsoft Learn: How to: Add ActiveX Controls to Windows Forms
    • https://learn.microsoft.com/en-us/dotnet/desktop/winforms/controls/how-to-add-activex-controls-to-windows-forms
  • Microsoft Learn: Expose .NET Core components to COM
    • https://learn.microsoft.com/en-us/dotnet/core/native-interop/expose-components-to-com
  • Microsoft Learn: Registration-Free COM Interop
    • https://learn.microsoft.com/en-us/dotnet/framework/interop/registration-free-com-interop
  • Microsoft Learn: DllSurrogate
    • https://learn.microsoft.com/en-us/windows/win32/com/dllsurrogate
  • Microsoft Learn: Registering the DLL Server for Surrogate Activation
    • https://learn.microsoft.com/en-us/windows/win32/com/registering-the-dll-server-for-surrogate-activation
  • Microsoft Learn: Frequently Asked Questions about Microsoft Edge
    • https://learn.microsoft.com/en-us/deployedge/microsoft-edge-frequently-asked-questions
  • Microsoft Learn: What is Internet Explorer (IE) mode?
    • https://learn.microsoft.com/en-us/deployedge/edge-ie-mode
  • Microsoft Learn: WebBrowser Class (System.Windows.Forms)
    • https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.webbrowser
  • Microsoft Learn: Introduction to Microsoft Edge WebView2
    • https://learn.microsoft.com/en-us/microsoft-edge/webview2/
  • KomuraSoft Blog: COM STA/MTA Fundamentals - Threading Models and How to Avoid Hangs
    • /en/blog/2026/01/31/000-sta-mta-com-relationship/
  • KomuraSoft Blog: Calling Native DLLs from C# - Why a C++/CLI Wrapper Is Worth It
    • /en/blog/2026/03/07/000-cpp-cli-wrapper-for-native-dlls/
  • KomuraSoft Blog: A Worked Example of a COM Bridge for Calling a 64-bit DLL from a 32-bit App
    • /en/blog/2026/01/25/002-com-case-study-32bit-to-64bit/
  • KomuraSoft Blog: What Are COM / ActiveX / OCX? - The Differences and Relationships Explained
    • /en/blog/2026/03/13/000-what-is-com-activex-ocx/
  • KomuraSoft Blog: Using a .NET 8 DLL from VBA with Full Typing - COM Exposure and dscom TLB
    • /en/blog/2026/03/16/007-dotnet8-dll-typed-vba-com-dscom-tlb/
  • KomuraSoft Blog: What Is Reg-Free COM - Using COM Without Registration
    • /en/blog/2026/03/16/011-what-is-reg-free-com/
  • KomuraSoft Blog: Registration and Bitness Pitfalls in COM/OCX/ActiveX Development
    • /en/blog/2026/04/15/001-com-ocx-activex-pitfalls-visual-studio-bitness-admin-rights/
  • KomuraSoft Blog: A Guide to Breaking Free from IE Mode Dependence
    • /en/blog/2026/04/25/003-ie-mode-internal-web-system-life-extension-and-exit/
  • KomuraSoft Blog: Is WebView2 the Right Successor to IE Mode? - The ActiveX Constraint and a Realistic Migration Design
    • /en/blog/webview2-embed-web-ui-in-windows-apps/

Recent articles sharing the same tags. Deepen your understanding with closely related topics.

These topic pages place the article in a broader service and decision context.

This article connects naturally to the following service pages.

Frequently Asked Questions

Common questions about the topic of this article.

Should ActiveX / OCX be replaced?
Judge by what the component has taken on, not by whether it is old. If it is a mere UI component and an alternative exists, replace it. If it carries device control, report generation, or a proprietary file format, wrap it first and sort out the boundary. If it runs stably and the scope of change is small, keeping it is a realistic decision as well. Both 'just rewrite everything' and 'freeze it forever because it is scary' are high-risk choices.
Can a 32-bit OCX be used from a 64-bit application?
Not in-process. An OCX has to match the bitness of the process that loads it, and that is a constraint of the model itself. The realistic options are three: keep the host application 32-bit for the time being; confine the OCX in a separate 32-bit process (a helper EXE or a COM LocalServer) and connect it to the 64-bit side through IPC or out-of-process COM; or replace the OCX dependency starting wherever it can be removed first.
What should be done about browser-hosted ActiveX dependencies?
It is better to view them with replacement as the priority. Microsoft Edge itself does not support ActiveX, and IE mode is positioned as a life-support measure. The WebBrowser control likewise drags the IE worldview along, so if you only want to display HTML, WebView2 is the first candidate. WebView2 is not a complete drop-in replacement, though: scripts premised on the IE DOM and assumptions around window.external do not carry over as they are.
What does wrapping an ActiveX / OCX actually involve?
It means confining the ActiveX / OCX inside a narrow boundary and presenting it to the surroundings as a new API or a new UI component. Established shapes include a WinForms host plus AxHost, separate-process bridging through a 32-bit helper EXE or a LocalServer, and a COM-compatible facade on the .NET side. When wrapping, do not copy the old API wholesale: use coarse-grained methods, decide at the boundary who is responsible for logging, timeouts, and exception translation, and shape it so that a future replacement can be swapped in behind the same interface.

Author Profile

Profile page for the article author.

Go Komura

Representative of KomuraSoft LLC

Focused on Windows software development, technical consulting, and investigations into failures that are difficult to reproduce.

Back to the Blog