현재의 ARM64 윈도우 환경에서는 x86/x64 어플리케이션도 대부분 실행이 가능하다.
따라서, 대부분의 어플리케이션은 그냥 컴파일만 하면 사용할 수 있다.

그런데, 가끔씩은 자신이 어떤 환경에서 동작되는 어떤 프로그램인지 알아야 할 때가 있다.

ARM64 윈도우가 등장하기 전까지는 이 조건이 그렇게 복잡하지 않았다.
다음 중 하나인지만 확인하면 충분했다.

  1. x86 윈도우에서 실행되는 x86 어플리케이션 (x86 native)
  2. x64 윈도우에서 실행되는 x86 어플리케이션 (WOW64)
  3. x64 윈도우에서 실행되는 x64 어플리케이션 (x64 native)

이 중 3번은 확인하기가 어렵지도 않았다.
x64 어플리케이션이면 윈도우는 x64 환경일 수 밖에 없기 때문이었다.

하지만 ARM64 윈도우가 등장한 이후로는 많은 것이 달라졌다.
(물론 ARM 32bit 윈도우도 있지만, 이건 사용자가 워낙 없으니 무시…)

위의 조건에 추가해서 다음의 조건들을 더 확인할 수 있어야 한다.

  1. ARM64 윈도우에서 실행되는 ARM64 어플리케이션 (ARM64 native)
  2. ARM64 윈도우에서 실행되는 x86 어플리케이션
  3. ARM64 윈도우에서 실행되는 x64 어플리케이션

이 정도로 복잡한 결과를 얻으려면 함수를 제대로 하나 만드는 게 좋다.
이를 위해 결과로 받을 타입을 선언한다.

enum class RuntimeKind {
    Arm64_Native,
    X86_on_X64,
    X86_Native,
    X64_Native,
    X86_on_Arm64,
    X64_on_Arm64,
    Unknown
};

이 기능은 kernel32.dllIsWow64Process2()GetProcessInformation() 를 이용해 구현한다.
이를 위해선 다음과 같이 선언해야 한다.

#include <windows.h>
#include <processthreadsapi.h>

// Windows 11 SDK 미만에서도 빌드되도록 값만 정의
#ifndef ProcessMachineTypeInfo
#define ProcessMachineTypeInfo static_cast<PROCESS_INFORMATION_CLASS>(9)
#endif

// Windows 11 SDK에 있는 구조체를 최소 필드로 재선언
#ifndef _PROCESS_MACHINE_INFORMATION_DEFINED
#define _PROCESS_MACHINE_INFORMATION_DEFINED
typedef struct _PROCESS_MACHINE_INFORMATION {
    USHORT ProcessMachine;   // IMAGE_FILE_MACHINE_*
    USHORT Res0;
    ULONG  MachineAttributes; // (여기선 미사용)
} PROCESS_MACHINE_INFORMATION, *PPROCESS_MACHINE_INFORMATION;
#endif

필요한 선언을 다 했으면, 마지막으로 해당 함수를 구현한다.

template<typename T>
static T GetK32Proc(LPCSTR name) {
    HMODULE h = GetModuleHandleW(L"kernel32.dll");
    return h ? reinterpret_cast<T>(GetProcAddress(h, name)) : nullptr;
}

RuntimeKind DetectRuntimeKind() {
    using IsWow64Process2_t = BOOL (WINAPI*)(HANDLE, USHORT*, USHORT*);
    using GetProcessInformation_t = BOOL (WINAPI*)(HANDLE, PROCESS_INFORMATION_CLASS, LPVOID, DWORD);

    auto pIsWow64Process2      = GetK32Proc<IsWow64Process2_t>("IsWow64Process2");
    auto pGetProcessInformation = GetK32Proc<GetProcessInformation_t>("GetProcessInformation");

    USHORT proc = IMAGE_FILE_MACHINE_UNKNOWN;
    USHORT host = IMAGE_FILE_MACHINE_UNKNOWN;

    if (pIsWow64Process2 && pIsWow64Process2(GetCurrentProcess(), &proc, &host)) {
        // --- ARM64 호스트 ---
        if (host == IMAGE_FILE_MACHINE_ARM64) {
            if (proc == IMAGE_FILE_MACHINE_I386)
                return RuntimeKind::X86_on_Arm64;

            // WOW64 아님 → x64 네이티브 또는 ARM64 네이티브
            if (proc == IMAGE_FILE_MACHINE_UNKNOWN) {
                if (pGetProcessInformation) {
                    PROCESS_MACHINE_INFORMATION mi{};
                    if (pGetProcessInformation(GetCurrentProcess(), ProcessMachineTypeInfo, &mi, sizeof(mi))) {
                        if (mi.ProcessMachine == IMAGE_FILE_MACHINE_ARM64)
                            return RuntimeKind::Arm64_Native;
                        if (mi.ProcessMachine == IMAGE_FILE_MACHINE_AMD64)
                            return RuntimeKind::X64_on_Arm64;
                    }
                }
                // 보수적 추정 (구형 OS/SDK)
#if defined(_M_ARM64)
                return RuntimeKind::Arm64_Native;
#elif defined(_M_X64)
                return RuntimeKind::X64_on_Arm64;
#else
                return RuntimeKind::Unknown;
#endif
            }
        }

        // --- x64 호스트 ---
        if (host == IMAGE_FILE_MACHINE_AMD64) {
            if (proc == IMAGE_FILE_MACHINE_I386)
                return RuntimeKind::X86_on_X64;
            if (proc == IMAGE_FILE_MACHINE_UNKNOWN)
                return RuntimeKind::X64_Native;
        }

        // --- 32비트 호스트 ---
        if (host == IMAGE_FILE_MACHINE_I386)
            return RuntimeKind::X86_Native;
    } else {
        // 레거시 대비: IsWow64Process2 불가 시
        BOOL isWow64 = FALSE;
        using IsWow64Process_t = BOOL (WINAPI*)(HANDLE, PBOOL);
        if (auto pIsWow64Process = GetK32Proc<IsWow64Process_t>("IsWow64Process")) {
            if (pIsWow64Process(GetCurrentProcess(), &isWow64) && isWow64)
                return RuntimeKind::X86_on_X64;
        }
        SYSTEM_INFO si{};
        GetNativeSystemInfo(&si);
        switch (si.wProcessorArchitecture) {
            case PROCESSOR_ARCHITECTURE_AMD64: return RuntimeKind::X64_Native;
            case PROCESSOR_ARCHITECTURE_INTEL: return RuntimeKind::X86_Native;
            case PROCESSOR_ARCHITECTURE_ARM64: return RuntimeKind::Unknown; // 구형 조합: 정보 부족
            default: return RuntimeKind::Unknown;
        }
    }
    return RuntimeKind::Unknown;
}

