August 14th, 2026
intriguinglikecompellingheart4 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.

3 comments

Sort by :
  • Chris Iverson 2 days ago

    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
      • 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