August 14th, 2026
intriguinglikecompellingheart5 reactions

Forcing an ARM64X executable to run as a specific architecture

ARM64X is a fat binary Windows executable and DLL format for 64-bit ARM systems. For DLLs, the choice is clear, since only one of them will work: The version of the DLL that is loaded is the one that matches the host process. If the host process uses the Windows ARM64 ABI, then the ARM64 version of the DLL is used, and if the host process is x86-64-based or uses the Windows ARM64EC ABI¹

For executables, the system has a choice. It could run the process as ARM64 or it could run it as ARM64EC. How can you force the system to choose the architecture you prefer?

You may want to do this if you have a program that is compiled as ARM64X because you have a plug-in model, and you want to be able to support plug-ins that are written either as ARM64 or x86-64. You compile an ARM64 version for ARM64 plug-ins, and you compile an ARM64EC version for x86-64 plug-ins. At run time, you realize that the user passed a plug-in for the other architecture, so you want to relaunch yourself as the matching architecture.

You can do it with the PROC_THREAD_ATTRIBUTE_MACHINE_TYPE attribute.

Here’s a program that takes a DLL on the command line. It tries to load it as the native architecture, but if that fails, and the native architecture is ARM64, then it relaunches itself as x86-64 to try again.

#include <windows.h>
#include <stdio.h>
#include <wil/result_macros.h>
#include <wil/resource.h>
#include <wil/stl.h>
#include <wil/win32_helpers.h>

int wmain(int argc, wchar_t** argv)
{
    if (argc < 2) {
        printf("Oops\n");
        return 0;
    }

    wil::unique_hmodule dll{ LoadLibraryExW(path, nullptr, 0) };
    if (dll) {
        return RunPlugin(dll);
    }

    if (GetLastError() != ERROR_BAD_EXE_FORMAT) {
        printf("Can't load DLL, sorry\n");
        return 0;
    }

    SYSTEM_INFO info{};
    GetSystemInfo(&info);
    if (info.wProcessorArchitecture != PROCESSOR_ARCHITECTURE_ARM64) {
        printf("Can't load DLL, sorry\n");
        return 0;
    }

    printf("Trying again as x86-64\n");

    WORD arch = IMAGE_FILE_MACHINE_AMD64;          
    auto single = make_proc_thread_attribute_list({
        {PROC_THREAD_ATTRIBUTE_MACHINE_TYPE, &arch}
    });                                            

    wchar_t self[MAX_PATH + 1];
    std::wstring self;
    THROW_IF_FAILED(wil::GetModuleFileNameW(nullptr, self));

    wil::unique_process_information pi;

    STARTUPINFOEXW info{ sizeof(STARTUPINFOEXW) };
    info.lpAttributeList = single.get();

    if (!CreateProcessW(self.data(), GetCommandLineW(), nullptr, nullptr,
            false, EXTENDED_STARTUPINFO_PRESENT, nullptr, nullptr,
            &info.StartupInfo, &pi)) {
        printf("Can't relaunch as x86-64, sorry\n");
        return 0;
    }

    WaitForSingleObject(pi.hProcess, INFINITE);
    // destructors will close the handles
}

If we can load the DLL, then great! We run it as usual.

If we can’t load the DLL because it’s in the wrong format, then we will retry as x86-64 if the current process is running as ARM64. To do that, we create an attribute list with the PROC_THREAD_ATTRIBUTE_MACHINE_TYPE attribute whose value is the architecture we want to try, namely AMD64 (which is the Windows name for x86-64), and relaunch ourselves with the same command line.²

If the DLL fails to load even as x86-64, then the x86-64 version of our program just gives up without trying again as ARM64. (You don’t want to have the x86-64 version try again as ARM64 because that would create an infinite loop.)

¹ You can think of ARM64EC as “pre-jitted x86-64 on ARM64.” It is like taking an x86-64 binary and compiling it to ARM64 code that is equivalent to (but presumably has better performance than) the version the emulator would have created on the fly from your x86-64 version. Instead of shipping an x86-64 version that the emulator has to translate to ARM64, just ship the translated version.

² In real life, you probably would add some safety precautions to prevent accidental fork bombs. While writing up this article, I fork bombed my machine a few times by mistake.

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

12 comments

Discussion is closed. Login to edit/delete existing comments.

Sort by :
  • Nathan Phillip Brink (Binki)

    Well, an installer should be able to decide on the right architecture to install at installation time. ARM64X is only necessary because ARM64 and x86-64 live together in the same namespace (we don’t have SysWOWAMD64 or Program Files (amd64)), so you have amd64-only code, ARM64-only code, and dual code living side by side in the same directories.

    • Me Gusta

      Unfortunately, that isn’t all that is to it.

      ARM64EC allows for x64 plugin compatibility too. This is pretty useful for a large suite like Office as one example. Instead of convincing plugin vendors to rebuild things for ARM64 it is possible to just use the x64 plugins as is with just the recomp and possible IPC costs.

      • LB

        Another example to add to Me Gusta's comment: OBS Studio virtual camera functionality works by registering a DLL to be loaded by other processes, but Windows doesn't provide a way to use a different DLL depending on architecture (likely a breaking change to do so), so it has to be ARM64X to be able to be loaded into both ARM64 processes and x86-64/ARM64EC processes. OBS Studio also has a large catalogue of community-made plugins that are historically x86-64, so eventually OBS itself may need an ARM64EC or ARM64X build to do exactly what this article suggests.

        Read more
  • LB

    ARM64X is pretty cool, but even cooler would be fat binary support for other architectures too. A single executable with x86-64 and ARM64 that can run on both x86-64 and ARM64 Windows, for example. At least, I haven’t seen evidence of ARM64X binaries that can run on x86-64 Windows. I’m not sure what cosmopolitan does but I don’t think it’s ARM64X.

    • Evgeny Vrublevsky

      I had the same idea a year ago. ARM64X PE file with x86-64 as the main view actually looks very promising. Potentially, it probably could be done if implemented in linker.

      Experimented with that a year ago (using existing linker), didn't manage to make a single binary that could run natively on x86-64 and ARM64 Windows. LLVM's LLD looks like also supports ARM64X these days, and it's open source, so it's much easier to play with it and modify its behavior if required. But I currently don't have access to an ARM64 system, so maybe somebody else would like to...

      Read more
  • Chris Iverson

    I think you accidentally cut off the end of the last sentence in the first paragraph.

    “If the host process uses the Windows ARM64 ABI, then the ARM64 version of the DLL is used, and if the host process is x86-64-based or uses the Windows ARM64EC ABI¹”

    I understand what you were trying to say, but the final part of that sentence is missing.

    • Kyle Sluder

      Since the whole point of ARM64EC is to support mixed x64 and ARM in the same process, I was confused by the statement that “The version of the DLL that is loaded is the one that matches the host process.”

      Raymond does not explicitly state it, but ARM64X also supports storing an x64 slice. So an ARM64X DLL loaded into an x64 or ARM64EC process will load either the ARM64EC or x64 slice, preferring the ARM64EC slice if present.

      Raymond’s post seems to be primarily focused on the pure-ARM64 case, which presumably has better enough codegen that it’s worth targeting instead of...

      Read more
      • Me Gusta

        @Kyle Sluder

        The documentation does strongly hint at ARM64EC being worse than native ARM64. The ARM64EC ABI conventions gives a mapping between the x64 registers and ARM64 registeres and states that ARM64EC will only ever use registers in that mapping (with the exception of getting the TEB). There is also the fact that there is going to be transitions between EC and native at some point, especially kernel mode.

        I would have to double check, but I also believe that there are differences with compiler intrinsics too.

      • Kyle Sluder

        (I never intended my comment to be a reply, so I apologize for perpetuating the terrible reverse threading.)

        “ you need to be able to create two types of host processes, one for ARM64 plug-ins and one for x64 plug-ins.”

        The question that every developer will be asking themselves is why bother producing an ARM64 executable at all? Given the lack of adoption of Windows on ARM before ARM64EC was introduced, the most practical decision might be to consider ARM64EC the sole AArch64 ABI for Windows, and only ever ship x64 and ARM64EC fat binaries.

        But that argument suffers if there are material...

        Read more
      • Jan Ringoš

        Raymond, if I may suggest, a “Tony Table” of what process can load which type of DLL on which architecture could make for a nice blog post.

      • Raymond ChenMicrosoft employee Author

        It's not about which has better code gen. It's about being able to run something at all. If some of your customers write ARM64 plug-ins, and some write x64 plug-ins (which on arm64 systems is converted to ARM64EC whether via the emulator, JIT, or by precompilation), you need to be able to create two types of host processes, one for ARM64 plug-ins and one for x64 plug-ins. If you have only one type of host process, than half of your customers' plug-ins won't run. And now that you there are two types of host processes, how do you tell the...

        Read more
      • Howard KapusteinMicrosoft employee

        point of ARM64EC is to support mixed x64 and ARM in the same process

        More like, to more efficiently support x64 DLLs in an x64 process on an ARM64 cpu. ARM64EC is only relevant for x64 processes on an ARM64 device -- ARM64 processes can't use ARM64EC, and don't need to.

        There's a perf cost to run x64 on ARM64 systems. ARM64EC reduces the x64 emulation overhead on an ARM64 device, thus better perf, power usage, etc. See https://learn.microsoft.com/en-us/windows/arm/arm64ec for more details.

        Windows App SDK added partial support for ARM64EC some time back, and even with just a subset built as ARM64EC apps...

        Read more