by Joche Ojeda | Jan 8, 2026 | netframework
If you’ve ever worked on a traditional .NET Framework application — the kind that predates .NET Core and .NET 5+ — this story may feel painfully familiar.
I’m talking about classic .NET Framework 4.x applications (4.0, 4.5, 4.5.1, 4.5.2, 4.6, 4.6.1, 4.6.2, 4.7, 4.7.1, 4.7.2, 4.8, and the final release 4.8.1). These systems often live long, productive lives… and accumulate interesting technical debt along the way.
This particular system is written in C# and relies heavily on COM components to render video, audio, and PDF content. Under the hood, many of these components are based on technologies like DirectShow filters, ActiveX controls, or other native COM DLLs.
And that’s where the story begins.
The Setup: COM, DirectShow, and Registration
Unlike managed .NET assemblies, COM components don’t just live quietly next to your executable. They need to be registered in the system registry so Windows knows:
- What CLSID they expose
- Which DLL implements that CLSID
- Whether it’s 32-bit or 64-bit
- How it should be activated
For DirectShow-based components (very common for video/audio playback in legacy apps), registration is usually done manually during development using regsvr32.
Example:
regsvr32 MyVideoFilter.dll
To unregister:
regsvr32 /u MyVideoFilter.dll
Important detail that bites a lot of people:
- 32-bit DLLs must be registered using:
C:\Windows\SysWOW64\regsvr32.exe My32BitFilter.dll
- 64-bit DLLs must be registered using:
C:\Windows\System32\regsvr32.exe My64BitFilter.dll
Yes — the folder names are historically confusing.
Development Works… Until It Doesn’t
So here’s the usual development flow:
- You register all required COM DLLs on your development machine
- Visual Studio runs the app
- Video plays, audio works, PDFs render
- Everyone is happy
Then comes the next step.
“Let’s build an installer.”
The Installer Paradox
This is where the real battle story begins.
Your application installer (MSI, InstallShield, WiX, Inno Setup — pick your poison) now needs to:
- Copy the COM DLLs
- Register them during installation
- Unregister them during uninstall
This seems reasonable… until you test it.
The Loop From Hell
Here’s what happens in practice:
- You install your app for testing
- The installer registers its own copies of the COM DLLs
- Your development environment was using different copies (maybe newer, maybe local builds)
- Suddenly:
- Your source build stops working
- Visual Studio debugging breaks
- Another app on your machine mysteriously fails
Then you:
- Uninstall the app
- The installer unregisters the DLLs
- Now nothing works anymore
So you re-register the DLLs manually for development…
…and the cycle repeats.
The Battle Story: It Only Worked… Until It Didn’t
For a long time, this system appeared to work just fine.
Video played. Audio rendered. PDFs opened. No obvious errors.
What we didn’t realize at first was a dangerous hidden assumption:
The system only worked on machines where a previous version had already been installed.
Those older installations had left COM DLLs registered in the system — quietly, globally, and invisibly.
So when we deployed a new version without removing the old one:
- Everything looked fine
- No one suspected missing registrations
- The system passed casual testing
The illusion broke the moment we tried a clean installation.
On a fresh machine — no previous version, no leftover registry entries — the application suddenly failed:
- Components didn’t initialize
- Media rendering silently broke
- COM activation errors appeared only in Event Viewer
The installer claimed it was registering the DLLs.
In reality, it wasn’t doing it correctly — or at least not in the way the application actually needed.
That’s when we realized we were standing on years of accidental state.
Why This Happens
The core problem is simple but brutal:
COM registration is global and mutable.
There is:
- One registry
- One CLSID mapping
- One “active” DLL per COM component
Your development environment, your installed application, and your installer are all fighting over the same global state.
.NET Framework itself isn’t the villain here — it’s just sitting on top of an old Windows integration model that predates modern isolation concepts.
A New Player Enters: ARM64
Just when we thought the problem space was limited to x86 vs x64, another variable entered the scene.
One of the development machines was ARM64.
Modern Windows on ARM adds a new layer of complexity:
- ARM64 native processes
- x64 emulation
- x86 emulation on top of ARM64
From the outside, everything looks like it’s running on x64.
Under the hood, it’s not that simple.
Why This Makes COM Registration Worse
COM registration is architecture-specific:
- x86 DLLs register under one view of the registry
- x64 DLLs register under another
- ARM64 introduces yet another execution context
On Windows ARM:
System32 contains ARM64 binaries
SysWOW64 contains x86 binaries
- x64 binaries often run through emulation layers
So now the questions multiply:
- Which
regsvr32 did the installer call?
- Was it ARM64, x64, or x86?
- Did the app run natively, or under emulation?
- Did the COM DLL match the process architecture?
The result is a system where:
- Some things work on Intel machines
- Some things work on ARM machines
- Some things only work if another version was installed first
At this point, debugging stops being logical and starts being archaeological.
Why This Is So Common in .NET Framework 4.x Apps
Many enterprise and media-heavy applications built on:
- .NET Framework 4.0–4.8.1
- WinForms or WPF
- DirectShow or ActiveX components
were designed in an era where:
- Global COM registration was normal
- Side-by-side isolation was rare
- “Just register the DLL” was accepted practice
These systems work, but they’re fragile — especially on developer machines.
Where the Article Is Going Next
In the rest of this article series, we’ll look at:
- Why install-time registration is often a mistake
- How to isolate development vs runtime environments
- Techniques like:
- Dedicated dev VMs
- Registration-free COM (where possible)
- App-local COM deployment
- Clear ownership rules for installers
- How to survive (and maintain) legacy .NET Framework systems without losing your sanity
If you’ve ever broken your own development environment just by testing your installer — you’re not alone.
This is the cost of living at the intersection of managed code and unmanaged history.
by Joche Ojeda | May 25, 2024 | CPU
A Brief Historical Context
x86 Architecture: The x86 architecture, referring to 32-bit processors, was originally developed by Intel. It was the foundation for early Windows operating systems and supported up to 4GB of RAM.
x64 Architecture: Also known as x86-64 or AMD64, the x64 architecture was introduced to overcome the limitations of x86. This 64-bit architecture supports significantly more RAM (up to 16 exabytes theoretically) and offers enhanced performance and security features.
The Transition Period
The shift from x86 to x64 began in the early 2000s:
- Windows XP Professional x64 Edition: Released in April 2005, this was one of the first major Windows versions to support 64-bit architecture.
- Windows Vista: Launched in 2007, it offered both 32-bit and 64-bit versions, encouraging a gradual migration to the 64-bit platform.
- Windows 7 and Beyond: By the release of Windows 7 in 2009, the push towards 64-bit systems became more pronounced, with most new PCs shipping with 64-bit Windows by default.
Impact on Program File Structure
To manage compatibility and distinguish between 32-bit and 64-bit applications, Windows implemented separate directories:
- 32-bit Applications: Installed in the
C:\Program Files (x86)\ directory.
- 64-bit Applications: Installed in the
C:\Program Files\ directory.
This separation ensures that the correct version of libraries and components is used by the respective applications.
Naming Convention for x64 and x86 Programs
x86 Programs: Often referred to simply as “32-bit” programs, they are installed in the Program Files (x86) directory.
x64 Programs: Referred to as “64-bit” programs, they are installed in the Program Files directory.
Why “Program Files (x86)” Instead of “Program Files (x64)”?
The decision to create Program Files (x86) instead of Program Files (x64) was driven by two main factors:
- Backward Compatibility: Many existing applications and scripts were hardcoded to use the
C:\Program Files\ path. Changing this path for 64-bit applications would have caused significant compatibility issues. By keeping 64-bit applications in Program Files and moving 32-bit applications to a new directory, Microsoft ensured that existing software would continue to function without modification.
- Clarity: Since 32-bit applications were the legacy standard, explicitly marking their directory with
(x86) indicated they were not the default or modern standard. Thus, Program Files without any suffix indicates the use of the newer, 64-bit standard.
Common Confusions
- Program Files Directories: Users often wonder why there are two “Program Files” directories and what the difference is. The presence of
Program Files and Program Files (x86) is to segregate 64-bit and 32-bit applications, respectively.
- Compatibility Issues: Running 32-bit applications on a 64-bit Windows system is generally smooth due to the Windows-on-Windows 64-bit (WoW64) subsystem, but there can be occasional compatibility issues with older software. Conversely, 64-bit applications cannot run on a 32-bit system.
- Driver Support: During the initial transition period, a common issue was the lack of 64-bit drivers for certain hardware, which caused compatibility problems and discouraged some users from migrating to 64-bit Windows.
- Performance Misconceptions: Some users believed that simply switching to a 64-bit operating system would automatically result in better performance. While 64-bit systems can handle more RAM and potentially run applications more efficiently, the actual performance gain depends on whether the applications themselves are optimized for 64-bit.
- Application Availability: Initially, not all software had 64-bit versions, leading to a mix of 32-bit and 64-bit applications on the same system. Over time, most major applications have transitioned to 64-bit.
Conclusion
The transition from x86 to x64 in Windows marked a significant evolution in computing capabilities, allowing for better performance, enhanced security, and the ability to utilize more memory. However, it also introduced some complexities, particularly in terms of program file structures and compatibility. Understanding the distinctions between 32-bit and 64-bit applications, and how Windows manages these, is crucial for troubleshooting and optimizing system performance.
By appreciating these nuances, users and developers alike can better navigate the modern computing landscape and make the most of their hardware and software investments.
by Joche Ojeda | May 23, 2024 | CPU
The ARM, x86, and Itanium CPU architectures each have unique characteristics that impact .NET developers. Understanding how these architectures affect your code, along with the importance of using appropriate NuGet packages, is crucial for developing efficient and compatible applications.
ARM Architecture and .NET Development
1. Performance and Optimization:
- Energy Efficiency: ARM processors are known for their power efficiency, benefiting .NET applications on devices like mobile phones and tablets with longer battery life and reduced thermal output.
- Performance: ARM processors may exhibit different performance characteristics compared to x86 processors. Developers need to optimize their code to ensure efficient execution on ARM architecture.
2. Cross-Platform Development:
- .NET Core and .NET 5+: These versions support cross-platform development, allowing code to run on Windows, macOS, and Linux, including ARM-based versions.
- Compatibility: Ensuring .NET applications are compatible with ARM devices may require testing and modifications to address architecture-specific issues.
3. Tooling and Development Environment:
- Visual Studio and Visual Studio Code: Both provide support for ARM development, though there may be differences in features and performance compared to x86 environments.
- Emulators and Physical Devices: Testing on actual ARM hardware or using emulators helps identify performance bottlenecks and compatibility issues.
x86 Architecture and .NET Development
1. Performance and Optimization:
- Processing Power: x86 processors are known for high performance and are widely used in desktops, servers, and high-end gaming.
- Instruction Set Complexity: The complex instruction set of x86 (CISC) allows for efficient execution of certain tasks, which can differ from ARM’s RISC approach.
2. Compatibility:
- Legacy Applications: x86’s extensive history means many enterprise and legacy applications are optimized for this architecture.
- NuGet Packages: Ensuring that NuGet packages target x86 or are architecture-agnostic is crucial for maintaining compatibility and performance.
3. Development Tools:
- Comprehensive Support: x86 development benefits from mature tools and extensive resources available in Visual Studio and other IDEs.
Itanium Architecture and .NET Development
1. Performance and Optimization:
- High-End Computing: Itanium processors were designed for high-end computing tasks, such as large-scale data processing and enterprise servers.
- EPIC Architecture: Itanium uses Explicitly Parallel Instruction Computing (EPIC), which requires different optimization strategies compared to x86 and ARM.
2. Limited Support:
- Niche Market: Itanium has a smaller market presence, primarily in enterprise environments.
- .NET Support: .NET support for Itanium is limited, requiring careful consideration of architecture-specific issues.
CPU Architecture and Code Impact
1. Instruction Sets and Performance:
- Differences: x86 (CISC), ARM (RISC), and Itanium (EPIC) have different instruction sets, affecting code efficiency. Optimizations effective on one architecture might not work well on another.
- Compiler Optimizations: .NET compilers optimize code for specific architectures, but understanding the underlying architecture helps write more efficient code.
2. Multi-Platform Development:
-
- Conditional Compilation: .NET supports conditional compilation for architecture-specific code optimizations.
#if ARM
// ARM-specific code
#elif x86
// x86-specific code
#elif Itanium
// Itanium-specific code
#endif
- Libraries and Dependencies: Ensure all libraries and dependencies in your .NET project are compatible with the target CPU architecture. Use NuGet packages that are either architecture-agnostic or specifically target your architecture.
3. Debugging and Testing:
- Architecture-Specific Bugs: Bugs may manifest differently across ARM, x86, and Itanium. Rigorous testing on all target architectures is essential.
- Performance Testing: Conduct performance testing on each architecture to identify and resolve any specific issues.
Supported CPU Architectures in .NET
1. .NET Core and .NET 5+:
- x86 and x64: Full support for 32-bit and 64-bit x86 architectures across all major operating systems.
- ARM32 and ARM64: Support for 32-bit and 64-bit ARM architectures, including Windows on ARM, Linux on ARM, and macOS on ARM (Apple Silicon).
- Itanium: Limited support, mainly in specific enterprise scenarios.
2. .NET Framework:
- x86 and x64: Primarily designed for Windows, the .NET Framework supports both 32-bit and 64-bit x86 architectures.
- Limited ARM and Itanium Support: The traditional .NET Framework has limited support for ARM and Itanium, mainly for older devices and specific enterprise applications.
3. .NET MAUI and Xamarin:
- Mobile Development: .NET MAUI (Multi-platform App UI) and Xamarin provide extensive support for ARM architectures, targeting Android and iOS devices which predominantly use ARM processors.
Using NuGet Packages
1. Architecture-Agnostic Packages:
- Compatibility: Use NuGet packages that are agnostic to CPU architecture whenever possible. These packages are designed to work across different architectures without modification.
- Example: Common libraries like Newtonsoft.Json, which work across ARM, x86, and Itanium.
2. Architecture-Specific Packages:
- Performance: For performance-critical applications, use NuGet packages optimized for the target architecture.
- Example: Graphics processing libraries optimized for x86 may need alternatives for ARM or Itanium.
Conclusion
For .NET developers, understanding the impact of ARM, x86, and Itanium architectures is essential for creating efficient, cross-platform applications. The differences in CPU architectures affect performance, compatibility, and optimization strategies. By leveraging cross-platform capabilities of .NET, using appropriate NuGet packages, and testing thoroughly on all target architectures, developers can ensure their applications run smoothly across ARM, x86, and Itanium devices.
by Joche Ojeda | May 23, 2024 | CPU
The world of CPU architectures is diverse, with ARM and x86 standing out as two of the most prominent types. Each architecture has its unique design philosophy, use cases, and advantages. This article delves into the intricacies of ARM and x86 architectures, their applications, key differences, and highlights an area where x86 holds a distinct advantage over ARM.
ARM Architecture
Design Philosophy:
ARM (Advanced RISC Machines) follows the RISC (Reduced Instruction Set Computer) architecture. This design philosophy emphasizes simplicity and efficiency, using a smaller, more optimized set of instructions. The goal is to execute instructions quickly by keeping them simple and minimizing complexity.
Applications:
- Mobile Devices: ARM processors dominate the smartphone and tablet markets due to their energy efficiency, which is crucial for battery-operated devices.
- Embedded Systems: Widely used in various embedded systems like smart appliances, automotive applications, and IoT devices.
- Servers and PCs: ARM is making inroads into server and desktop markets with products like Apple’s M1/M2 chips and some data center processors.
Instruction Set:
ARM uses simple and uniform instructions, which generally take a consistent number of cycles to execute. This simplicity enhances performance in specific applications and simplifies processor design.
Performance:
- Power Consumption: ARM’s design focuses on lower power consumption, translating to longer battery life for portable devices.
- Scalability: ARM cores can be scaled up or down easily, making them versatile for applications ranging from small sensors to powerful data center processors.
x86 Architecture
Design Philosophy:
x86 follows the CISC (Complex Instruction Set Computer) architecture. This approach includes a larger set of more complex instructions, allowing for more direct implementation of high-level language constructs and potentially fewer instructions per program.
Applications:
- Personal Computers: x86 processors are the standard in desktop and laptop computers, providing high performance for a broad range of applications.
- Servers: Widely used in servers and data centers due to their powerful processing capabilities and extensive software ecosystem.
- Workstations and Gaming: Favored in workstations and gaming PCs for their high performance and compatibility with a wide range of software.
Instruction Set:
The x86 instruction set is complex and varied, capable of performing multiple operations within a single instruction. This complexity can lead to more efficient execution of certain tasks but requires more transistors and power.
Performance:
- Processing Power: x86 processors are known for their high performance and ability to handle intensive computing tasks, such as gaming, video editing, and large-scale data processing.
- Power Consumption: Generally consume more power compared to ARM processors, which can be a disadvantage in mobile or embedded applications.
Key Differences Between ARM and x86
- Instruction Set Complexity:
- ARM: Uses a RISC architecture with a smaller, simpler set of instructions.
- x86: Uses a CISC architecture with a larger, more complex set of instructions.
- Power Efficiency:
- ARM: Designed to be power-efficient, making it ideal for battery-operated devices.
- x86: Generally consumes more power, which is less of an issue in desktops and servers but can be a drawback in mobile environments.
- Performance and Applications:
- ARM: Suited for energy-efficient and mobile applications but increasingly capable in desktops and servers (e.g., Apple M1/M2).
- x86: Suited for high-performance computing tasks in desktops, workstations, and servers, with a long history of extensive software support.
- Market Presence:
- ARM: Dominates the mobile and embedded markets, with growing presence in desktops and servers.
- x86: Dominates the desktop, laptop, and server markets, with a rich legacy and extensive software ecosystem.
An Area Where x86 Excels: High-End PC Gaming and Specialized Software
One key area where x86 can perform tasks that ARM typically cannot (or does so with more difficulty) is in running legacy software that was specifically designed for x86 architectures. This is particularly evident in high-end PC gaming and specialized software.
High-End PC Gaming:
- Compatibility with Legacy Games:
- Many high-end PC games, especially older ones, are optimized specifically for x86 architecture. Games like “The Witcher 3” or “Crysis” were designed to leverage the architecture and instruction sets provided by x86 CPUs.
- These games often make extensive use of the complex instructions available on x86 processors, which can directly translate to better performance and higher frame rates on x86 hardware compared to ARM.
- Graphics and Physics Engines:
- Engines such as Unreal Engine or Unity are traditionally optimized for x86 architectures, making the most of its processing power for complex calculations, realistic physics, and detailed graphics rendering.
- Advanced features like real-time ray tracing, high-resolution textures, and complex AI calculations tend to perform better on x86 systems due to their raw processing power and extensive optimization for the architecture.
Specialized Software:
- Enterprise Software and Legacy Applications:
- Many enterprise applications, such as older versions of Microsoft Office, Adobe Creative Suite, or proprietary business applications, are built specifically for x86 and may not run natively on ARM processors without emulation.
- While ARM processors can emulate x86 instructions, this often comes with a performance penalty. This is evident in cases where businesses rely on legacy software that performs crucial tasks but is not available or optimized for ARM.
- Professional Tools:
- Professional software such as AutoCAD, certain versions of MATLAB, or legacy database management systems (like some older Oracle Database setups) are heavily optimized for x86.
- These tools often use x86-specific optimizations and plugins that may not have ARM equivalents, leading to suboptimal performance or compatibility issues when running on ARM.
Conclusion
ARM and x86 architectures each have their strengths and are suited to different applications. ARM’s power efficiency and scalability make it ideal for mobile devices and embedded systems, while x86’s processing power and extensive software ecosystem make it the go-to choice for desktops, servers, and high-end computing tasks. Understanding these differences is crucial for selecting the right architecture for your specific needs, particularly when considering the performance of legacy and specialized software.