참고로, 이 포스팅은 구라제거기ARM64를 지원하는 과정에서 구현한 내용이다.


좀 더 생각해보니 ARM64 프로세스인 경우는 굳이 API를 호출할 필요가 없다.
컴파일 타임에서 확정이 가능하기 때문.
프로세스가 x86/x64인 경우만 API를 호출하면 된다.

이를 반영해서 살짝 수정하면 아래와 같다.

RuntimeKind DetectRuntimeKind() {
    // --- 컴파일 타임에서 확정 가능한 경우 ---
#if defined(_M_ARM64)
    return RuntimeKind::Arm64_Native; // ARM64 빌드 → 무조건 ARM64 네이티브
#else
    using IsWow64Process2_t = BOOL(WINAPI*)(HANDLE, USHORT*, USHORT*);
    using GetProcessInformation_t = BOOL(WINAPI*)(HANDLE, PROCESS_INFORMATION_CLASS, LPVOID, DWORD);

    auto pIsWow64Process2{ GetK32Proc<IsWow64Process2_t>("IsWow64Process2") };
    auto pGetProcessInformation{ GetK32Proc<GetProcessInformation_t>("GetProcessInformation") };

    USHORT proc{ IMAGE_FILE_MACHINE_UNKNOWN };
    USHORT host{ IMAGE_FILE_MACHINE_UNKNOWN };

    if (pIsWow64Process2 && pIsWow64Process2(GetCurrentProcess(), &proc, &host))
    {
        // --- ARM64 호스트 ---
        if (host == IMAGE_FILE_MACHINE_ARM64)
        {
            if (proc == IMAGE_FILE_MACHINE_I386)
                return RuntimeKind::X86_on_Arm64;
            if (proc == IMAGE_FILE_MACHINE_AMD64)
                return RuntimeKind::X64_on_Arm64;
            if (proc == IMAGE_FILE_MACHINE_UNKNOWN)
            {
                // ProcessMachine이 UNKNOWN이면 ARM64 네이티브 또는 X64 on ARM64
                if (pGetProcessInformation)
                {
                    PROCESS_MACHINE_INFORMATION mi{};
                    if (pGetProcessInformation(GetCurrentProcess(), ProcessMachineTypeInfo, &mi, sizeof(mi)))
                    {
                        if (mi.ProcessMachine == IMAGE_FILE_MACHINE_ARM64)
                            return RuntimeKind::Arm64_Native;
                        if (mi.ProcessMachine == IMAGE_FILE_MACHINE_AMD64)
                            return RuntimeKind::X64_on_Arm64;
                    }
                }
                return RuntimeKind::Arm64_Native; // 보수적 추정
            }
        }

        // --- x64 호스트 ---
        if (host == IMAGE_FILE_MACHINE_AMD64)
        {
            if (proc == IMAGE_FILE_MACHINE_I386)
                return RuntimeKind::X86_on_X64;
            if (proc == IMAGE_FILE_MACHINE_UNKNOWN)
                return RuntimeKind::X64_Native;
        }

        // --- x86 호스트 ---
        if (host == IMAGE_FILE_MACHINE_I386)
            return RuntimeKind::X86_Native;
    }
    else
    {
        // 레거시 호환
        BOOL isWow64{ FALSE };
        using IsWow64Process_t = BOOL(WINAPI*)(HANDLE, PBOOL);
        if (auto pIsWow64Process = GetK32Proc<IsWow64Process_t>("IsWow64Process"))
        {
            if (pIsWow64Process(GetCurrentProcess(), &isWow64) && isWow64)
                return RuntimeKind::X86_on_X64;
        }
        SYSTEM_INFO si{};
        GetNativeSystemInfo(&si);
        switch (si.wProcessorArchitecture)
        {
        case PROCESSOR_ARCHITECTURE_AMD64:
            return RuntimeKind::X64_Native;
        case PROCESSOR_ARCHITECTURE_INTEL:
            return RuntimeKind::X86_Native;
        case PROCESSOR_ARCHITECTURE_ARM64:
            return RuntimeKind::Arm64_Native; // 구형 환경 추정
        default:
            return RuntimeKind::Unknown;
        }
    }

    return RuntimeKind::Unknown;
#endif

카테고리:

업데이트: