◎ Part 4: WindowProc + WinMain (메시지 처리 & 진입점)

 // ------------ WindowProc sub ----------

WNDPROC g_OldEditProc = NULL;

LRESULT CALLBACK EditProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if (msg == WM_KEYDOWN)
    {
        if (wParam == 'A' && (GetKeyState(VK_CONTROL) & 0x8000))
        {
            SendMessage(hwnd, EM_SETSEL, 0, -1); // Ctrl+A 전체 선택
            return 0; // 기본 EDIT 처리 막기
        }
    }
    return CallWindowProc(g_OldEditProc, hwnd, msg, wParam, lParam);
}

// ------------ WindowProc ------------

LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_CREATE:
        CreateControls(hwnd);

        // Input Edit 서브클래싱
        g_OldEditProc = (WNDPROC)SetWindowLongPtr(
            hInputEdit,
            GWLP_WNDPROC,
            (LONG_PTR)EditProc
        );

        // Output Edit도 동일하게 (Ctrl+A 허용)
        SetWindowLongPtr(
            hOutputEdit,
            GWLP_WNDPROC,
            (LONG_PTR)EditProc
        );
        return 0;

    case WM_COMMAND:
    {
        int btnId = LOWORD(wParam);

        static char input[MAX_BUFFER] = { 0 };
        static char output[MAX_BUFFER] = { 0 };

        GetWindowTextA(hInputEdit, input, MAX_BUFFER - 1);
        output[0] = '\0'; // 이전 결과 제거

        bool converted = true;

        switch (btnId)
        {
        case IDC_TEXT_TO_HEX: textToHex(input, output, MAX_BUFFER); break;
        case IDC_HEX_TO_TEXT: hexToText(input, output, MAX_BUFFER); break;
        case IDC_DEC_TO_HEX: decToHex(input, output, MAX_BUFFER); break;
        case IDC_HEX_TO_DEC: hexToDec(input, output, MAX_BUFFER); break;
        case IDC_TEXT_TO_DEC: textToDec(input, output, MAX_BUFFER); break;
        case IDC_DEC_TO_TEXT: decToText(input, output, MAX_BUFFER); break;
        case IDC_DEC_TO_OCTAL: decToOctal(input, output, MAX_BUFFER); break;
        case IDC_OCTAL_TO_DEC: octalToDec(input, output, MAX_BUFFER); break;
        case IDC_TEXT_TO_UTF7: textToUtf7(input, output, MAX_BUFFER); break;
        case IDC_UTF7_TO_TEXT: utf7ToText(input, output, MAX_BUFFER); break;
        case IDC_HEX_TO_UCS2: hexToUcs2(input, output, MAX_BUFFER); break;
        case IDC_UCS2_TO_HEX: ucs2ToHex(input, output, MAX_BUFFER); break;
        case IDC_TEXT_TO_BINARY: textToBinary(input, output, MAX_BUFFER); break;
        case IDC_BINARY_TO_TEXT: binaryToText(input, output, MAX_BUFFER); break;
        case IDC_ESCAPE: escapeString(input, output, MAX_BUFFER); break;
        case IDC_UNESCAPE: unescapeString(input, output, MAX_BUFFER); break;
        case IDC_ENCODE_HTML: encodeHTML(input, output, MAX_BUFFER); break;
        case IDC_DECODE_HTML: decodeHTML(input, output, MAX_BUFFER); break;
        case IDC_TEXT_TO_BASE64: encodeBase64(input, output, MAX_BUFFER); break;
        case IDC_BASE64_TO_TEXT: decodeBase64(input, output, MAX_BUFFER); break;
        case IDC_HEX_TO_BASE64: hexToBase64(input, output, MAX_BUFFER); break;
        case IDC_BASE64_TO_HEX: base64ToHex(input, output, MAX_BUFFER); break;
        case IDC_COPY_OUTPUT:
        {
            static char buf[MAX_BUFFER] = { 0 };
            GetWindowTextA(hOutputEdit, buf, MAX_BUFFER - 1);
            copyToClipboard(buf);
            converted = false;
            break;
        }

        case IDC_COPY_TO_INPUT:
        {
            static char buf[MAX_BUFFER] = { 0 };
            GetWindowTextA(hOutputEdit, buf, MAX_BUFFER - 1);
            SetWindowTextA(hInputEdit, buf);
            converted = false;
            break;
        }

        case IDC_CLEAR_ALL:
            SetWindowTextA(hInputEdit, "");
            SetWindowTextA(hOutputEdit, "");
            converted = false;
            break;

        default:
            converted = false;
            break;
        }

        // 핵심: 변환 버튼이면 결과 출력
        if (converted)
            SetWindowTextA(hOutputEdit, output);

        return 0;
    }

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProcW(hwnd, msg, wParam, lParam);
}



// ------------ WinMain ------------

    int WINAPI WinMain(
        _In_ HINSTANCE hInst,
        _In_opt_ HINSTANCE hPrev,
        _In_ LPSTR lpCmd,
        _In_ int nCmdShow)
    {
        const wchar_t CLASS_NAME[] = L"ConvToolImproved";

        WNDCLASSW wc = { 0 };
        wc.lpfnWndProc = WindowProc;
        wc.hInstance = hInst;
        wc.lpszClassName = CLASS_NAME;
        wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
        wc.hCursor = LoadCursorW(NULL, IDC_ARROW);

        if (!RegisterClassW(&wc)) return 0;

        HWND hwnd = CreateWindowExW(
            0,
            CLASS_NAME,
            L"Data Conversion Tool",
            WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX & ~WS_THICKFRAME,
            CW_USEDEFAULT, CW_USEDEFAULT,
            680, 480,
            NULL, NULL, hInst, NULL);

        if (!hwnd) return 0;

        ShowWindow(hwnd, nCmdShow);
        UpdateWindow(hwnd);

        MSG msg = { 0 };
        while (GetMessageW(&msg, NULL, 0, 0) > 0) {
            TranslateMessage(&msg);
            DispatchMessageW(&msg);
        }
        return (int)msg.wParam;
    }

 

 - WindowProc: 메시지 처리

LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
    case WM_CREATE:
        CreateControls(hwnd);
        break;
  • 윈도우 생성될 때: CreateControls()를 호출해서 모든 UI 생성.
    case WM_COMMAND: {
        int btnId = LOWORD(wParam);
        char* input = (char*)malloc(MAX_BUFFER);
        char* output = (char*)malloc(MAX_BUFFER);
        if (!input || !output) {
            if (input)  free(input);
            if (output) free(output);
            break;
        }
        ZeroMemory(input, MAX_BUFFER);
        ZeroMemory(output, MAX_BUFFER);
  • 사용자가 버튼을 클릭할 때 발생.
  • btnId : 클릭된 버튼의 ID (201~228 중 하나).
  • 동적 메모리 할당 : 8KB짜리 입력/출력 버퍼.
  • ZeroMemory() : 메모리를 0으로 초기화 (쓰레기값 제거).
        GetWindowTextA(hInputEdit, input, MAX_BUFFER - 1);
  • 입력 텍스트박스에서 사용자가 입력한 텍스트를 읽기.
        switch (btnId) {
        case IDC_TEXT_TO_HEX:    textToHex(input, output, MAX_BUFFER);    break;
        case IDC_HEX_TO_TEXT:    hexToText(input, output, MAX_BUFFER);    break;
        // ... 등등
        case IDC_TEXT_TO_BASE64: encodeBase64(input, output, MAX_BUFFER); break;
        case IDC_BASE64_TO_TEXT: decodeBase64(input, output, MAX_BUFFER); break;
        // ...
        }
  • 클릭된 버튼에 따라 해당 변환 함수 호출.
  • 예: IDC_TEXT_TO_HEX면 textToHex() 함수 실행.
        case IDC_COPY_OUTPUT: {
            char* buf = (char*)malloc(MAX_BUFFER);
            if (buf) {
                GetWindowTextA(hOutputEdit, buf, MAX_BUFFER - 1);
                copyToClipboard(buf);
                free(buf);
            }
            free(input); free(output);
            return 0;
        }
  • "Copy Output to Clipboard" 버튼
    >> 출력 텍스트박스의 내용을 읽고
    >> copyToClipboard()로 클립보드에 복사
    >> 메모리 해제 후 바로 반환 (SetWindowTextA 호출 안 함).
        case IDC_COPY_TO_INPUT: {
            char* buf = (char*)malloc(MAX_BUFFER);
            if (buf) {
                GetWindowTextA(hOutputEdit, buf, MAX_BUFFER - 1);
                SetWindowTextA(hInputEdit, buf);
                free(buf);
            }
            free(input); free(output);
            return 0;
        }
  • "Copy Output to Input" 버튼
    >> 출력 내용을 입력 텍스트박스에 붙여넣기.
    >> 메모리 해제.
        case IDC_CLEAR_ALL:
            SetWindowTextA(hInputEdit, "");
            SetWindowTextA(hOutputEdit, "");
            free(input); free(output);
            return 0;
  • "Clear All" 버튼: 입력/출력 박스 모두 비우기.
        SetWindowTextA(hOutputEdit, output);

        free(input);
        free(output);
        break;
    }
  • 변환 함수 호출 후 결과를 출력 텍스트박스에 표시.
  • 메모리 해제 (malloc 되었으니 반드시 필요).
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProcW(hwnd, msg, wParam, lParam);
}
  • 윈도우가 닫힐 때 프로그램 종료.

 

 - WinMain: 진입점

int WINAPI WinMain(
    _In_ HINSTANCE hInst,
    _In_opt_ HINSTANCE hPrev,
    _In_ LPSTR lpCmd,
    _In_ int nCmdShow)
{
    const wchar_t CLASS_NAME[] = L"ConvToolImproved";
  • 프로그램의 시작점.
  • hInst : 현재 프로그램의 인스턴스 핸들.
  • nCmdShow : 윈도우를 어떻게 보여줄지 (최소화/정상/최대화).
  • CLASS_NAME : 윈도우 클래스 이름 (등록할 클래스 이름).
    WNDCLASSW wc = { 0 };
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hInst;
    wc.lpszClassName = CLASS_NAME;
    wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
    wc.hCursor = LoadCursorW(NULL, IDC_ARROW);

    if (!RegisterClassW(&wc)) return 0;
  • 윈도우 클래스 정의:
    >> lpfnWndProc : 메시지 처리 함수 (WindowProc).
    >> hInstance : 인스턴스.
    >> lpszClassName : 클래스 이름.
    >> hbrBackground : 배경색 (버튼 면 색상).
    >> hCursor : 마우스 커서 (기본 화살표).
    >> RegisterClassW() : 이 클래스를 윈도우 시스템에 등록.
    HWND hwnd = CreateWindowExW(
        0,
        CLASS_NAME,
        L"Data Conversion Tool",
        WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX & ~WS_THICKFRAME,
        CW_USEDEFAULT, CW_USEDEFAULT,
        680, 480,
        NULL, NULL, hInst, NULL);

    if (!hwnd) return 0;
  • 실제 윈도우 생성
    >> CLASS_NAME : 위에서 등록한 클래스 사용
    >> L"Data Conversion Tool" : 타이틀 바에 표시될 제목
    >> WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX & ~WS_THICKFRAME :
       >>> 기본 윈도우 스타일에서
       >>> 최대화 버튼 제거 (~WS_MAXIMIZEBOX)
       >>> 크기 조절 불가능 (~WS_THICKFRAME)
    >> 680, 480 : 초기 윈도우 크기 (680×480).
    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);
  • 윈도우 표시 (ShowWindow).
  • 윈도우 갱신 (UpdateWindow) - 화면에 즉시 그리기.
    MSG msg = { 0 };
    while (GetMessageW(&msg, NULL, 0, 0) > 0) {
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }
    return (int)msg.wParam;
  • 메시지 루프 (프로그램의 핵심):
    >> GetMessageW() : 윈도우 메시지 대기 (사용자 클릭, 키 입력 등).
    >> TranslateMessage() : 키 입력을 문자로 변환.
    >> DispatchMessageW() : 메시지를 WindowProc로 전달.
    >> 루프는 WM_QUIT 메시지를 받을 때까지 계속 반복.


◎ 실행 흐름 요약

1. WinMain 시작
   ↓
2. 윈도우 클래스 등록 (WNDCLASSW)
   ↓
3. 윈도우 생성 (CreateWindowExW)
   ↓
4. 윈도우 표시 (ShowWindow)
   ↓
5. 메시지 루프 시작 (while GetMessageW)
   ├─ 사용자 입력 대기
   ├─ WindowProc에서 메시지 처리
   │  ├─ WM_CREATE : CreateControls() 호출 (UI 생성)
   │  ├─ WM_COMMAND : 버튼 클릭 감지
   │  │  ├─ 변환 함수 호출 (textToHex, hexToText 등)
   │  │  └─ 결과를 출력 텍스트박스에 표시
   │  └─ WM_DESTROY : PostQuitMessage(0)
   │
   └─ 루프 종료 (WM_QUIT 받음)
   ↓
6. 프로그램 종료

 

◎ Part 3: CreateControls 함수 (UI 생성)

// ------------ CreateControls (WM_CREATE 분리) ------------

void CreateControls(HWND hwnd) {
    HFONT hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);

    // Input label
    HWND hStatic1 = CreateWindowW(L"STATIC", L"Input",
        WS_CHILD | WS_VISIBLE,
        10, 10, 100, 20,
        hwnd, NULL, NULL, NULL);
    if (hStatic1 && hFont) {
        SendMessage(hStatic1, WM_SETFONT, (WPARAM)hFont, TRUE);
    }

    // Input edit
    hInputEdit = CreateWindowExW(
        WS_EX_CLIENTEDGE,
        L"EDIT", L"",
        WS_CHILD | WS_VISIBLE | WS_VSCROLL |
        ES_MULTILINE | ES_WANTRETURN | ES_AUTOVSCROLL,
        10, 30, 400, 180,
        hwnd, (HMENU)(INT_PTR)IDC_INPUT_TEXT,
        NULL, NULL);
    if (hInputEdit && hFont) {
        SendMessage(hInputEdit, WM_SETFONT, (WPARAM)hFont, TRUE);
    }

    // Output label
    HWND hStatic2 = CreateWindowW(L"STATIC", L"Output",
        WS_CHILD | WS_VISIBLE,
        10, 220, 100, 20,
        hwnd, NULL, NULL, NULL);
    if (hStatic2 && hFont) {
        SendMessage(hStatic2, WM_SETFONT, (WPARAM)hFont, TRUE);
    }

    // Output edit
    hOutputEdit = CreateWindowExW(
        WS_EX_CLIENTEDGE,
        L"EDIT", L"",
        WS_CHILD | WS_VISIBLE | WS_VSCROLL |
        ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY,
        10, 240, 400, 180,
        hwnd, (HMENU)(INT_PTR)IDC_OUTPUT_TEXT,
        NULL, NULL);
    if (hOutputEdit && hFont) {
        SendMessage(hOutputEdit, WM_SETFONT, (WPARAM)hFont, TRUE);
    }

    // Group box
    CreateWindowW(L"BUTTON", L"Conversion Options",
        WS_CHILD | WS_VISIBLE | BS_GROUPBOX,
        420, 10, 235, 335,
        hwnd, NULL, NULL, NULL);

    int x = 430, y = 30, w = 105, h = 24, gap = 4;

    // Row 1
    CreateWindowW(L"BUTTON", L"Text to Hex",
        WS_CHILD | WS_VISIBLE, x, y, w, h,
        hwnd, (HMENU)(INT_PTR)IDC_TEXT_TO_HEX, NULL, NULL);
    CreateWindowW(L"BUTTON", L"Hex to Text",
        WS_CHILD | WS_VISIBLE, x + w + gap, y, w, h,
        hwnd, (HMENU)(INT_PTR)IDC_HEX_TO_TEXT, NULL, NULL);
    y += h + gap;

    // Row 2
    CreateWindowW(L"BUTTON", L"Dec to Hex",
        WS_CHILD | WS_VISIBLE, x, y, w, h,
        hwnd, (HMENU)(INT_PTR)IDC_DEC_TO_HEX, NULL, NULL);
    CreateWindowW(L"BUTTON", L"Hex to Dec",
        WS_CHILD | WS_VISIBLE, x + w + gap, y, w, h,
        hwnd, (HMENU)(INT_PTR)IDC_HEX_TO_DEC, NULL, NULL);
    y += h + gap;

    // Row 3
    CreateWindowW(L"BUTTON", L"Text to Dec",
        WS_CHILD | WS_VISIBLE, x, y, w, h,
        hwnd, (HMENU)(INT_PTR)IDC_TEXT_TO_DEC, NULL, NULL);
    CreateWindowW(L"BUTTON", L"Dec to Text",
        WS_CHILD | WS_VISIBLE, x + w + gap, y, w, h,
        hwnd, (HMENU)(INT_PTR)IDC_DEC_TO_TEXT, NULL, NULL);
    y += h + gap;

    // Row 4
    CreateWindowW(L"BUTTON", L"Dec to Octal",
        WS_CHILD | WS_VISIBLE, x, y, w, h,
        hwnd, (HMENU)(INT_PTR)IDC_DEC_TO_OCTAL, NULL, NULL);
    CreateWindowW(L"BUTTON", L"Octal to Dec",
        WS_CHILD | WS_VISIBLE, x + w + gap, y, w, h,
        hwnd, (HMENU)(INT_PTR)IDC_OCTAL_TO_DEC, NULL, NULL);
    y += h + gap;

    // Row 5
    CreateWindowW(L"BUTTON", L"Text to UTF7",
        WS_CHILD | WS_VISIBLE, x, y, w, h,
        hwnd, (HMENU)(INT_PTR)IDC_TEXT_TO_UTF7, NULL, NULL);
    CreateWindowW(L"BUTTON", L"UTF7 to Text",
        WS_CHILD | WS_VISIBLE, x + w + gap, y, w, h,
        hwnd, (HMENU)(INT_PTR)IDC_UTF7_TO_TEXT, NULL, NULL);
    y += h + gap;

    // Row 6
    CreateWindowW(L"BUTTON", L"Hex to UCS2",
        WS_CHILD | WS_VISIBLE, x, y, w, h,
        hwnd, (HMENU)(INT_PTR)IDC_HEX_TO_UCS2, NULL, NULL);
    CreateWindowW(L"BUTTON", L"UCS2 to Hex",
        WS_CHILD | WS_VISIBLE, x + w + gap, y, w, h,
        hwnd, (HMENU)(INT_PTR)IDC_UCS2_TO_HEX, NULL, NULL);
    y += h + gap;

    // Row 7
    CreateWindowW(L"BUTTON", L"Text to Binary",
        WS_CHILD | WS_VISIBLE, x, y, w, h,
        hwnd, (HMENU)(INT_PTR)IDC_TEXT_TO_BINARY, NULL, NULL);
    CreateWindowW(L"BUTTON", L"Binary to Text",
        WS_CHILD | WS_VISIBLE, x + w + gap, y, w, h,
        hwnd, (HMENU)(INT_PTR)IDC_BINARY_TO_TEXT, NULL, NULL);
    y += h + gap;

    // Row 8
    CreateWindowW(L"BUTTON", L"Escape",
        WS_CHILD | WS_VISIBLE, x, y, w, h,
        hwnd, (HMENU)(INT_PTR)IDC_ESCAPE, NULL, NULL);
    CreateWindowW(L"BUTTON", L"Unescape",
        WS_CHILD | WS_VISIBLE, x + w + gap, y, w, h,
        hwnd, (HMENU)(INT_PTR)IDC_UNESCAPE, NULL, NULL);
    y += h + gap;

    // Row 9
    CreateWindowW(L"BUTTON", L"Encode HTML",
        WS_CHILD | WS_VISIBLE, x, y, w, h,
        hwnd, (HMENU)(INT_PTR)IDC_ENCODE_HTML, NULL, NULL);
    CreateWindowW(L"BUTTON", L"Decode HTML",
        WS_CHILD | WS_VISIBLE, x + w + gap, y, w, h,
        hwnd, (HMENU)(INT_PTR)IDC_DECODE_HTML, NULL, NULL);
    y += h + gap;

    // Row 10
    CreateWindowW(L"BUTTON", L"Text to Base64",
        WS_CHILD | WS_VISIBLE, x, y, w, h,
        hwnd, (HMENU)(INT_PTR)IDC_TEXT_TO_BASE64, NULL, NULL);
    CreateWindowW(L"BUTTON", L"Base64 to Text",
        WS_CHILD | WS_VISIBLE, x + w + gap, y, w, h,
        hwnd, (HMENU)(INT_PTR)IDC_BASE64_TO_TEXT, NULL, NULL);
    y += h + gap;

    // Row 11
    CreateWindowW(L"BUTTON", L"Hex to Base64",
        WS_CHILD | WS_VISIBLE, x, y, w, h,
        hwnd, (HMENU)(INT_PTR)IDC_HEX_TO_BASE64, NULL, NULL);
    CreateWindowW(L"BUTTON", L"Base64 to Hex",
        WS_CHILD | WS_VISIBLE, x + w + gap, y, w, h,
        hwnd, (HMENU)(INT_PTR)IDC_BASE64_TO_HEX, NULL, NULL);

    // Utility buttons
    y = 350;
    CreateWindowW(L"BUTTON", L"Copy Output to Clipboard",
        WS_CHILD | WS_VISIBLE, 435, y, 200, h,
        hwnd, (HMENU)(INT_PTR)IDC_COPY_OUTPUT, NULL, NULL);
    y += h + gap;
    CreateWindowW(L"BUTTON", L"Copy Output to Input",
        WS_CHILD | WS_VISIBLE, 435, y, 200, h,
        hwnd, (HMENU)(INT_PTR)IDC_COPY_TO_INPUT, NULL, NULL);
    y += h + gap;
    CreateWindowW(L"BUTTON", L"Clear All",
        WS_CHILD | WS_VISIBLE, 435, y, 200, h,
        hwnd, (HMENU)(INT_PTR)IDC_CLEAR_ALL, NULL, NULL);
}

 

 - 큰 스택 사용량을 CreateControls() 함수로 분리

void CreateControls(HWND hwnd) {
    HFONT hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
  • 기본 GUI 폰트 가져오기 (시스템 기본값).
  • 모든 컨트롤에 동일한 폰트 적용.
  • Input 영역 생성
    HWND hStatic1 = CreateWindowW(L"STATIC", L"Input",
        WS_CHILD | WS_VISIBLE,
        10, 10, 100, 20,
        hwnd, NULL, NULL, NULL);
    if (hStatic1 && hFont) {
        SendMessage(hStatic1, WM_SETFONT, (WPARAM)hFont, TRUE);
    }
  • "Input" 레이블 생성.
  • 위치: (10, 10), 크기: 100×20.
  • SendMessage : 폰트 설정 메시지 전송.
   hInputEdit = CreateWindowExW(
        WS_EX_CLIENTEDGE,
        L"EDIT", L"",
        WS_CHILD | WS_VISIBLE | WS_VSCROLL |
        ES_MULTILINE | ES_WANTRETURN | ES_AUTOVSCROLL,
        10, 30, 400, 180,
        hwnd, (HMENU)(INT_PTR)IDC_INPUT_TEXT,
        NULL, NULL);
  • 입력 텍스트박스 생성.
  • WS_EX_CLIENTEDGE : 테두리 스타일 (3D 효과).
  • ES_MULTILINE : 여러 줄 입력 가능.
  • ES_WANTRETURN : Enter 키로 줄바꿈 (실행이 아님).
  • ES_AUTOVSCROLL : 자동 스크롤.
  • 위치: (10, 30), 크기: 400×180.
  • ID: IDC_INPUT_TEXT (101).
  • Output 영역 생성
   hOutputEdit = CreateWindowExW(
        WS_EX_CLIENTEDGE,
        L"EDIT", L"",
        WS_CHILD | WS_VISIBLE | WS_VSCROLL |
        ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY,
        10, 240, 400, 180,
        hwnd, (HMENU)(INT_PTR)IDC_OUTPUT_TEXT,
        NULL, NULL);
  • 출력 텍스트박스 (읽기 전용).
  • ES_READONLY : 사용자가 수정할 수 없음.
  • 변환 버튼들 생성
    int x = 430, y = 30, w = 105, h = 24, gap = 4;

    // Row 1
    CreateWindowW(L"BUTTON", L"Text to Hex",
        WS_CHILD | WS_VISIBLE, x, y, w, h,
        hwnd, (HMENU)(INT_PTR)IDC_TEXT_TO_HEX, NULL, NULL);
    CreateWindowW(L"BUTTON", L"Hex to Text",
        WS_CHILD | WS_VISIBLE, x + w + gap, y, w, h,
        hwnd, (HMENU)(INT_PTR)IDC_HEX_TO_TEXT, NULL, NULL);
    y += h + gap;
  • 변수 x, y, w, h, gap로 레이아웃 계산
    >> x = 430 : 오른쪽 시작 위치.
    >> y = 30 : 첫 행 위치.
    >> w = 105 : 버튼 너비.
    >> h = 24 : 버튼 높이.
    >> gap = 4 : 버튼 간격.

 

  • 첫 번째 버튼: (430, 30) 위치, 105×24 크기.
  • 두 번째 버튼: (430 + 105 + 4, 30) = (539, 30) 위치 (옆에 배치).
  • y += h + gap : 다음 행으로 이동 (24 + 4 = 28픽셀 아래).

 

 - 이 패턴을 11개 행(각 2개 버튼)으로 반복

Row 1:  [Text to Hex]  [Hex to Text]
Row 2:  [Dec to Hex]   [Hex to Dec]
Row 3:  [Text to Dec]  [Dec to Text]
...
Row 11: [Hex to Base64] [Base64 to Hex]


 - 유틸리티 버튼들

    y = 350;
    CreateWindowW(L"BUTTON", L"Copy Output to Clipboard",
        WS_CHILD | WS_VISIBLE, 435, y, 200, h,
        hwnd, (HMENU)(INT_PTR)IDC_COPY_OUTPUT, NULL, NULL);
    y += h + gap;
    CreateWindowW(L"BUTTON", L"Copy Output to Input",
        WS_CHILD | WS_VISIBLE, 435, y, 200, h,
        hwnd, (HMENU)(INT_PTR)IDC_COPY_TO_INPUT, NULL, NULL);
    y += h + gap;
    CreateWindowW(L"BUTTON", L"Clear All",
        WS_CHILD | WS_VISIBLE, 435, y, 200, h,
        hwnd, (HMENU)(INT_PTR)IDC_CLEAR_ALL, NULL, NULL);
  • 세 개의 큰 버튼 (200픽셀 너비).
  • y = 350 : 변환 버튼들 아래에 배치.

◎ Part 2: 헤더 + 매크로 + 변환 함수들

// Base64 table
const char* base64_table =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

// ------------ 변환 함수들 ------------

// Base64 encode
bool encodeBase64(const char* input, char* output, size_t outSize) {
    // 입력 검증
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t len = strlen(input);
    size_t required = ((len + 2) / 3) * 4 + 1;  // +1 for null terminator

    // 버퍼 크기 검증
    if (outSize < required) {
        return false;
    }

    size_t outPos = 0;

    for (size_t i = 0; i < len; i += 3) {
        unsigned char b1 = (unsigned char)input[i];
        unsigned char b2 = (i + 1 < len) ? (unsigned char)input[i + 1] : 0;
        unsigned char b3 = (i + 2 < len) ? (unsigned char)input[i + 2] : 0;

        output[outPos++] = base64_table[(b1 >> 2) & 0x3F];
        output[outPos++] = base64_table[(((b1 & 0x03) << 4) | ((b2 >> 4) & 0x0F)) & 0x3F];
        output[outPos++] = (i + 1 < len)
            ? base64_table[(((b2 & 0x0F) << 2) | ((b3 >> 6) & 0x03)) & 0x3F]
            : '=';
        output[outPos++] = (i + 2 < len)
            ? base64_table[b3 & 0x3F]
            : '=';
    }

    output[outPos] = '\0';
    return true;
}

// Base64 decode
bool decodeBase64(const char* input, char* output, size_t outSize) {
    // Base64 디코딩 테이블 (signed char 사용)
    static const int8_t table[256] = {
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,
        52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,
        -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
        15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,
        -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
        41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
    };

    // 입력 검증
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t len = strlen(input);
    size_t outPos = 0;
    unsigned int val = 0;
    int bits = -8;

    for (size_t i = 0; i < len; i++) {
        unsigned char c = (unsigned char)input[i];

        // 공백 문자 스킵
        if (c == ' ' || c == '\n' || c == '\r' || c == '\t') {
            continue;
        }

        // 패딩 문자 처리
        if (c == '=') {
            break;
        }

        // 디코딩 테이블 조회
        int8_t decoded = table[c];
        if (decoded == -1) {
            // 잘못된 Base64 문자
            return false;
        }

        val = (val << 6) | (unsigned int)decoded;
        bits += 6;

        if (bits >= 0) {
            // 버퍼 오버플로우 체크
            if (outPos >= outSize - 1) {
                return false;
            }
            output[outPos++] = (char)((val >> bits) & 0xFF);
            bits -= 8;
        }
    }

    output[outPos] = '\0';
    return true;
}

// Text to Hex
bool textToHex(const char* input, char* output, size_t outSize) {
    static const char hexDigits[] = "0123456789ABCDEF";

    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t inputLen = strlen(input);

    if (inputLen == 0) {
        output[0] = '\0';
        return true;
    }

    // 필요한 크기: inputLen * 3 (마지막 공백 포함) + 1 (null)
    // 실제로는 inputLen * 3 - 1 + 1 = inputLen * 3
    if (outSize < inputLen * 3) {
        return false;
    }

    size_t outPos = 0;

    for (size_t i = 0; i < inputLen; i++) {
        unsigned char byte = (unsigned char)input[i];

        output[outPos++] = hexDigits[(byte >> 4) & 0x0F];  // 상위 4비트
        output[outPos++] = hexDigits[byte & 0x0F];          // 하위 4비트

        // 마지막 문자가 아니면 공백 추가
        if (i < inputLen - 1) {
            output[outPos++] = ' ';
        }
    }

    output[outPos] = '\0';
    return true;
}

// Hex to Text
bool hexToText(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t outPos = 0;
    size_t i = 0;

    while (input[i] != '\0' && outPos < outSize - 1) {
        // 공백 및 구분자 스킵
        while (input[i] == ' ' || input[i] == ':' || input[i] == '-' ||
            input[i] == '\t' || input[i] == '\n' || input[i] == '\r') {
            if (input[i] == '\0') break;
            i++;
        }

        if (input[i] == '\0') break;

        // 첫 번째 hex 문자 확인
        int high = hexCharToValue(input[i]);
        if (high == -1) {
            return false;
        }

        // 두 번째 hex 문자 확인 (범위 체크)
        if (input[i + 1] == '\0') {
            return false;  // 홀수 개의 hex 문자
        }

        int low = hexCharToValue(input[i + 1]);
        if (low == -1) {
            return false;
        }

        // 바이트 조합
        output[outPos++] = (char)((high << 4) | low);
        i += 2;
    }

    output[outPos] = '\0';
    return true;
}

// Text to Decimal
bool textToDec(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t inputLen = strlen(input);

    if (inputLen == 0) {
        output[0] = '\0';
        return true;
    }

    // 필요한 크기: 최대 4바이트 * inputLen
    if (outSize < inputLen * 4 + 1) {
        return false;
    }

    size_t outPos = 0;

    for (size_t i = 0; i < inputLen; i++) {
        unsigned char byte = (unsigned char)input[i];

        // 버퍼 체크 (최대 4바이트 필요: "255 ")
        if (outPos + 4 >= outSize) {
            return false;
        }

        // 숫자 변환
        size_t numLen = uintToStr(byte, output + outPos);
        outPos += numLen;

        // 마지막이 아니면 공백 추가
        if (i < inputLen - 1) {
            output[outPos++] = ' ';
        }
    }

    output[outPos] = '\0';
    return true;
}

// Decimal to Text
bool decToText(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t outPos = 0;
    size_t i = 0;

    while (input[i] != '\0') {
        // 숫자가 아닌 문자 스킵
        while (input[i] != '\0' && !isdigit((unsigned char)input[i])) {
            i++;
        }

        if (input[i] == '\0') break;

        // 숫자 파싱 (수동으로 안전하게)
        int num = 0;
        int digitCount = 0;

        while (isdigit((unsigned char)input[i])) {
            int digit = input[i] - '0';

            // 오버플로우 체크 (255 초과 방지)
            if (num > 25 || (num == 25 && digit > 5)) {
                return false;  // 255 초과
            }

            num = num * 10 + digit;
            digitCount++;
            i++;

            // 숫자가 너무 길면 에러 (최대 3자리)
            if (digitCount > 3) {
                return false;
            }
        }

        // 출력 버퍼 체크
        if (outPos >= outSize - 1) {
            return false;
        }

        output[outPos++] = (char)num;
    }

    output[outPos] = '\0';
    return true;
}

// Decimal to Hex
bool decToHex(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t outPos = 0;
    size_t i = 0;
    bool firstNumber = true;

    while (input[i] != '\0') {
        // 숫자가 아닌 문자 스킵
        while (input[i] != '\0' && !isdigit((unsigned char)input[i])) {
            i++;
        }

        if (input[i] == '\0') break;

        // 10진수 파싱
        unsigned long num = 0;
        int digitCount = 0;

        while (isdigit((unsigned char)input[i])) {
            int digit = input[i] - '0';

            // 오버플로우 체크 (UINT_MAX = 4294967295)
            if (num > (UINT_MAX - digit) / 10) {
                return false;
            }

            num = num * 10 + digit;
            digitCount++;
            i++;

            // 최대 10자리
            if (digitCount > 10) {
                return false;
            }
        }

        // 공백 추가 (첫 번째가 아닌 경우)
        if (!firstNumber) {
            if (outPos >= outSize - 1) {
                return false;
            }
            output[outPos++] = ' ';
        }

        // 16진수 변환 (최대 8자리 필요)
        if (outPos + 8 >= outSize) {
            return false;
        }

        size_t hexLen = uintToHex((unsigned int)num, output + outPos);
        outPos += hexLen;
        firstNumber = false;
    }

    output[outPos] = '\0';
    return true;
}

// Hex to Decimal
bool hexToDec(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t outPos = 0;
    size_t i = 0;
    bool firstNumber = true;

    while (input[i] != '\0') {
        // hex 숫자가 아닌 문자 스킵
        while (input[i] != '\0' && !isxdigit((unsigned char)input[i])) {
            i++;
        }

        if (input[i] == '\0') break;

        // hex 파싱
        unsigned long num = 0;
        int digitCount = 0;

        while (isxdigit((unsigned char)input[i])) {
            int val = hexCharToValue(input[i]);
            if (val == -1) {
                return false;
            }

            // 오버플로우 체크 (UINT_MAX = 0xFFFFFFFF)
            if (num > (UINT_MAX >> 4)) {
                return false;
            }

            num = (num << 4) | val;
            digitCount++;
            i++;

            // 최대 8자리 (32비트)
            if (digitCount > 8) {
                return false;
            }
        }

        // 공백 추가 (첫 번째가 아닌 경우)
        if (!firstNumber) {
            if (outPos >= outSize - 1) {
                return false;
            }
            output[outPos++] = ' ';
        }

        // 10진수 변환 (최대 10자리 필요)
        if (outPos + 10 >= outSize) {
            return false;
        }

        size_t decLen = uintToDecimal((unsigned int)num, output + outPos);
        outPos += decLen;
        firstNumber = false;
    }

    output[outPos] = '\0';
    return true;
}

// Decimal to Octal
bool decToOctal(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t outPos = 0;
    size_t i = 0;
    bool firstNumber = true;

    // 32비트 unsigned int는 8진수로 최대 11자리 필요 (예: 4294967295 -> 37777777777)
    const size_t MAX_OCTAL_DIGITS = 11;

    while (input[i] != '\0') {
        // 숫자가 아닌 문자 스킵
        while (input[i] != '\0' && !isdigit((unsigned char)input[i])) {
            i++;
        }

        if (input[i] == '\0') break;

        // 10진수 파싱
        unsigned long num = 0;
        int digitCount = 0;

        while (isdigit((unsigned char)input[i])) {
            int digit = input[i] - '0';

            // 오버플로우 체크 (unsigned int 범위: UINT_MAX)
            // unsigned long을 사용하여 파싱하더라도, 최종 변환은 unsigned int 범위 내에서만 처리합니다.
            // num > (UINT_MAX - digit) / 10 대신, num이 이미 UINT_MAX를 초과했는지 확인하는 것이 더 명확합니다.
            // 하지만 기존 코드의 로직을 유지하면서 UINT_MAX까지만 지원합니다.
            if (num > (UINT_MAX - digit) / 10) {
                // 숫자가 UINT_MAX (4294967295)를 초과합니다.
                return false;
            }

            num = num * 10 + digit;
            digitCount++;
            i++;

            // 최대 10자리 (UINT_MAX는 10자리)
            if (digitCount > 10) {
                // 10자리를 초과하면 UINT_MAX를 초과한 것으로 간주하여 실패 처리
                return false;
            }
        }

        // 10자리인데 값이 UINT_MAX를 초과하는 경우 (예: 4294967296)를 처리하기 위해
        // 위 오버플로우 체크가 중요합니다.

        // 공백 추가 (첫 번째가 아닌 경우)
        if (!firstNumber) {
            if (outPos >= outSize - 1) {
                return false; // 버퍼 부족
            }
            output[outPos++] = ' ';
        }

        // 8진수 변환 (최대 11자리 필요)
        if (outPos + MAX_OCTAL_DIGITS >= outSize) {
            return false; // 버퍼 부족
        }

        // 8진수 변환 및 길이 업데이트
        size_t octalLen = uintToOctal((unsigned int)num, output + outPos);
        if (octalLen == 0) {
            return false; // 변환 실패
        }

        outPos += octalLen;
        firstNumber = false;
    }

    output[outPos] = '\0';
    return true;
}

// Octal to Decimal
bool octalToDec(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t outPos = 0;
    size_t i = 0;
    bool firstNumber = true;

    // 32비트 unsigned int는 10진수로 최대 10자리 필요
    const size_t MAX_DECIMAL_DIGITS = 10;

    while (input[i] != '\0') {
        // 숫자가 아닌 문자 스킵
        while (input[i] != '\0' && !isdigit((unsigned char)input[i])) {
            i++;
        }

        if (input[i] == '\0') break;

        // 8진수 파싱 및 10진수로 변환
        unsigned long num = 0;

        // 8진수 숫자의 시작 위치 기억
        size_t octalStart = i;

        while (isdigit((unsigned char)input[i])) {
            int digit = input[i] - '0';

            // 8진수 유효성 검사 (숫자가 0~7 범위인지 확인)
            if (digit >= 8) {
                // '8' 또는 '9'는 8진수 숫자가 아님
                return false;
            }

            // 오버플로우 체크 (num * 8 + digit <= UINT_MAX)
            if (num > UINT_MAX / 8 || (num == UINT_MAX / 8 && digit > UINT_MAX % 8)) {
                // 결과가 unsigned int 범위를 초과합니다.
                return false;
            }

            num = num * 8 + digit;
            i++;
        }

        // 실제로 파싱된 숫자가 없는 경우 (예: "   ")는 위에서 이미 처리됨
        // 하지만 만약 숫자를 처리하지 못하고 루프를 빠져나왔다면 문제가 발생할 수 있지만, 
        // 위 로직은 isdigit을 통과한 후 while(isdigit)에서 파싱하므로 octalStart와 i가 같다면 
        // while(isdigit)의 첫 번째 문자가 8 또는 9였다는 뜻이며, 이는 이미 false를 반환합니다.

        // 공백 추가 (첫 번째 숫자가 아닌 경우)
        if (!firstNumber) {
            if (outPos >= outSize - 1) {
                return false; // 버퍼 부족
            }
            output[outPos++] = ' ';
        }

        // 10진수 변환 (최대 10자리 필요)
        if (outPos + MAX_DECIMAL_DIGITS >= outSize) {
            return false; // 버퍼 부족
        }

        // 10진수 변환 및 길이 업데이트
        size_t decLen = uintToDec((unsigned int)num, output + outPos);
        if (decLen == 0) {
            return false; // 변환 실패
        }

        outPos += decLen;
        firstNumber = false;
    }

    output[outPos] = '\0';
    return true;
}

// Text to UTF-7
bool textToUtf7(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t inputLen = strlen(input);
    size_t outPos = 0;
    size_t i = 0;
    const char* direct_chars = " '(),./";

    while (i < inputLen) {
        unsigned char current_char = (unsigned char)input[i];

        // 1. Direct Character
        if (isalnum(current_char) || strchr(direct_chars, current_char)) {
            if (outPos >= outSize - 1) return false;
            output[outPos++] = (char)current_char;
            i++;
        }
        // 2. '+' 특수 처리
        else if (current_char == '+') {
            if (outPos + 2 >= outSize) return false;
            output[outPos++] = '+';
            output[outPos++] = '-';
            i++;
        }
        // 3. Encoded Characters
        else {
            // UTF-16 Big Endian으로 변환
            unsigned char block_bytes[6];  // 최대 3문자 = 6바이트
            size_t block_byte_idx = 0;

            while (i < inputLen &&
                !isalnum((unsigned char)input[i]) &&
                !strchr(direct_chars, (unsigned char)input[i]) &&
                input[i] != '+' &&
                block_byte_idx < 6) {

                unsigned char c = (unsigned char)input[i];

                // UTF-16 Big Endian 변환
                block_bytes[block_byte_idx++] = 0x00;  // 상위 바이트 (ASCII는 0)
                block_bytes[block_byte_idx++] = c;     // 하위 바이트

                i++;
            }

            // '+' 마커
            if (outPos >= outSize - 1) return false;
            output[outPos++] = '+';

            // Base64 인코딩
            size_t b64_len = encode_base64_block(block_bytes, block_byte_idx,
                output + outPos, outSize - outPos);
            if (b64_len == 0) return false;
            outPos += b64_len;

            // '-' 마커
            if (outPos >= outSize - 1) return false;
            output[outPos++] = '-';
        }
    }

    output[outPos] = '\0';
    return true;
}

// UTF-7 to Text
bool utf7ToText(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t inputLen = strlen(input);
    size_t outPos = 0;
    size_t i = 0;

    // 디코딩된 UTF-16 바이트를 저장할 임시 버퍼 (충분히 크게 설정)
    unsigned char decoded_bytes[512];

    // Base64 블록 저장을 위한 임시 버퍼를 정적 크기로 선언
#define MAX_B64_BLOCK_SIZE 512
    char temp_b64_block[MAX_B64_BLOCK_SIZE];

    while (i < inputLen) {
        // ... (생략) ...

        // 1. Base64 인코딩 블록 시작 감지
        if (input[i] == '+') {
            i++; // '+' 건너뛰기

            // 1-1. "+-" 패턴 처리 (인코딩된 '+' 문자)
            if (input[i] == '-') {
                // ... (생략) ...
                output[outPos++] = '+';
                i++;
                continue;
            }

            // 1-2. Base64 블록 파싱 (다음 '-'가 나올 때까지)
            size_t block_start = i;
            size_t block_len = 0;

            // Base64 블록 추출
            while (i < inputLen && input[i] != '-') {
                if (block_len >= MAX_B64_BLOCK_SIZE - 1) {
                    return false; // 임시 버퍼 오버플로우 방지
                }
                i++;
                block_len++;
            }

            // 임시 버퍼에 Base64 문자열 복사
            // VLA 대신 정적 버퍼를 사용하고 block_len만큼 복사
            memcpy(temp_b64_block, input + block_start, block_len);
            temp_b64_block[block_len] = '\0'; // 널 문자 추가

            // Base64 디코딩 (결과는 UTF-16 바이트 스트림)
            size_t decoded_len = decode_base64_block(temp_b64_block, block_len, decoded_bytes, sizeof(decoded_bytes));

            if (decoded_len == 0 && block_len > 0) {
                return false; // 디코딩 실패
            }

            // UTF-16 바이트에서 ASCII 문자 추출 (0x00 XX 형태에서 XX만 추출)
            for (size_t j = 0; j < decoded_len; j++) {
                // 홀수 인덱스 (하위 바이트)만 유효한 ASCII 문자
                if (j % 2 == 1) {
                    if (outPos >= outSize - 1) return false;
                    output[outPos++] = (char)decoded_bytes[j];
                }
            }

            // 디코딩 후 '-'가 있다면 건너뛰기
            if (i < inputLen && input[i] == '-') {
                i++;
            }
        }
        // 2. 직접 표현 문자 처리
        else {
            if (outPos >= outSize - 1) return false;
            output[outPos++] = input[i];
            i++;
        }
    }

    output[outPos] = '\0';
    return true;
}

// Hex to UCS-2
bool hexToUcs2(const char* input, char* output, size_t outSize)
{
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t outPos = 0;
    size_t i = 0;

    static const char hexDigits[] = "0123456789ABCDEF";

    while (input[i] != '\0') {
        // 공백 및 구분자 스킵
        while (input[i] == ' ' || input[i] == ':' || input[i] == '-' ||
            input[i] == '\t' || input[i] == '\n' || input[i] == '\r') {
            if (input[i] == '\0') break;
            i++;
        }
        if (input[i] == '\0') break;

        // 첫 번째 hex 문자 (상위 nibble)
        int high = hexCharToValue(input[i]);
        if (high == -1) {
            return false;
        }

        // 두 번째 hex 문자 (하위 nibble)
        if (input[i + 1] == '\0') {
            return false; // 홀수 개의 hex 문자
        }
        int low = hexCharToValue(input[i + 1]);
        if (low == -1) {
            return false;
        }

        unsigned char byte = (unsigned char)((high << 4) | low);

        // UCS-2 Little Endian: 낮은 바이트 먼저, 그 다음 00
        // 출력 버퍼에 4자리 Hex로 기록: byte(Low) + 00(High)

        if (outPos + 4 >= outSize) {
            return false; // 버퍼 부족
        }

        // 낮은 바이트 (Little Endian)
        output[outPos++] = hexDigits[byte >> 4];
        output[outPos++] = hexDigits[byte & 0x0F];

        // 높은 바이트 (항상 00 for BMP)
        output[outPos++] = '0';
        output[outPos++] = '0';

        i += 2;
    }

    output[outPos] = '\0';
    return true;
}

// UCS-2 to Hex
bool ucs2ToHex(const char* input, char* output, size_t outSize)
{
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t outPos = 0;
    size_t i = 0;

    static const char hexDigits[] = "0123456789ABCDEF";

    while (input[i] != '\0') {
        // 공백 및 구분자 스킵
        while (input[i] == ' ' || input[i] == ':' || input[i] == '-' ||
            input[i] == '\t' || input[i] == '\n' || input[i] == '\r') {
            if (input[i] == '\0') break;
            i++;
        }
        if (input[i] == '\0') break;

        // UCS-2는 4자리 Hex 단위여야 함 (2바이트: Low + High)
        // 4자리 읽기

        int nibbles[4];
        for (int j = 0; j < 4; j++) {
            if (input[i] == '\0') {
                return false; // 4자리 미만으로 끝남
            }

            // 구분자 다시 스킵 (만약 중간에 있을 수 있음)
            while (input[i] == ' ' || input[i] == ':' || input[i] == '-' ||
                input[i] == '\t' || input[i] == '\n' || input[i] == '\r') {
                i++;
                if (input[i] == '\0') return false;
            }

            nibbles[j] = hexCharToValue(input[i]);
            if (nibbles[j] == -1) {
                return false; // 유효하지 않은 hex 문자
            }
            i++;
        }

        // Little Endian: 낮은 바이트가 먼저 옴
        // nibbles[0..1]: Low byte, nibbles[2..3]: High byte
        unsigned char lowByte = (unsigned char)((nibbles[0] << 4) | nibbles[1]);
        unsigned char highByte = (unsigned char)((nibbles[2] << 4) | nibbles[3]);

        // BMP 범위에서는 highByte가 00이어야 정상 (U+0000 ~ U+FFFF)
        // 프로그램의 기존 용도상 highByte가 00이 아닌 경우도 허용할지 결정
        // 여기서는 경고 없이 lowByte만 사용 (대부분의 용례가 ASCII이기 때문)
        // 필요시 아래 주석 해제하여 엄격히 검사 가능
        // if (highByte != 0) return false;  // 서러게이트나 비BMP 문자 거부

        // 출력: lowByte만 Hex로 변환 (2자리)
        if (outPos + 2 >= outSize) {
            return false; // 버퍼 부족
        }

        output[outPos++] = hexDigits[lowByte >> 4];
        output[outPos++] = hexDigits[lowByte & 0x0F];
    }

    output[outPos] = '\0';
    return true;
}


// Text to Binary
bool textToBinary(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t inputLen = strlen(input);

    if (inputLen == 0) {
        output[0] = '\0';
        return true;
    }

    size_t required = inputLen * 9;
    if (outSize < required) {
        return false;
    }

    size_t outPos = 0;

    for (size_t i = 0; i < inputLen; i++) {
        unsigned char byte = (unsigned char)input[i];

        // 비트마스크로 직접 변환
        output[outPos++] = (byte & 0x80) ? '1' : '0';
        output[outPos++] = (byte & 0x40) ? '1' : '0';
        output[outPos++] = (byte & 0x20) ? '1' : '0';
        output[outPos++] = (byte & 0x10) ? '1' : '0';
        output[outPos++] = (byte & 0x08) ? '1' : '0';
        output[outPos++] = (byte & 0x04) ? '1' : '0';
        output[outPos++] = (byte & 0x02) ? '1' : '0';
        output[outPos++] = (byte & 0x01) ? '1' : '0';

        // 마지막이 아니면 공백
        if (i < inputLen - 1) {
            output[outPos++] = ' ';
        }
    }

    output[outPos] = '\0';
    return true;
}

// Binary to Text
bool binaryToText(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    char temp[9];
    int tempIdx = 0;
    size_t outPos = 0;

    for (size_t i = 0; input[i] != '\0'; i++) {
        if (input[i] == '0' || input[i] == '1') {
            if (tempIdx >= 8) {
                // 8비트 초과
                return false;
            }
            temp[tempIdx++] = input[i];

            if (tempIdx == 8) {
                temp[8] = '\0';

                unsigned char byte;
                if (!parseBinaryByte(temp, &byte)) {
                    return false;
                }

                if (outPos >= outSize - 1) {
                    return false;
                }

                output[outPos++] = (char)byte;
                tempIdx = 0;
            }
        }
        else if (input[i] == ' ' || input[i] == '\t' ||
            input[i] == '\n' || input[i] == '\r') {
            // 공백 허용
            continue;
        }
        else {
            // 잘못된 문자
            return false;
        }
    }

    // 남은 비트 체크
    if (tempIdx != 0) {
        // 불완전한 바이트
        return false;
    }

    output[outPos] = '\0';
    return true;
}

// Escape
bool escapeString(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t inputLen = strlen(input);
    size_t required = inputLen * 6 + 1;  // 각 문자당 \uXXXX (6바이트)

    if (outSize < required) {
        return false;
    }

    size_t outPos = 0;

    for (size_t i = 0; i < inputLen; i++) {
        unsigned char c = (unsigned char)input[i];
        snprintf(output + outPos, outSize - outPos, "\\u%04x", c);
        outPos += 6;
    }

    output[outPos] = '\0';
    return true;
}

// Unescape
bool unescapeString(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t outPos = 0;

    for (size_t i = 0; input[i] != '\0'; i++) {
        if (input[i] == '\\') {
            if (input[i + 1] == '\0') {
                return false;
            }

            i++;

            switch (input[i]) {
            case 'n':
                if (outPos >= outSize - 1) return false;
                output[outPos++] = '\n';
                break;
            case 'r':
                if (outPos >= outSize - 1) return false;
                output[outPos++] = '\r';
                break;
            case 't':
                if (outPos >= outSize - 1) return false;
                output[outPos++] = '\t';
                break;
            case '\\':
                if (outPos >= outSize - 1) return false;
                output[outPos++] = '\\';
                break;
            case '\"':
                if (outPos >= outSize - 1) return false;
                output[outPos++] = '\"';
                break;
            case '\'':
                if (outPos >= outSize - 1) return false;
                output[outPos++] = '\'';
                break;
            case 'b':
                if (outPos >= outSize - 1) return false;
                output[outPos++] = '\b';
                break;
            case 'f':
                if (outPos >= outSize - 1) return false;
                output[outPos++] = '\f';
                break;
            case '0':
                if (outPos >= outSize - 1) return false;
                output[outPos++] = '\0';
                break;

            case 'u': {
                // \uXXXX
                if (input[i + 1] == '\0' || input[i + 2] == '\0' ||
                    input[i + 3] == '\0' || input[i + 4] == '\0') {
                    return false;
                }

                int d1 = hexCharToValue(input[i + 1]);
                int d2 = hexCharToValue(input[i + 2]);
                int d3 = hexCharToValue(input[i + 3]);
                int d4 = hexCharToValue(input[i + 4]);

                if (d1 < 0 || d2 < 0 || d3 < 0 || d4 < 0) {
                    return false;
                }

                unsigned int codepoint = (d1 << 12) | (d2 << 8) | (d3 << 4) | d4;

                // UTF-8로 인코딩 (최대 4바이트)
                char utf8[4];
                size_t utf8Len = encodeUTF8(codepoint, utf8);

                if (utf8Len == 0 || outPos + utf8Len > outSize - 1) {
                    return false;
                }

                for (size_t j = 0; j < utf8Len; j++) {
                    output[outPos++] = utf8[j];
                }

                i += 4;
                break;
            }

            case 'x': {
                // \xXX
                if (input[i + 1] == '\0' || input[i + 2] == '\0') {
                    return false;
                }

                int h1 = hexCharToValue(input[i + 1]);
                int h2 = hexCharToValue(input[i + 2]);

                if (h1 < 0 || h2 < 0) {
                    return false;
                }

                if (outPos >= outSize - 1) return false;
                output[outPos++] = (char)((h1 << 4) | h2);
                i += 2;
                break;
            }

            default:
                return false;
            }
        }
        else {
            if (outPos >= outSize - 1) return false;
            output[outPos++] = input[i];
        }
    }

    output[outPos] = '\0';
    return true;
}

// HTML Encode
bool encodeHTML(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t outPos = 0;

    for (size_t i = 0; input[i] != '\0'; i++) {
        const char* entity = NULL;
        size_t entityLen = 0;
        unsigned char c = (unsigned char)input[i];

        // HTML 엔티티 매핑
        switch (c) {
        case '&':  entity = "&";   entityLen = 5; break;
        case '<':  entity = "&lt;";    entityLen = 4; break;
        case '>':  entity = "&gt;";    entityLen = 4; break;
        case '"':  entity = "&quot;";  entityLen = 6; break;
        case '\'': entity = "&#39;";   entityLen = 5; break;
            // 추가 엔티티
        case '\n': entity = "&#10;";   entityLen = 5; break;
        case '\r': entity = "&#13;";   entityLen = 5; break;
        default:
            // 출력 불가능한 문자는 숫자 엔티티로
            if (c < 32 || c == 127) {
                // &#XX; 형식 (최대 6바이트)
                if (outPos + 6 >= outSize) return false;
                int written = snprintf(output + outPos, outSize - outPos,
                    "&#%u;", c);
                if (written < 0 || (size_t)written >= outSize - outPos) {
                    return false;
                }
                outPos += written;
                continue;
            }
            // 일반 문자
            if (outPos >= outSize - 1) return false;
            output[outPos++] = c;
            continue;
        }

        // 엔티티 쓰기
        if (outPos + entityLen >= outSize) {
            return false;
        }

        memcpy(output + outPos, entity, entityLen);
        outPos += entityLen;
    }

    output[outPos] = '\0';
    return true;
}

// HTML Decode
bool decodeHTML(const char* input, char* output, size_t outSize) {
    if (!input || !output || outSize == 0) {
        return false;
    }

    size_t outPos = 0;
    size_t inputLen = strlen(input);

    for (size_t i = 0; i < inputLen; i++) {
        if (outPos >= outSize - 1) {
            return false;
        }

        if (input[i] == '&') {
            size_t remaining = inputLen - i;
            bool matched = false;

            // 각 엔티티의 최소 길이 체크 후 비교
            if (remaining >= 5 && memcmp(input + i, "&", 5) == 0) {
                output[outPos++] = '&';
                i += 4;
                matched = true;
            }
            else if (remaining >= 4 && memcmp(input + i, "&lt;", 4) == 0) {
                output[outPos++] = '<';
                i += 3;
                matched = true;
            }
            else if (remaining >= 4 && memcmp(input + i, "&gt;", 4) == 0) {
                output[outPos++] = '>';
                i += 3;
                matched = true;
            }
            else if (remaining >= 6 && memcmp(input + i, "&quot;", 6) == 0) {
                output[outPos++] = '"';
                i += 5;
                matched = true;
            }
            else if (remaining >= 5 && memcmp(input + i, "&#39;", 5) == 0) {
                output[outPos++] = '\'';
                i += 4;
                matched = true;
            }

            if (!matched) {
                output[outPos++] = input[i];
            }
        }
        else {
            output[outPos++] = input[i];
        }
    }

    output[outPos] = '\0';
    return true;
}

// HexToBase64
bool hexToBase64(const char* input, char* output, size_t outSize)
{
    if (!input || !output || outSize == 0) {
        return false;
    }

    unsigned char binary[MAX_BUFFER];
    size_t binPos = 0;
    size_t i = 0;

    // 1. Hex → binary
    while (input[i] != '\0' && binPos < MAX_BUFFER) {
        // 구분자 스킵
        while (input[i] == ' ' || input[i] == ':' || input[i] == '-' ||
            input[i] == '\t' || input[i] == '\n' || input[i] == '\r') {
            i++;
        }
        if (input[i] == '\0') break;

        int high = hexCharToValue(input[i++]);
        if (high == -1) return false;

        if (input[i] == '\0') return false;  // 홀수 hex
        int low = hexCharToValue(input[i++]);
        if (low == -1) return false;

        binary[binPos++] = (unsigned char)((high << 4) | low);
    }

    if (binPos == 0) {
        output[0] = '\0';
        return true;
    }

    static const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

    size_t outPos = 0;
    size_t pos = 0;

    // 2. binary → Base64 (3바이트씩 처리)
    while (pos < binPos) {
        size_t remaining = binPos - pos;  // 남은 바이트 수 (1~3)

        unsigned char b1 = binary[pos++];
        unsigned char b2 = (remaining >= 2) ? binary[pos++] : 0;
        unsigned char b3 = (remaining >= 3) ? binary[pos++] : 0;

        unsigned int block = (b1 << 16) | (b2 << 8) | b3;

        if (outPos + 4 >= outSize) return false;

        output[outPos++] = base64_chars[(block >> 18) & 0x3F];
        output[outPos++] = base64_chars[(block >> 12) & 0x3F];
        output[outPos++] = (remaining >= 2) ? base64_chars[(block >> 6) & 0x3F] : '=';
        output[outPos++] = (remaining >= 3) ? base64_chars[block & 0x3F] : '=';
    }

    output[outPos] = '\0';
    return true;
}

// Base64ToHex
bool base64ToHex(const char* input, char* output, size_t outSize)
{
    if (!input || !output || outSize == 0) {
        return false;
    }

    // Base64 디코딩을 위한 임시 바이너리 버퍼
    unsigned char binary[MAX_BUFFER];
    size_t binPos = 0;

    size_t i = 0;
    while (input[i] != '\0') {
        // 4개 문자씩 하나의 블록 처리
        char block[4];
        int val[4];
        int valCount = 0;

        // Base64 문자 4개 읽기 (패딩 = 허용)
        for (int j = 0; j < 4; j++) {
            if (input[i] == '\0') {
                return false; // 블록 중간에 끝나면 오류
            }

            char c = input[i++];
            if (c == '=') {
                block[j] = '=';
                val[j] = -1; // 패딩 표시
            }
            else {
                int v = base64_char_to_value(c);
                if (v == -1) {
                    return false; // 유효하지 않은 Base64 문자
                }
                block[j] = c;
                val[j] = v;
                valCount++;
            }
        }

        // 패딩 처리: 마지막 블록에서 = 개수 확인
        if (block[3] == '=') {
            if (block[2] != '=') return false; // 잘못된 패딩
            if (valCount != 2) return false;
        }
        else if (block[2] == '=') {
            if (valCount != 3) return false;
        }
        else {
            if (valCount != 4) return false;
        }

        // 24비트 블록 구성
        unsigned int bits = 0;
        if (valCount >= 1) bits |= (val[0] << 18);
        if (valCount >= 2) bits |= (val[1] << 12);
        if (valCount >= 3) bits |= (val[2] << 6);
        if (valCount >= 4) bits |= (val[3]);

        // 출력 바이트 수 계산
        int byteCount = (valCount * 6) / 8; // 2 -> 1바이트, 3 -> 2바이트, 4 -> 3바이트

        // 바이너리 버퍼에 저장
        if (byteCount >= 1) {
            if (binPos >= MAX_BUFFER) return false;
            binary[binPos++] = (unsigned char)((bits >> 16) & 0xFF);
        }
        if (byteCount >= 2) {
            if (binPos >= MAX_BUFFER) return false;
            binary[binPos++] = (unsigned char)((bits >> 8) & 0xFF);
        }
        if (byteCount >= 3) {
            if (binPos >= MAX_BUFFER) return false;
            binary[binPos++] = (unsigned char)(bits & 0xFF);
        }
    }

    // 바이너리를 Hex 문자열로 변환
    size_t outPos = 0;
    static const char hexDigits[] = "0123456789ABCDEF";

    for (size_t k = 0; k < binPos; k++) {
        if (outPos + 2 >= outSize) {
            return false; // 출력 버퍼 부족
        }
        unsigned char byte = binary[k];
        output[outPos++] = hexDigits[byte >> 4];
        output[outPos++] = hexDigits[byte & 0x0F];
    }

    output[outPos] = '\0';
    return true;
}

// Clipboard helper
bool copyToClipboard(const char* text) {
    if (!text) {
        return false;
    }

    size_t len = strlen(text);
    if (len == 0) {
        return false;
    }

    // ANSI 메모리 준비
    HGLOBAL hMemAnsi = GlobalAlloc(GMEM_MOVEABLE, len + 1);
    if (!hMemAnsi) {
        return false;
    }

    char* pMemAnsi = (char*)GlobalLock(hMemAnsi);
    if (!pMemAnsi) {
        GlobalFree(hMemAnsi);
        return false;
    }

    memcpy(pMemAnsi, text, len + 1);
    GlobalUnlock(hMemAnsi);

    // 유니코드 메모리 준비
    int wideLen = MultiByteToWideChar(CP_UTF8, 0, text, -1, NULL, 0);
    HGLOBAL hMemUnicode = NULL;

    if (wideLen > 0) {
        hMemUnicode = GlobalAlloc(GMEM_MOVEABLE, wideLen * sizeof(wchar_t));
        if (hMemUnicode) {
            wchar_t* pMemUnicode = (wchar_t*)GlobalLock(hMemUnicode);
            if (pMemUnicode) {
                MultiByteToWideChar(CP_UTF8, 0, text, -1, pMemUnicode, wideLen);
                GlobalUnlock(hMemUnicode);
            }
            else {
                GlobalFree(hMemUnicode);
                hMemUnicode = NULL;
            }
        }
    }

    // 클립보드 열기 (재시도)
    bool success = false;
    for (int retry = 0; retry < 3; retry++) {
        if (OpenClipboard(NULL)) {
            EmptyClipboard();

            // ANSI 설정
            bool ansiOk = (SetClipboardData(CF_TEXT, hMemAnsi) != NULL);

            // 유니코드 설정
            bool unicodeOk = true;
            if (hMemUnicode) {
                unicodeOk = (SetClipboardData(CF_UNICODETEXT, hMemUnicode) != NULL);
            }

            CloseClipboard();

            // 둘 중 하나라도 성공하면 OK
            if (ansiOk || unicodeOk) {
                success = true;

                // 성공한 핸들은 클립보드가 소유
                // 실패한 핸들만 해제
                if (!ansiOk) {
                    GlobalFree(hMemAnsi);
                }
                if (hMemUnicode && !unicodeOk) {
                    GlobalFree(hMemUnicode);
                }

                break;
            }
        }

        Sleep(10);
    }

    // 완전 실패 시 메모리 해제
    if (!success) {
        GlobalFree(hMemAnsi);
        if (hMemUnicode) {
            GlobalFree(hMemUnicode);
        }
        return false;
    }

    return true;
}

 

1. Base64 테이블

// Base64 table
const char* base64_table =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  • Base64는 64개 문자만 사용해서 인코딩하는 방식입니다.
  • 이 문자열의 인덱스(0~63)가 Base64 코드입니다.
  • 인덱스 0 → 'A', 인덱스 1 → 'B' ... 인덱스 63 → '/'
    >> 예: 숫자 0을 인덱스로 쓰면 base64_table[0] = 'A' 를 얻습니다.

2. 변환 함수들 (encodeBase64, decodeBase64, textToHex 등)

 - Base64 인코딩

bool encodeBase64(const char* input, char* output, size_t outSize) {
  • 함수선언
  • 반환값: bool (성공/실패 여부)
  • const char* input: 인코딩할 입력 문자열 (읽기만 함)
  • char* output: 인코딩된 결과를 쓸 출력 버퍼
  • size_t outSize: 출력 버퍼의 최대 크기 (오버플로우 방지)
    if (!input || !output || outSize == 0) {
        return false;
    }
  • 입력검증
  • !input : 입력 포인터가 NULL이면 실패
  • !output : 출력 포인터가 NULL이면 실패
  • outSize == 0 : 출력 버퍼의 크기가 0이면 실패
  • 예)
    >> encodeBase64(NULL, buf, 100);      //  return false (input NULL)
    >> encodeBase64("text", NULL, 100);   //  return false (output NULL)
    >> encodeBase64("text", buf, 0);      //  return false (outSize 0)
    size_t len = strlen(input);
  • 입력 길이 계산
  • 입력 문자열의 길이를 계산 (널 문자 제외)
  • 예: "ABC" → len = 3
    size_t required = ((len + 2) / 3) * 4 + 1;  // +1 for null terminator
  • 필요한 버퍼 크기 계산
  • 이 식이 Base64의 핵심입니다.
  • 계산 공식 분석
  • Base64의 규칙
    >> 입력: 3바이트 단위로 읽음
    >> 출력: 4문자로 변환

 

  > 따라서

입력 3바이트   →  출력 4문자
입력 1바이트   →  출력 2문자 (패딩 "=" 2개)
입력 2바이트   →  출력 3문자 (패딩 "=" 1개)

 

  > 구체적 예시

  • 예1: 입력 "ABC" (3바이트)
len = 3
(len + 2) / 3 = (3 + 2) / 3 = 5 / 3 = 1  (정수 나눗셈)
required = 1 * 4 + 1 = 5

출력: "QUJD\0"  (4글자 + 널 문자)

 

  > 구체적 예시

  • 예2: 입력 "A" (1바이트)
len = 1
(len + 2) / 3 = (1 + 2) / 3 = 3 / 3 = 1
required = 1 * 4 + 1 = 5

출력: "QQ==\0"  (2글자 + 패딩 2개 + 널 문자)

 

  > 구체적 예시

  • 예3: 입력 "AB" (2바이트)
len = 2
(len + 2) / 3 = (2 + 2) / 3 = 4 / 3 = 1  (정수 나눗셈)
required = 1 * 4 + 1 = 5

출력: "QUI=\0"  (3글자 + 패딩 1개 + 널 문자)

 

  > 구체적 예시

  • 예4: 입력 "ABCDEF" (6바이트)
len = 6
(len + 2) / 3 = (6 + 2) / 3 = 8 / 3 = 2  (정수 나눗셈)
required = 2 * 4 + 1 = 9

출력: "QUJDREVG\0"  (8글자 + 널 문자)

 

  • 왜 (len + 2) / 3 일까?
    >> 올림(ceiling) 계산
len = 1  →  (1 + 2) / 3 = 1  (올림)
len = 2  →  (2 + 2) / 3 = 1  (올림)
len = 3  →  (3 + 2) / 3 = 1
len = 4  →  (4 + 2) / 3 = 2  (올림)
len = 5  →  (5 + 2) / 3 = 2  (올림)
len = 6  →  (6 + 2) / 3 = 2
  • 정수 나눗셈으로 자동으로 올림 처리됩니다.
    if (outSize < required) {
        return false;
    }
  • 버퍼 크기 검증
  • 사용자가 제공한 버퍼 크기(outSize)가 필요한 크기(required)보다 작으면 실패
  • 오버플로우 방지

  > 예시

char buf[5];  // 5바이트만 할당
encodeBase64("ABC", buf, 5);    //  OK (required = 5)
encodeBase64("ABCD", buf, 5);   //  FAIL (required = 9)

 

    size_t outPos = 0;
  • 출력 위치 초기화
  • 출력 버퍼에 현재 쓴 위치를 기록
  • 처음에는 **맨 앞(0)**에서 시작
    for (size_t i = 0; i < len; i += 3) {
  • 3바이트씩 읽는 루프
  • i += 3 : 3바이트씩 증가
  • Base64는 항상 3바이트 단위로 처리

 > 예시 (입력 "ABCDEFGH" = 8바이트):

반복 1: i = 0  → input[0], input[1], input[2] 읽음 ("ABC")
반복 2: i = 3  → input[3], input[4], input[5] 읽음 ("DEF")
반복 3: i = 6  → input[6], input[7] 읽음 ("GH" + 패딩)
루프 종료: i = 9 > 8

 

        unsigned char b1 = (unsigned char)input[i];
        unsigned char b2 = (i + 1 < len) ? (unsigned char)input[i + 1] : 0;
        unsigned char b3 = (i + 2 < len) ? (unsigned char)input[i + 2] : 0;
  • 바이트 읽기
  • b1 현재 바이트 (항상 존재)
  • b2 다음 바이트 (없으면 0으로 채움)
  • b3 그 다음 바이트 (없으면 0으로 채움)

 

 > 예시 (입력 "A"):

b1 = 'A' (65)
b2 = 0 (없음)
b3 = 0 (없음)

 

        output[outPos++] = base64_table[(b1 >> 2) & 0x3F];
  • 첫 번째 Base64 문자

 > 비트 분석

b1 = 'A' = 65 = 01000001 (이진)
b1 >> 2  =     00010000 (2비트 오른쪽)
                      = 16 (10진)
  • b1 >> 2 : b1을 오른쪽으로 2비트 시프트 (상위 6비트 추출)
0x3F = 00111111 (6개 비트가 1)
16 & 0x3F = 16
  • & 0x3F : 하위 6비트만 남기기 (0~63 범위)
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
 0123456789...                              16 = 'Q'
  • base64_table[16]
  • 결과: output[outPos++] = 'Q'
        output[outPos++] = base64_table[(((b1 & 0x03) << 4) | ((b2 >> 4) & 0x0F)) & 0x3F];
  • 두 번째 Base64 문자

 > 비트 조합

b1 = 01000001
     ......01  (하위 2비트) = 1
  • b1 & 0x03 : b1의 하위 2비트
1 << 4 = 00010000 = 16
  • << 4 : 왼쪽으로 4비트 시프트 (앞으로 당김) 
b2 = 00000000
     0000....  (상위 4비트) = 0
  • b2 >> 4 : b2의 상위 4비트
0 & 0x0F = 0
  • & 0x0F : 하위 4비트만 남기기
00010000  (b1 하위 2비트, 시프트)
| 0000   (b2 상위 4비트)
= 00010000 = 16
  • | (또는): 6비트 조합
16 & 0x3F = 16
  • & 0x3F : 최종 6비트 범위 확인
    결과: base64_table[16] = 'Q' 
        output[outPos++] = (i + 1 < len)
            ? base64_table[(((b2 & 0x0F) << 2) | ((b3 >> 6) & 0x03)) & 0x3F]
            : '=';
  • 세 번째 Base64 문자 (패딩 처리)
  • 조건: i + 1 < len
  • 실제 2번째 바이트가 존재하면? → 계산
  • 없으면 → 패딩 문자 '='

 > 비트 조합 (2번째 바이트가 있을 때)

b2 = 01000010
     ....0010  (하위 4비트) = 2
  • b2 & 0x0F : b2의 하위 4비트 
2 << 2 = 00001000 = 8
  • << 2 : 왼쪽으로 2비트 시프트
b3 = 01000011
     01......  (상위 2비트) = 1
  • b3 >> 6 : b3의 상위 2비트
1 & 0x03 = 1
  • & 0x03 : 하위 2비트만
00001000  (b2 하위 4비트, 시프트)
| 0000001 (b3 상위 2비트)
= 00001001 = 9
  • | (또는): 6비트 조합
  • 결과: base64_table[9] = 'J' 또는 '=' (없을 때)
        output[outPos++] = (i + 2 < len)
            ? base64_table[b3 & 0x3F]
            : '=';
  • 네 번째 Base64 문자 (패딩 처리)
  • 조건: i + 2 < len
  • 실제 3번째 바이트가 존재하면? → 계산
  • 없으면 → 패딩 문자 '='
b3 = 01000011
     00000011  (하위 6비트) = 3
  • b3 & 0x3F : b3의 하위 6비트
  • 결과: base64_table[3] = 'D' 또는 '=' (없을 때)
    output[outPos] = '\0';
  • 널 문자 추가
  • 출력 버퍼의 끝에 널 문자('\0') 추가
  • C 문자열의 종료 표시
  • outPos는 이미 마지막 문자 다음 위치

 > 예시

output = ['Q', 'U', 'J', 'D', '\0']
          0     1     2     3     4
                            outPos = 4

 

    return true;
}
  • 성공 반환
  • 모든 검증 통과, 인코딩 완료
  • true 반환 (성공)

 

 - Base64 디코딩

bool decodeBase64(const char* input, char* output, size_t outSize) {
  • 함수 선언
  • 반환값: bool (성공/실패 여부)
  • const char* input: 디코딩할 Base64 문자열 (읽기만 함)
  • char* output: 디코딩된 결과를 쓸 출력 버퍼
  • size_t outSize: 출력 버퍼의 최대 크기
    static const int8_t table[256] = {
        -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
// ... 중략 ...
        -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
        // ... 중략 ...
    };
인덱스 ASCII 문자 테이블 값 의미
43 + 62 Base64 문자 +는 값 62
47 / 63 Base64 문자 /는 값 63
48~57 0~9 52~61 Base64 문자 0~9는 값 52~61
65~90 A~Z 0~25 Base64 문자 A~Z는 값 0~25
97~122 a~z 26~51 Base64 문자 a~z는 값 26~51
그 외 - -1 유효하지 않은 문자 

   
 > 구체적 예시

Base64 테이블:
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

문자 'A' (ASCII 65)  → table[65] = 0
문자 'B' (ASCII 66)  → table[66] = 1
문자 'Z' (ASCII 90)  → table[90] = 25
문자 'a' (ASCII 97)  → table[97] = 26
문자 'z' (ASCII 122) → table[122] = 51
문자 '0' (ASCII 48)  → table[48] = 52
문자 '9' (ASCII 57)  → table[57] = 61
문자 '+' (ASCII 43)  → table[43] = 62
문자 '/' (ASCII 47)  → table[47] = 63
문자 '@' (ASCII 64)  → table[64] = -1  (유효하지 않음)

 

 > 왜 int8_t를 사용할까?

static const int8_t table[256] = {
    -1, -1, -1, ...  // -1은 "유효하지 않은 문자" 표시
    0, 1, 2, ...     // 0~63은 Base64 값
};
  • -1 : 유효하지 않은 문자 표시 (감지 가능)
  • 0~63 : Base64 값 (6비트)
  • int8_t : -128 ~ 127 범위 (충분함)

 > 만약 uint8_t (0~255)였다면

static const uint8_t table[256] = {
    255, 255, ...  // -1 대신 255 사용 (헷갈림)
};

 

    if (!input || !output || outSize == 0) {
        return false;
    }
  • 입력 검증
  • !input 입력 포인터가 NULL이면 실패
  • !output 출력 포인터가 NULL이면 실패
  • outSize == 0 출력 버퍼의 크기가 0이면 실패

 > 예시

decodeBase64(NULL, buf, 100);       //  return false
decodeBase64("QQ==", NULL, 100);    //  return false
decodeBase64("QQ==", buf, 0);       //  return false
decodeBase64("QQ==", buf, 100);     //  OK

 

    size_t len = strlen(input);
  • 입력 길이 계산
  • 입력 Base64 문자열의 길이 (널 문자 제외)
  • 예: "QQ==" → len = 4
    size_t outPos = 0;
  • 변수 초기화
  • 출력 버퍼의 현재 쓴 위치 (처음에는 0)
    unsigned int val = 0;
  • 비트를 임시로 저장하는 변수
  • 6비트씩 쌓아서 8비트(1바이트)가 되면 출력
    int bits = -8;
  • 현재 val에 몇 비트의 유효 데이터가 있는지 기록
  • -8 : "아직 데이터가 없음"을 의미
  • 값이 증가: -8 → -2 → 0 → 6 → 8 → ...
    for (size_t i = 0; i < len; i++) {
  • Base64 문자 하나씩 처리 루프
  • 입력 Base64 문자를 하나씩 읽음 (인코딩과 달리 1개씩)
        unsigned char c = (unsigned char)input[i];
  • 현재 문자 읽기
  • 현재 위치의 문자를 읽음
  • 예: input[0] → 'Q' (ASCII 81)
        if (c == ' ' || c == '\n' || c == '\r' || c == '\t') {
            continue;
        }
  • 공백 문자 스킵
  • 공백 문자는 무시하고 다음 루프로
  • Base64는 공백으로 포맷팅되는 경우가 많음

 > 예시

입력: "QQ== QU== "
처리:
  'Q' → 처리
  'Q' → 처리
  '=' → 처리
  '=' → 처리
  ' ' → 스킵!
  'Q' → 처리
  'U' → 처리
  '=' → 처리
  '=' → 처리
  ' ' → 스킵!

 

        if (c == '=') {
            break;
        }
  • 패딩 문자 처리
  • 패딩 문자 '='를 만나면 디코딩 종료
  • 더 이상의 데이터가 없다는 뜻

 > 예시

입력: "QQ=="
처리:
  'Q' → 처리
  'Q' → 처리
  '=' → break! (루프 종료)
  '=' → 처리 안 함

 

        int8_t decoded = table[c];
  • 테이블 조회
  • 현재 문자 c의 ASCII 값을 인덱스로 사용
  • 테이블에서 해당 Base64 값을 조회

 > 예시

c = 'Q' (ASCII 81)
decoded = table[81] = 16  // 'Q'는 Base64 값 16

c = 'U' (ASCII 85)
decoded = table[85] = 20  // 'U'는 Base64 값 20

c = '@' (ASCII 64)
decoded = table[64] = -1  // 유효하지 않음

 

        if (decoded == -1) {
            // 잘못된 Base64 문자
            return false;
        }
  • 잘못된 문자 검사
  • 테이블에서 -1이 나오면 유효하지 않은 문자
  • 디코딩 실패로 반환

 > 예시

input = "QQ@="  // '@'는 유효하지 않은 문자
// decodeBase64 실행 → return false (실패)

 

        val = (val << 6) | (unsigned int)decoded;
  • 비트 누적
  • 기존 val을 6비트 왼쪽으로 시프트하고 새 데이터 추가
  • Base64는 6비트 단위로 저장됨

 > 구체적 예시 (입력 "QQ==")

val = 0
val << 6 = 0
val | 16 = 16 = 00010000
val = 00010000 (6비트)
  • 1번째: 'Q' (값 16)
val = 00010000
val << 6 = 00010000 << 6 = 0001000000000000 (12비트)
val | 16 = 0001000000010000
val = 0001000000010000 (12비트)
  • 2번째: 'Q' (값 16)
        bits += 6;
  • 유효 비트 개수 증가
  • Base64 문자 하나가 6비트이므로 6 증가
  • -8 → -2 → 4 → 10 → ...
초기:        bits = -8
1번째 'Q':   bits = -8 + 6 = -2
2번째 'Q':   bits = -2 + 6 = 4
3번째 '=':   break (루프 종료)

최종: bits = 4
  • 비트 변화 추적
        if (bits >= 0) {
  • 8비트 이상 모았을 때 출력
  • 유효 비트가 8비트 이상 모였을 때만 실행
  • 1바이트(8비트)를 만들 수 있는 상태

 > 실행 조건

bits = -8 : 실행 안 함 (8비트 미만)
bits = -2 : 실행 안 함 (8비트 미만)
bits = 4  : 실행 안 함 (8비트 미만)
bits = 0  : 실행 함! (정확히 8비트)
bits = 6  : 실행 함! (14비트)

 

             if (outPos >= outSize - 1) {
                return false;
            }
  • 버퍼 오버플로우 체크
  • 출력할 데이터가 버퍼 크기를 초과하면 실패
  • -1 : 널 문자 공간 예약

 > 예시

char buf[5];
decodeBase64("QQ==", buf, 5);

outPos = 0, 1, 2, 3 (최대 4개)
4번째 바이트 쓸 때: outPos >= 4 ? → return false

 

            output[outPos++] = (char)((val >> bits) & 0xFF);
  • 8비트 추출 및 출력
  • val 에서 필요한 8비트를 추출하여 출력
  • val >> bits : 필요한 위치로 시프트
  • & 0xFF : 하위 8비트만 남기기

 > 구체적 예시

1번째 'Q' (16):
  val = 00010000 (6비트)
  bits = -2
  bits >= 0? NO → 스킵

2번째 'Q' (16):
  val = (00010000 << 6) | 16
      = 0001000000010000 (12비트)
  bits = 4
  bits >= 0? YES → 추출!
  
  val >> bits = 0001000000010000 >> 4 = 00010000
  & 0xFF = 00010000 = 16 = 'Q'
  
  output[0] = 'Q'
  outPos = 1
  bits = 4 - 8 = -4

3번째 '=':
  break (루프 종료)

최종 output = "Q"
  • 입력 "QQ==" 디코딩:
'A' (65) = 01000001 (8비트)

인코딩:
  01000001 → 010000 (6비트) + 01 (2비트)
  010000 = 16 = 'Q'
  010000 (2비트) + 패딩(4비트) = 01000000 = 32 = 'g'
  
결과: "QQ=="

디코딩:
  'Q' = 16 = 010000 (6비트)
  'Q' = 16 = 010000 (6비트)
  '=' = 패딩
  '=' = 패딩
  
  010000 (6비트) + 010000 (6비트) = 01000001 (8비트)
  01000001 = 65 = 'A'
  
결과: "A"
  • 실제 계산 (ASCII 표현)
            bits -= 8;
  • 비트 개수 감소
  • 8비트를 사용했으므로 8 감소
  • -4 → 0 → 6 → ...

 > 비트 변화 완전 추적

초기:           bits = -8, val = 0
1번째 'Q' (16): bits = -2, val = 0001000000000000
2번째 'Q' (16): bits = 4,  val = 0001000000010000
                → bits >= 0 ? YES
                → output[0] = 'Q'
                → bits = -4
3번째 '=':      break

최종: output = "Q", outPos = 1

 

    output[outPos] = '\0';
  • 널 문자 추가
  • 출력 버퍼의 끝에 널 문자('\0') 추가
  • C 문자열의 종료 표시

 > 예시

output = ['A', 'B', 'C', '\0']
          0     1     2     3
                    outPos = 3

 

    return true;
}
  • 성공 반환
  • 모든 검증 통과, 디코딩 완료
  • true 반환 (성공)

 

 - Text to Hex

bool textToHex(const char* input, char* output, size_t outSize) {
  • 함수 선언
  • 반환값: bool (성공/실패 여부)
  • const char* input: 16진수로 변환할 텍스트 문자열
  • char* output: 변환된 16진수 결과를 쓸 출력 버퍼
  • size_t outSize: 출력 버퍼의 최대 크기
    static const char hexDigits[] = "0123456789ABCDEF";
  • 16진수 숫자 테이블
  • 16진수 문자들을 저장하는 문자열
  • 인덱스 0~15에 해당 문자가 저장됨

 > 테이블 매핑

인덱스 문자 의미
0~9 '0'~'9' 10진수 0~9
10 'A' 16진수 A (10)
11 'B' 16진수 B (11)
12 'C' 16진수 C (12)
13 'D' 16진수 D (13)
14 'E' 16진수 E (14)
15 'F' 16진수 F (15)

 

 

> 구체적 예시

hexDigits[0]  = '0'
hexDigits[9]  = '9'
hexDigits[10] = 'A'
hexDigits[15] = 'F'

 

 > 사용

unsigned char byte = 165;  // 10100101
상위 4비트: (165 >> 4) & 0x0F = 10 → hexDigits[10] = 'A'
하위 4비트: 165 & 0x0F = 5 → hexDigits[5] = '5'

결과: "A5"

 

 > 왜 static 사용?

static const char hexDigits[] = "0123456789ABCDEF";
  • static : 함수가 호출될 때마다 다시 만들지 않음
  • 메모리 효율성 : 한 번만 초기화
  • const : 읽기만 하므로 변경 불가
    if (!input || !output || outSize == 0) {
        return false;
    }
  • 입력 검증
  • !input 입력 포인터가 NULL이면 실패
  • !output 출력 포인터가 NULL이면 실패
  • outSize == 0 출력 버퍼의 크기가 0이면 실패

 > 예시

textToHex(NULL, buf, 100);       //  return false
textToHex("ABC", NULL, 100);     //  return false
textToHex("ABC", buf, 0);        //  return false
textToHex("ABC", buf, 100);      //  OK

 

    size_t inputLen = strlen(input);
  • 입력 길이 계산
  • 입력 문자열의 길이를 계산 (널 문자 제외)
  • 예: "ABC" → inputLen = 3
    if (inputLen == 0) {
        output[0] = '\0';
        return true;
    }
  • 빈 문자열 처리
  • 입력이 빈 문자열이면?
    >> 출력도 빈 문자열 (\0만)
    >> 성공(true) 반환

 > 예시

textToHex("", buf, 100);
// output = "\0"
// return true

 

    size_t outPos = 0;
  • 출력 위치 초기화
  • 출력 버퍼에 현재 쓰는 위치를 기록
  • 처음에는 맨 앞(0)에서 시작
    if (outSize < inputLen * 3) {
        return false;
    }
  • 필요한 버퍼 크기 계산
  • 계산 공식 분석
  • 각 입력 문자 → 2개 16진수 문자 + 1개 공백 = 3바이트

 > 예1: 입력 "A" (1문자)

'A' → "41 " (2글자 + 공백 = 3바이트) + 널 = 4바이트
그런데 마지막에는 공백이 없음!
'A' → "41\0" (2글자 + 널 = 3바이트)

필요: inputLen * 3 = 1 * 3 = 3바이트

 

> 예2: 입력 "AB" (2문자)

'A' → "41 " (3바이트)
'B' → "42\0" (2글자 + 널 = 3바이트)

총: 6바이트
필요: inputLen * 3 = 2 * 3 = 6바이트

 

 > 예3: 입력 "ABC" (3문자)

'A' → "41 " (3바이트)
'B' → "42 " (3바이트)
'C' → "43\0" (2글자 + 널 = 3바이트)

총: 9바이트
필요: inputLen * 3 = 3 * 3 = 9바이트

 

 > 공식 설명

각 문자당: 16진수 2글자 + 공백 1개 = 3바이트
마지막 문자만: 16진수 2글자 + 널 1개 = 3바이트 (공백 없음)

총합: inputLen * 3 - 1 + 1 = inputLen * 3

(마지막 공백 대신 널 문자)

 

 > 구체적 계산

입력: "ABC"
inputLen = 3
필요 크기: 3 * 3 = 9

출력: "41 42 43\0"
      A  B  C
      0  1  2  3  4  5  6  7  8
      
문자 'A' (65)  → 0x41 → "41 "  (위치 0, 1, 2)
문자 'B' (66)  → 0x42 → "42 "  (위치 3, 4, 5)
문자 'C' (67)  → 0x43 → "43"   (위치 6, 7)
널 문자        →         "\0"   (위치 8)

 

    for (size_t i = 0; i < inputLen; i++) {
        unsigned char byte = (unsigned char)input[i];
  • 버퍼 크기 검증
  • 사용자가 제공한 버퍼 크기(outSize)가 필요한 크기보다 작으면 실패
  • 오버플로우 방지

 > 예시

char buf[9];
textToHex("ABC", buf, 9);    //  OK (필요: 9바이트)
textToHex("ABC", buf, 8);    //  FAIL (필요: 9바이트, 제공: 8)

 

   for (size_t i = 0; i < inputLen; i++) {
  • 각 문자를 16진수로 변환하는 루프
  • 입력 문자를 하나씩 읽음
  • i = 0, 1, 2, ... 순서대로 처리

 > 예시 (입력 "ABC"):

반복 1: i = 0, input[0] = 'A'
반복 2: i = 1, input[1] = 'B'
반복 3: i = 2, input[2] = 'C'
루프 종료: i = 3 > 2

 

        unsigned char byte = (unsigned char)input[i];
  • 현재 문자를 바이트로 읽기
  • 현재 문자를 부호 없는 정수(0~255)로 변환
  • 예: 'A' → 65, 'B' → 66

 > 예시

input[0] = 'A'
byte = 65 = 01000001 (이진)

 

        output[outPos++] = hexDigits[(byte >> 4) & 0x0F];
  • 상위 4비트를 16진수로 변환

 > 비트 분석

byte = 'A' = 65 = 01000001 (이진)
byte >> 4  =       00000100 (4비트 오른쪽)
                         = 4 (10진)
  • byte >> 4 : 바이트를 오른쪽으로 4비트 시프트 (상위 4비트 추출)
0x0F = 00001111 (4개 비트가 1)
4 & 0x0F = 4
  • & 0x0F : 하위 4비트만 남기기 (0~15 범위)
hexDigits = "0123456789ABCDEF"
            0123456789...
                        4 = '4'
  • hexDigits[4]
  • 결과: output[outPos++] = '4' (outPos++이므로 위치도 증가)
입력 바이트 이진 상위 4비트 10진 16진수
'A' 65 01000001 0100 4 '4'
'B' 66 01000010 0100 4 '4'
'C' 67 01000011 0100 4 '4'
' ' 32 00100000 0010 2 '2'
'0' 48 00110000 0011 3 '3'
'Z' 90 01011010 0101 5 '5'

 

        output[outPos++] = hexDigits[byte & 0x0F];   

 

 > 비트 분석

byte = 'A' = 65 = 01000001 (이진)
0x0F = 00001111 (4개 비트가 1)
byte & 0x0F = 00000001 = 1 (10진)
  • byte & 0x0F : 바이트의 하위 4비트만 추출
hexDigits = "0123456789ABCDEF"
             0123456789...
                     1 = '1'
  • hexDigits[1]
  • 결과: output[outPos++] = '1' (outPos++이므로 위치도 증가)

 > 구체적 예시

입력 바이트 이진 하위 4비트 10진 16진수
'A' 65 01000001 0001 1 '1'
'B' 66 01000010 0010 2 '2'
'C' 67 01000011 0011 3 '3'
' ' 32 00100000 0000 0 '0'
'0' 48 00110000 0000 0 '0'
'Z' 90 01011010 1010 10 'A'

 

        if (i < inputLen - 1) {
            output[outPos++] = ' ';
        }
  • 마지막 문자가 아니면 공백 추가
  • 마지막 문자 직전까지 공백 추가
  • 마지막 문자 뒤에는 공백 없음 (널 문자가 올 예정)

 > 조건 분석

inputLen = 3 (입력 "ABC")
i = 0: 0 < 2 ? YES  → 공백 추가
i = 1: 1 < 2 ? YES  → 공백 추가
i = 2: 2 < 2 ? NO   → 공백 없음 (마지막)
  • i < inputLen - 1

 > 구체적 예시

입력: "ABC"

i=0, 'A':
  output[0] = '4' (상위)
  output[1] = '1' (하위)
  i < 2 ? YES → output[2] = ' '
  outPos = 3

i=1, 'B':
  output[3] = '4' (상위)
  output[4] = '2' (하위)
  i < 2 ? YES → output[5] = ' '
  outPos = 6

i=2, 'C':
  output[6] = '4' (상위)
  output[7] = '3' (하위)
  i < 2 ? NO → 공백 없음
  outPos = 8

최종 상태:
output = ['4', '1', ' ', '4', '2', ' ', '4', '3', ?, ?]
outPos = 8

 

    output[outPos] = '\0';
  • 널 문자 추가
  • 출력 버퍼의 끝에 널 문자('\0') 추가
  • C 문자열의 종료 표시

 > 예시

output = ['4', '1', ' ', '4', '2', ' ', '4', '3', '\0']
          0     1     2     3     4     5     6     7     8
                                                  outPos = 8
  • 결과: 문자열 "41 42 43"
    output[outPos] = '\0';
}
  • 성공 반환
  • 모든 검증 통과, 변환 완료
  • true 반환 (성공)

 

 - Hex to Text

static inline int hexCharToValue(char c) {
  • 헬퍼 함수: hexCharToValue
  • static inline
    >> static = 이 파일 내에서만 사용
    >> inline = 함수 호출 오버헤드 제거 (컴파일러가 코드를 직접 삽입)
  • 반환값: int (-1 = 유효하지 않은 문자, 0~15 = 16진수 값)
  • 목적: 16진수 문자('0'~'9', 'A'~'F', 'a'~'f')를 숫자(0~15)로 변환
    if (c >= '0' && c <= '9') return c - '0';
  • 16진수 숫자 판별 (0~9)

 > .비트 분석

'0' = 48
'1' = 49
'2' = 50
...
'9' = 57
  • 문자 '0'~'9'의 ASCII 값
'0' - '0' = 48 - 48 = 0 ✓
'1' - '0' = 49 - 48 = 1 ✓
'2' - '0' = 50 - 48 = 2 ✓
...
'9' - '0' = 57 - 48 = 9 ✓
  • c - '0' 계산

 > 예시

hexCharToValue('0') → 0
hexCharToValue('5') → 5
hexCharToValue('9') → 9

 

    if (c >= 'A' && c <= 'F') return c - 'A' + 10;
  • 16진수 대문자 판별 (A~F)

 > 계산 과정

'A' = 65
'B' = 66
'C' = 67
'D' = 68
'E' = 69
'F' = 70
  • 문자 'A'~'F'의 ASCII 값
'A' - 'A' + 10 = 0 + 10 = 10  (16진수 A) ✓
'B' - 'A' + 10 = 1 + 10 = 11  (16진수 B) ✓
'C' - 'A' + 10 = 2 + 10 = 12  (16진수 C) ✓
'D' - 'A' + 10 = 3 + 10 = 13  (16진수 D) ✓
'E' - 'A' + 10 = 4 + 10 = 14  (16진수 E) ✓
'F' - 'A' + 10 = 5 + 10 = 15  (16진수 F) ✓
  • c - 'A' + 10 계산

 > 예시

hexCharToValue('A') → 10
hexCharToValue('F') → 15

 

    if (c >= 'a' && c <= 'f') return c - 'a' + 10;
  • 16진수 소문자 판별 (a~f)

 > 계산 과정

'a' = 97
'b' = 98
'c' = 99
'd' = 100
'e' = 101
'f' = 102
  • 문자 'a'~'f'의 ASCII 값
'a' - 'a' + 10 = 0 + 10 = 10  (16진수 a) ✓
'b' - 'a' + 10 = 1 + 10 = 11  (16진수 b) ✓
'f' - 'a' + 10 = 5 + 10 = 15  (16진수 f) ✓
  • c - 'a' + 10 계산

 > 예시

hexCharToValue('a') → 10
hexCharToValue('f') → 15

 

    return -1;
  • 유효하지 않은 문자
  • 위의 어떤 조건도 만족하지 않으면 -1 반환
  • "이건 16진수 문자가 아님" 을 의미

 > 예시

hexCharToValue('G') → -1  (F 다음)
hexCharToValue('z') → -1  (16진수 범위 벗어남)
hexCharToValue(' ') → -1  (공백)
hexCharToValue('@') → -1  (특수문자)

 

bool hexToText(const char* input, char* output, size_t outSize) {
  • 함수 선언
  • 반환값: bool (성공/실패 여부)
  • const char* input: 16진수 문자열 (예: "41 42 43")
  • char* output: 변환된 텍스트 결과
  • size_t outSize: 출력 버퍼의 최대 크기
    if (!input || !output || outSize == 0) {
        return false;
    }
  • 입력 검증
  • !input 입력 포인터가 NULL이면 실패
  • !output 출력 포인터가 NULL이면 실패
  • outSize == 0 출력 버퍼의 크기가 0이면 실패

 > 예시

hexToText(NULL, buf, 100);         //  return false
hexToText("41 42", NULL, 100);     //  return false
hexToText("41 42", buf, 0);        //  return false
hexToText("41 42", buf, 100);      //  OK

 

    size_t outPos = 0;
  • 변수 초기화
  • 출력 버퍼의 현재 쓰는 위치 (처음에는 0)
    size_t i = 0;
  • 입력 문자열의 현재 읽는 위치 (처음에는 0)
    while (input[i] != '\0' && outPos < outSize - 1) {
  • 메인 루프: 16진수 쌍 처리
  • 루프 조건
    >> input[i] != '\0' : 입력 문자열이 끝나지 않았으면 계속
    >> outPos < outSize - 1 : 출력 버퍼에 공간이 있으면 계속
       >>> -1 : 널 문자 공간 예약

 > 예시

input = "41 42 43"
outPos = 0, 1, 2, ... (최대 outSize-2까지만)

 

        while (input[i] == ' ' || input[i] == ':' || input[i] == '-' ||
            input[i] == '\t' || input[i] == '\n' || input[i] == '\r') {
            if (input[i] == '\0') break;
            i++;
        }
  • 공백 및 구분자 스킵

 > 처리하는 구분자들

문자 이름 ASCII 용도
  공백 32 16진수 구분
: 콜론 58 MAC 주소 (AA:BB:CC)
- 하이픈 45 범위 표현 (AA-BB-CC)
\t 9 공백
\n 줄바꿈 10 개행
\r  캐리지 리턴 13 개행

 

 > 구체적 예시

입력: "41 42:43-44"
처리:
  i=0: '4' → 16진수 (스킵 안 함)
  i=0: 숫자 처리
  ...
  i=2: ' ' → 스킵!
  i=3: '4' → 16진수
  i=3: 숫자 처리
  ...
  i=5: ':' → 스킵!
  i=6: '4' → 16진수
  ...

 

while (input[i] == ' ' || input[i] == ':' || ...) {
    if (input[i] == '\0') break;  // 문자열 끝이면 탈출
    i++;  // 다음 문자로
}
  • nested while 구조
i=2, input[2]=' ' → 조건 만족
  input[2] == '\0'? NO
  i++ → i=3

i=3, input[3]='4' → 조건 불만족
  while 탈출
  • 흐름
        if (input[i] == '\0') break;
  • 문자열 끝 확인
  • 공백 스킵 후에도 문자열이 끝나면 루프 탈출
  • 더 이상 처리할 16진수가 없음

 > 예시

입력: "41 42 "  (공백으로 끝남)
처리:
  i=0~3: "41 42" 처리
  i=5: ' ' → 스킵
  i=6: '\0' → break

 

        int high = hexCharToValue(input[i]);
        if (high == -1) {
            return false;
        }
  • 첫 번째 16진수 문자 확인
  • 상위 4비트를 나타낼 16진수 문자 읽기
  • 유효하지 않으면 실패 반환

 > 예시

입력: "41 42"
i=0: input[0]='4' → hexCharToValue('4')=4 → high=4
i=0: input[0]='G' → hexCharToValue('G')=-1 → return false

 

        if (input[i + 1] == '\0') {
            return false;  // 홀수 개의 hex 문자
        }
  • 두 번째 16진수 문자 존재 여부 확인
  • 16진수는 항상 쌍으로 나와야 함 (2개씩)
  • input[i+1]이 널 문자면 홀수 개 → 실패

 > 예시

입력: "41 42 4"  (홀수 개)
처리:
  "41" → OK (2개)
  "42" → OK (2개)
  "4" → input[i+1]='\0' → return false

 

        int low = hexCharToValue(input[i + 1]);
        if (low == -1) {
            return false;
        }
  • 두 번째 16진수 문자 확인
  • 하위 4비트를 나타낼 16진수 문자 읽기
  • 유효하지 않으면 실패 반환

 > 예시

입력: "41 4G"
처리:
  i=3: input[3]='4' → high=4
  i=4: input[4]='G' → hexCharToValue('G')=-1 → return false

 

        output[outPos++] = (char)((high << 4) | low);
  • 두 문자를 하나의 바이트로 조합

 > 비트 조합 과정

high = 4 = 0000 0100
high << 4 = 0100 0000 (64)
  • high << 4 : 상위 4비트를 왼쪽으로 4비트 시프트
high << 4 = 0100 0000 (64)
low = 1 = 0000 0001
|           0100 0001 (65 = 'A')
  • | low : 하위 4비트를 또는(OR) 연산으로 합치기
  • 결과: output[0] = 65 = 'A'

 > 구체적 예시

16진수 high low 계산 결과 문자
41 4 1 (4<<4)|1 65 'A'
42 4 2 (4<<4)|2 66 'B'
43 4 3 (4<<4)|3 67 'C'
FF 15 15 (15<<4)|15 255 (최댓값)
00 0 0 (0<<4)|0 0 '\0
48 4 8 (4<<4)|8 72 'H'
65 6 5 (6<<4)|5 101 'e'

 

high = 4 (문자 '4')
low = 1 (문자 '1')

4 << 4 = 0000 0100 << 4 = 0100 0000 (이진)
1 = 0000 0001 (이진)

0100 0000 | 0000 0001 = 0100 0001 (이진) = 65 (10진) = 'A'
  • 상세 계산 (입력 "41")
        i += 2;
  • 위치 이동
  • 2개의 16진수 문자를 읽었으므로 2칸 전진
  • 공백까지 포함하면 보통 3칸 (다음 루프에서 공백 스킵)

 > 예시

입력: "41 42 43"
      01 23 45 67

i=0: '4' 읽음
i=1: '1' 읽음
i += 2 → i=2

i=2: ' ' → while 루프에서 스킵 → i=3
i=3: '4' 읽음
i=4: '2' 읽음
i += 2 → i=5

...

 

    output[outPos] = '\0';
  • 널 문자 추가
  • 출력 버퍼의 끝에 널 문자('\0') 추가
  • C 문자열의 종료 표시

 > 예시

output = ['A', 'B', 'C', '\0']
          0     1     2     3
                    outPos = 3

 

    return true;
}
  • 성공 반환
  • 모든 검증 통과, 변환 완료
  • true 반환 (성공)

 

◎ Part 1: 헤더 + 매크로 + 변환 함수들

#ifndef MAIN_H
#define MAIN_H

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <ctype.h>
#include <limits.h>
#include <stddef.h>

#pragma comment(linker, "/SUBSYSTEM:WINDOWS")
#pragma comment(linker, "/ENTRY:WinMainCRTStartup")

// Control IDs
#define IDC_INPUT_TEXT      101
#define IDC_OUTPUT_TEXT     102

// Conversion buttons
#define IDC_TEXT_TO_HEX     201
#define IDC_HEX_TO_TEXT     202
#define IDC_DEC_TO_HEX      203
#define IDC_HEX_TO_DEC      204
#define IDC_TEXT_TO_DEC     205
#define IDC_DEC_TO_TEXT     206
#define IDC_DEC_TO_OCTAL    207
#define IDC_OCTAL_TO_DEC    208
#define IDC_TEXT_TO_UTF7    209
#define IDC_UTF7_TO_TEXT    210
#define IDC_HEX_TO_UCS2     211
#define IDC_UCS2_TO_HEX     212
#define IDC_TEXT_TO_BINARY  213
#define IDC_BINARY_TO_TEXT  214
#define IDC_ESCAPE          215
#define IDC_UNESCAPE        216
#define IDC_ENCODE_HTML     217
#define IDC_DECODE_HTML     218
#define IDC_TEXT_TO_BASE64  219
#define IDC_BASE64_TO_TEXT  220
#define IDC_HEX_TO_BASE64   221
#define IDC_BASE64_TO_HEX   222

// Utility buttons
#define IDC_COPY_OUTPUT     226
#define IDC_COPY_TO_INPUT   227
#define IDC_CLEAR_ALL       228

// Buffer sizes
#define MAX_BUFFER 8192
#define BASE64_DECODE_MAX_SIZE(n) (((n) / 4) * 3 + 1)
#define TEXT_TO_HEX_SIZE(n) ((n) == 0 ? 1 : ((n) * 3))
#define HEX_TO_TEXT_MAX_SIZE(n) (((n) / 2) + 1)
#define TEXT_TO_DEC_SIZE(n) ((n) == 0 ? 1 : ((n) * 4 + 1))
#define DEC_TO_HEX_MAX_SIZE(count) ((count) * 9 + 1)
#define TEXT_TO_BINARY_SIZE(n) ((n) == 0 ? 1 : ((n) * 9))
#define BINARY_TO_TEXT_MAX_SIZE(bits) (((bits) / 8) + 1)
#define ESCAPE_STRING_MAX_SIZE(n) ((n) * 2 + 1)
#define ENCODE_HTML_MAX_SIZE(n) ((n) * 6 + 1)

// ============ 공통 헬퍼 함수들 ============

// Hex 문자를 숫자로 변환
static inline int hexCharToValue(char c) {
    if (c >= '0' && c <= '9') return c - '0';
    if (c >= 'A' && c <= 'F') return c - 'A' + 10;
    if (c >= 'a' && c <= 'f') return c - 'a' + 10;
    return -1;
}

//
static inline size_t uintToStr(unsigned int val, char* buf) {
    if (val == 0) {
        buf[0] = '0';
        return 1;
    }

    char temp[4];  // 최대 3자리 + null
    size_t len = 0;

    // 역순으로 숫자 추출
    while (val > 0) {
        temp[len++] = '0' + (val % 10);
        val /= 10;
    }

    // 정순으로 복사
    for (size_t i = 0; i < len; i++) {
        buf[i] = temp[len - 1 - i];
    }

    return len;
}

// 숫자를 10진수 문자열로 변환
static inline size_t uintToDecimal(unsigned int val, char* buf) {
    if (val == 0) {
        buf[0] = '0';
        return 1;
    }

    char temp[11];
    size_t len = 0;

    while (val > 0) {
        temp[len++] = '0' + (val % 10);
        val /= 10;
    }

    for (size_t i = 0; i < len; i++) {
        buf[i] = temp[len - 1 - i];
    }

    return len;
}

// 숫자를 16진수 문자열로 변환
static inline size_t uintToHex(unsigned int val, char* buf) {
    static const char hexDigits[] = "0123456789ABCDEF";

    if (val == 0) {
        buf[0] = '0';
        return 1;
    }

    char temp[9];
    size_t len = 0;

    while (val > 0) {
        temp[len++] = hexDigits[val & 0x0F];
        val >>= 4;
    }

    for (size_t i = 0; i < len; i++) {
        buf[i] = temp[len - 1 - i];
    }

    return len;
}

// 안전한 2진수 파싱
static bool parseBinaryByte(const char* str, unsigned char* result) {
    if (!str || strlen(str) != 8) {
        return false;
    }

    // 모든 문자가 0 또는 1인지 확인
    for (int i = 0; i < 8; i++) {
        if (str[i] != '0' && str[i] != '1') {
            return false;
        }
    }

    char* endptr;
    errno = 0;
    unsigned long val = strtoul(str, &endptr, 2);

    if (errno != 0 || endptr != str + 8 || val > 255) {
        return false;
    }

    *result = (unsigned char)val;
    return true;
}

// UTF-8로 인코딩 (간단한 버전)
static size_t encodeUTF8(unsigned int codepoint, char* output) {
    if (codepoint <= 0x7F) {
        // 1바이트: 0xxxxxxx
        output[0] = (char)codepoint;
        return 1;
    }
    else if (codepoint <= 0x7FF) {
        // 2바이트: 110xxxxx 10xxxxxx
        output[0] = (char)(0xC0 | (codepoint >> 6));
        output[1] = (char)(0x80 | (codepoint & 0x3F));
        return 2;
    }
    else if (codepoint <= 0xFFFF) {
        // 3바이트: 1110xxxx 10xxxxxx 10xxxxxx
        output[0] = (char)(0xE0 | (codepoint >> 12));
        output[1] = (char)(0x80 | ((codepoint >> 6) & 0x3F));
        output[2] = (char)(0x80 | (codepoint & 0x3F));
        return 3;
    }
    else if (codepoint <= 0x10FFFF) {
        // 4바이트: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
        output[0] = (char)(0xF0 | (codepoint >> 18));
        output[1] = (char)(0x80 | ((codepoint >> 12) & 0x3F));
        output[2] = (char)(0x80 | ((codepoint >> 6) & 0x3F));
        output[3] = (char)(0x80 | (codepoint & 0x3F));
        return 4;
    }

    return 0;  // 잘못된 코드포인트
}

// Dec to 8진수(Octal)로 변환
static size_t uintToOctal(unsigned int num, char* output) {
    if (num == 0) {
        output[0] = '0';
        output[1] = '\0';
        return 1;
    }

    char buffer[12]; // 최대 11자리 + 널 문자
    int i = 11;
    buffer[i] = '\0';

    while (num > 0 && i > 0) {
        i--;
        buffer[i] = (num % 8) + '0';
        num /= 8;
    }

    size_t len = 11 - i;
    // 결과 복사
    for (size_t j = 0; j < len; j++) {
        output[j] = buffer[i + j];
    }
    output[len] = '\0';

    return len;
}

// 8진수(Octal)에서 변환
static size_t uintToDec(unsigned int num, char* output) {
    // 32비트 unsigned int는 최대 10자리 (4294967295) + 널 문자
    int len = snprintf(output, 11, "%u", num);
    if (len < 0 || len >= 11) {
        return 0; // 변환 실패 또는 버퍼 부족
    }
    return (size_t)len;
}

// text to UTF7로 변환
static const char base64_chars[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

static size_t encode_base64_block(const unsigned char* input, size_t inputLen,
    char* output, size_t outSize) {
    if (inputLen == 0 || inputLen > 3) {
        return 0;
    }

    size_t outPos = 0;
    unsigned int block = 0;

    // 바이트 조립
    for (size_t i = 0; i < inputLen; i++) {
        block = (block << 8) | (unsigned char)input[i];
    }

    // 패딩을 포함한 블록 생성
    size_t padding = 3 - inputLen;
    block <<= (8 * padding);

    // Base64 문자 수 계산
    size_t b64_char_count = (inputLen * 8 + 5) / 6;

    if (outSize < b64_char_count + 1) {
        return 0;
    }

    // 6비트씩 추출
    size_t total_bits = (inputLen + padding) * 8;
    for (size_t j = 0; j < b64_char_count; j++) {
        int shift = (int)(total_bits - (j + 1) * 6);  // 시프트는 int OK
        unsigned char index = (block >> shift) & 0x3F;
        output[outPos++] = base64_chars[index];
    }

    return outPos;
}

// --- 헬퍼 함수 1: Base64 문자에서 값 가져오기 ---

// Base64 문자(0-63)의 인덱스 값을 반환합니다.
static int base64_char_to_value(char c) {
    if (c >= 'A' && c <= 'Z') return c - 'A';
    if (c >= 'a' && c <= 'z') return c - 'a' + 26;
    if (c >= '0' && c <= '9') return c - '0' + 52;
    if (c == '+') return 62;
    if (c == '/') return 63;

    // UTF-7 Base64 블록 내에서는 패딩 문자 '='는 발생하지 않아야 합니다.
    return -1; // 유효하지 않은 Base64 문자
}

// --- 헬퍼 함수 2: UTF-7 Modified Base64 디코딩 ---

// Base64 문자열을 디코딩하여 바이트 스트림 (UTF-16 바이트)으로 변환합니다.
// inputLen: Base64 문자열의 길이 (예: 'ICE'는 3)
// outSize: 디코딩된 바이트를 저장할 버퍼 크기
static size_t decode_base64_block(const char* input, size_t inputLen, unsigned char* output, size_t outSize) {
    size_t outPos = 0;
    size_t i = 0;

    while (i < inputLen) {
        int val[4];
        int val_count = 0;

        // Base64 문자 4개(또는 끝까지) 읽고 6비트 값으로 변환
        for (int j = 0; j < 4 && i < inputLen; j++) {
            int current_val = base64_char_to_value(input[i++]);
            if (current_val != -1) {
                val[val_count++] = current_val;
            }
            else {
                // 유효하지 않은 Base64 문자는 무시하거나 오류 처리 가능. 여기서는 오류 처리 없이 진행.
            }
        }

        if (val_count == 0) break;

        // 6비트 4개를 묶어 24비트 블록 생성 (최대)
        unsigned int block = 0;
        if (val_count >= 1) block |= val[0] << 18;
        if (val_count >= 2) block |= val[1] << 12;
        if (val_count >= 3) block |= val[2] << 6;
        if (val_count >= 4) block |= val[3];

        // 디코딩되는 바이트 수 계산 (val_count * 6 / 8의 내림)
        int byte_count = (val_count * 3) / 4;

        // 1 바이트 (상위 8비트)
        if (byte_count >= 1) {
            if (outPos >= outSize) return 0; // 버퍼 부족
            output[outPos++] = (unsigned char)((block >> 16) & 0xFF);
        }

        // 2 바이트 (중간 8비트)
        if (byte_count >= 2) {
            if (outPos >= outSize) return 0;
            output[outPos++] = (unsigned char)((block >> 8) & 0xFF);
        }

        // 3 바이트 (하위 8비트)
        if (byte_count >= 3) {
            if (outPos >= outSize) return 0;
            output[outPos++] = (unsigned char)(block & 0xFF);
        }
    }

    return outPos;
}

#endif // MAIN_H

HWND hInputEdit, hOutputEdit;

 

1. #include 와 #define

 - 전처리기와 헤더들

#define WIN32_LEAN_AND_MEAN
  • #define은 “매크로 정의”입니다.
  • WIN32_LEAN_AND_MEAN을 정의하면 windows.h 안에서 자주 안 쓰는 오래된 API들을 빼고 포함해서, 컴파일 속도와 충돌 가능성을 줄여 줍니다.
#include <windows.h>
  • Windows API 함수와 자료형(예: HWND, HINSTANCE, CreateWindowEx 등)을 쓰기 위해 필요한 헤더입니다.
#include <stdio.h>
  • 표준 입출력 함수들(printf, snprintf 등)을 사용하기 위한 헤더입니다.
#include <string.h>
  • 문자열 관련 함수들(strlen, memcpy, strncmp 등)을 사용하기 위한 헤더입니다.
#include <stdbool.h>
  • Boolean 타입(bool, true, false)을 사용하기 위한 헤더입니다.
#include <stdint.h>
  • 고정 크기 정수 타입들(int8_t, uint16_t, int32_t, uint64_t 등)을 사용하기 위한 헤더입니다.
#include <stdlib.h>
  • malloc, free, atoi, strtol 같은 메모리/숫자 변환 함수를 사용하기 위한 헤더입니다.
#include <ctype.h>
  • isdigit, isxdigit 등의 문자 판별 함수를 사용하기 위한 헤더입니다.

 

 - 링커·컴파일러 옵션

#pragma comment(linker, "/SUBSYSTEM:WINDOWS")
  • 링커에게 “이 프로그램은 윈도우 GUI 프로그램이다”라고 알려 줍니다.​ 그래서 콘솔 창 없이 실행되게 됩니다.
#pragma comment(linker, "/ENTRY:WinMainCRTStartup")
  • 링커에게 프로그램 시작 함수 이름을 지정합니다.
  • 여기서는 C 런타임 초기화 함수 WinMainCRTStartup을 엔트리로 쓰겠다는 의미이고, 그 안에서 결국 사용자가 만든 WinMain이 호출됩니다.

 

 - 컨트롤 ID 정의

#define IDC_INPUT_TEXT      101
#define IDC_OUTPUT_TEXT     102
  • 에디트 박스(입력/출력)에 붙일 ID 번호입니다.
  • 나중에 WM_COMMAND에서 LOWORD(wParam)을 읽어 “어떤 컨트롤인지” 구분할 때 사용합니다.
// Conversion buttons
#define IDC_TEXT_TO_HEX     201
#define IDC_HEX_TO_TEXT     202
#define IDC_DEC_TO_HEX      203
#define IDC_HEX_TO_DEC      204
#define IDC_TEXT_TO_DEC     205
#define IDC_DEC_TO_TEXT     206
#define IDC_DEC_TO_OCTAL    207
#define IDC_OCTAL_TO_DEC    208
#define IDC_TEXT_TO_UTF7    209
#define IDC_UTF7_TO_TEXT    210
#define IDC_HEX_TO_UCS2     211
#define IDC_UCS2_TO_HEX     212
#define IDC_TEXT_TO_BINARY  213
#define IDC_BINARY_TO_TEXT  214
#define IDC_ESCAPE          215
#define IDC_UNESCAPE        216
#define IDC_ENCODE_HTML     217
#define IDC_DECODE_HTML     218
#define IDC_TEXT_TO_BASE64  219
#define IDC_BASE64_TO_TEXT  220
#define IDC_HEX_TO_BASE64   221
#define IDC_BASE64_TO_HEX   222
  • 각각의 변환 기능 버튼에 대한 고유 ID입니다.
  • 예를 들어 "Text to Hex" 버튼을 만들 때 IDC_TEXT_TO_HEX를 붙여 두고, 나중에 switch (btnId)에서 case IDC_TEXT_TO_HEX:이면 textToHex() 함수를 호출하는 식으로 연결합니다.​
// Utility buttons
#define IDC_COPY_OUTPUT     226
#define IDC_COPY_TO_INPUT   227
#define IDC_CLEAR_ALL       228
  • 유틸리티 기능(출력 복사, 출력→입력 복사, 전체 지우기) 버튼들의 ID입니다.
  • 마찬가지로 WM_COMMAND에서 이 번호로 어떤 버튼인지 구분합니다.

 

 - 버퍼 크기 상수

#define MAX_BUFFER 8192
  • 이 프로그램에서 문자열 버퍼를 만들 때 공통으로 사용할 최대 크기입니다.
  • 예: char input[MAX_BUFFER]; 같이 사용해서, 한 번에 처리할 수 있는 최대 글자 수를 8192 바이트로 제한합니다.


2. 전역 변수

HWND hInputEdit, hOutputEdit;
  • HWND = "Handle to Window" : “윈도우(컨트롤)를 가리키는 핸들(번호)”입니다.​
  • 윈도우 핸들 : 입력/출력 텍스트박스를 어디서나 접근할 수 있도록 전역변수를 선언합니다.
  • hInputEdit : 입력용 에디트 박스, hOutputEdit : 출력용 에디트 박스를 가리킵니다.

 

◎ 개요

 - 2025년 10월 7일에 공식 출시된 Python 3.14는 파이썬 역사상 가장 중요한 릴리스 중 하나로 평가받고 있습니다.

 - 이번 버전은 성능, 보안, 그리고 개발자 경험을 크게 향상시키는 혁신적인 기능들을 도입했습니다.

◎ 핵심 새로운 기능들
 - Template String Literals (T-Strings)
  > Python 3.14의 가장 주목할 만한 새 기능 중 하나는 템플릿 문자열 리터럴입니다.

  > T-strings는 기존 f-string 문법을 사용하지만 즉시 문자열로 변환되지 않고 Template 객체를 반환합니다.

variety = 'Stilton'
template = t'Try some {variety} cheese!'

  > 이 기능의 핵심 장점은 보안성입니다.

  > T-strings는 SQL 인젝션이나 XSS 공격과 같은 보안 취약점을 방지할 수 있는 안전한 문자열 처리를 제공합니다.

  > 또한 HTML, SQL, 구조화된 로깅 등 다양한 도메인에서 맞춤형 문자열 처리가 가능합니다.

 - 지연된 어노테이션 평가 (Deferred Annotation Evaluation)
  > PEP 649를 통해 Python 3.14는 함수, 클래스, 모듈의 어노테이션을 즉시 평가하지 않고 필요할 때만 평가하도록 변경했습니다.

  > 이는 성능 향상과 순환 참조 문제 해결에 도움이 됩니다.

# Python 3.14에서는 이것이 가능합니다
def process_user(user: User) -> dict:
    return {"name": user.name}

class User:
    name: str

  > User 클래스가 아직 정의되지 않았더라도 함수 정의 시점에서는 오류가 발생하지 않습니다.

 - Free-Threading (GIL 제거)
  > Python 3.14는 Free-threaded Python을 공식적으로 지원합니다.

  > 이는 Global Interpreter Lock(GIL)을 비활성화하여 진정한 멀티스레드 병렬성을 가능하게 합니다.

  > 이전 Python 3.13에서 실험적이었던 이 기능이 이제 공식 지원되며, 단일 스레드 코드의 성능 페널티가 40%에서 5-10%로 크게 감소했습니다.

 - 서브인터프리터 (Subinterpreters)
  > PEP 734를 통해 Python 3.14는 표준 라이브러리에서 여러 인터프리터를 동시에 사용할 수 있는 기능을 추가했습니다.

  > 새로운 concurrent.interpreters 모듈을 통해 동일한 프로세스 내에서 독립적인 Python 인터프리터를 관리할 수 있습니다.

from concurrent.futures import InterpreterPoolExecutor

def sums(num):
    return sum(i * i for i in range(num + 1))

with InterpreterPoolExecutor() as executor:
    print(list(executor.map(sums, [100_000] * 4)))

  > 각 인터프리터는 자체 GIL을 가지므로 필요에 따라 별도의 CPU를 사용할 수 있어 멀티스레딩과 멀티프로세싱의 장점을 모두 제공합니다.

◎ 사용자 인터페이스 개선사항
 - REPL 개선

  > Python 3.14의 REPL(대화형 셸)은 여러 중요한 개선사항을 포함합니다.

  • 실시간 구문 강조: 코드를 입력할 때 키워드, 문자열, 주석, 숫자 등이 각각 다른 색상으로 표시됩니다
  • 자동 완성: import 문에서 모듈 이름을 자동 완성할 수 있습니다
  • 디버거 구문 강조: Python 디버거(pdb)에서도 소스 코드가 색상으로 표시됩니다

 

 - 색상 지원 확장

  > Python 3.14는 여러 명령줄 도구에서 색상 출력을 지원합니다.

  • unittest: 실패한 테스트는 빨간색, 통과한 테스트는 녹색으로 표시됩니다
  • argparse: 도움말 텍스트가 색상으로 표시됩니다
  • json 모듈: JSON 출력이 기본적으로 색상으로 표시됩니다

 

 - 개선된 오류 메시지

  > Python 3.14는 더 친근한 오류 메시지를 제공합니다.

  > 예를 들어, argparse는 이제 오타에 대한 수정 제안을 제공합니다.

◎ 표준 라이브러리 개선사항
 - pathlib 새로운 메서드들

  > pathlib.Path 객체에 네 가지 새로운 메서드가 추가되었습니다.

  • copy(): 파일을 대상 위치로 복사
  • copy_into(): 파일을 특정 디렉토리로 복사
  • move(): 파일을 대상 위치로 이동
  • move_into(): 파일을 특정 디렉토리로 이동

  > 이제 shutil 모듈을 사용하지 않고도 Path 객체에서 직접 파일 복사와 이동이 가능합니다.

 

 - datetime 개선

  > Python 3.14에서는 날짜와 시간 문자열을 직접 datetime.date나 datetime.time 객체로 파싱할 수 있습니다:

from datetime import date, time

# 이제 직접 파싱 가능
my_date = date.strptime("2026-03-14", "%Y-%m-%d")
my_time = time.strptime("15:30:00", "%H:%M:%S")


 - UUID 개선
  > uuid 모듈이 이제 UUID 버전 6-8을 지원하며, 특히 생성 시간으로 정렬 가능한 UUID7을 지원합니다.

  > 또한 기존 버전(3-5)의 생성 속도가 최대 40% 향상되었습니다.

 - Zstandard 압축 지원
  > PEP 784를 통해 새로운 compression.zstd 모듈이 추가되어 Zstandard 압축 알고리즘을 기본 지원합니다.

  > Zstandard는 Facebook이 개발한 압축 알고리즘으로 기존 gzip이나 bz2보다 빠르고 높은 압축률을 제공합니다.

◎ 성능 개선사항
 - 실험적 JIT 컴파일러
  > Python 3.14는 macOS와 Windows 공식 바이너리에 실험적 JIT(Just-In-Time) 컴파일러를 포함합니다.

  > 환경 변수 PYTHON_JIT=1로 활성화할 수 있으며, 특정 컴파일러에서 3-5%의 성능 향상을 제공합니다.

 - 새로운 인터프리터 타입
  > Python 3.14는 특정 최신 컴파일러에서 상당한 성능 향상을 제공하는 새로운 타입의 인터프리터를 추가했습니다.

  > 이는 꼬리 호출 기반 구현을 사용하여 성능을 개선합니다.

◎ 문법 개선사항
 - 예외 처리 간소화
  > PEP 758을 통해 여러 예외 타입을 포착할 때 괄호를 생략할 수 있게 되었습니다:

# Python 3.14에서 가능
try:
    int(value)
except ValueError, TypeError:
    print("Invalid number")


 - 안전성 향상
  > PEP 765를 통해 finally 블록을 종료하는 return/break/continue 문이 금지되었습니다.

  > 이는 코드의 안전성을 향상시킵니다.

◎ 디버깅 및 개발 도구 개선
 - 향상된 디버깅 인터페이스
  > Python 3.14는 실행 중인 Python 프로세스에 안전하게 연결할 수 있는 제로 오버헤드 디버깅 인터페이스를 제공합니다.

  > 새로운 명령줄 인터페이스를 통해 비동기 작업을 검사할 수 있습니다:

python -m asyncio ps PID
python -m asyncio pstree PID

  > pdb 모듈도 이제 -p PID 옵션을 통해 실행 중인 Python 프로세스에 원격으로 연결할 수 있습니다.

◎ 미래 전망
 - Python 3.14는 파이썬의 진화에서 중요한 이정표입니다. Free-threading의 공식 지원, 서브인터프리터의 표준화, 그리고 T-strings와 같은 새로운 언어 기능들은 Python이 현대적인 애플리케이션 개발의 요구사항을 충족하기 위해 지속적으로 발전하고 있음을 보여줍니다.
 - 이러한 개선사항들은 개발자들에게 더 나은 성능, 향상된 보안성, 그리고 개선된 개발 경험을 제공하며, Python의 미래를 더욱 밝게 만들고 있습니다.

1. 가상환경 사용 이유

 - Python 가상환경은 각 프로젝트마다 독립된 Python 환경을 제공하는 핵심적인 개발 도구입니다.

 

  > 의존성 격리(Dependency Isolation)

  • 가상환경의 주요 목적은 프로젝트 간 패키지 충돌을 방지하는 것입니다.
  • 예를 들어, 프로젝트 A에서는 Django 2.0이 필요하고 프로젝트 B에서는 Django 3.0이 필요한 경우, 전역 Python 환경에서는 버전 충돌이 발생합니다.

 

  > 재현 가능한 개발 환경

  • 가상환경을 사용하면 동일한 버전의 패키지들을 정확히 지정하여 다른 개발자나 시스템에서도 동일한 환경을 재현할 수 있습니다.

 

  > 프로젝트 이식성

  • 각 프로젝트가 자체 환경을 가지므로, 프로젝트를 다른 시스템으로 이동하거나 공유할 때 의존성 문제 없이 작업할 수 있습니다.

 

  > 시스템 Python 보호

  • 전역 Python 환경을 건드리지 않으므로 시스템 Python 설치가 손상될 위험이 없습니다.

 

2. Python2와 Python3 가상환경 비교

 - Python3 - venv 모듈 (권장)

  > Python 3.3부터 표준 라이브러리에 포함된 venv 모듈은 별도 설치 없이 사용 가능합니다.

  > 특징

  • Python 3.3+ 표준 라이브러리 포함
  • 별도 설치 불필요
  • 가볍고 빠른 실행
  • Python2 지원 안함

 

 - Python2 - virtualenv 패키지 (필수)

  > Python2에서는 virtualenv 서드파티 패키지를 반드시 설치해야 합니다.

  > 특징

  • Python2와 Python3 모두 지원
  • 별도 설치 필요 (pip install virtualenv)
  • venv보다 더 많은 기능 제공
  • 더 많은 커스터마이징 옵션

 

3. Python2 가상환경 설정

 - Python2 pip 설치

  > 먼저 Python2용 pip를 설치해야 합니다:

get-pip.py 스크립트 다운로드
# wget https://bootstrap.pypa.io/pip/2.7/get-pip.py

Python2용 pip 설치
# python2 get-pip.py

설치 확인
# which pip2
# pip2 --version


 - virtualenv 설치

Python2용 virtualenv 설치
# pip2 install virtualenv

또는 사용자 로컬 설치
# pip2 install --user virtualenv


 - Python2 가상환경 생성 및 사용

가상환경 생성용 패키지 설치
# apt install python버전-venv

가상환경 생성
# virtualenv venv-python2

Python 버전 지정하여 생성
# virtualenv --python=python2.7 venv-python2

가상환경 활성화
# source venv-python2/bin/activate

활성화 확인 (프롬프트에 (venv-python2) 표시)
# python --version
# which python

패키지 설치
# pip install requests flask

의존성 목록 생성
# pip freeze > requirements.txt

가상환경 비활성화
# deactivate


4. Python3 가상환경 설정

venv 모듈 사용 (권장)

가상환경 생성
# python3 -m venv venv-python3

가상환경 활성화
# source venv-python3/bin/activate

활성화 확인
# python --version
# which python

pip 업그레이드
# pip install --upgrade pip

패키지 설치
# pip install numpy pandas matplotlib

의존성 목록 생성
# pip freeze > requirements.txt

가상환경 비활성화
# deactivate


 - virtualenv 사용 (선택사항)

virtualenv 설치 (필요한 경우)
# pip3 install virtualenv

가상환경 생성
# python3 -m virtualenv venv-python3

활성화 및 사용법은 동일
# source venv-python3/bin/activate


5. 의존성 패키지 관리

 - requirements.txt 활용

  > 의존성 목록 생성:

현재 설치된 패키지 목록 저장
# pip freeze > requirements.txt

로컬 가상환경 패키지만 저장 (권장)
# pip freeze --local > requirements.txt


  > 의존성 설치:

requirements.txt에서 패키지 설치
# pip install -r requirements.txt

 

 - requirements.txt 파일 위치

  • 프로젝트 루트 디렉터리에 위치해야 하며, 가상환경 폴더 안에 두면 안됩니다. 버전 관리 시스템(Git)에 포함되어야 합니다.

 

 - 패키지 관리 명령어

패키지 설치
# pip install package_name

특정 버전 설치
# pip install package_name==1.2.3

패키지 업그레이드
# pip install --upgrade package_name

설치된 패키지 목록 확인
# pip list

패키지 정보 확인
# pip show package_name

패키지 제거
# pip uninstall package_name


6. 가상환경 관리 및 제거

 - 가상환경 비활성화

현재 활성화된 가상환경에서 실행
# deactivate


 - 가상환경 완전 제거

가상환경 비활성화 후 폴더 삭제
# deactivate
# rm -rf venv-python2
# rm -rf venv-python3


 - 여러 가상환경 관리

프로젝트별 가상환경 구조 (권장)
~/projects/
├── project1/
│   ├── venv/
│   ├── requirements.txt
│   └── main.py
├── project2/
│   ├── venv/
│   ├── requirements.txt
│   └── app.py
└── project3/
    ├── venv/
    ├── requirements.txt
    └── script.py


7. 고급 활용 팁

 - 가상환경 자동 활성화

  > .bashrc 또는 .zshrc에 함수 추가:

프로젝트 디렉터리로 이동하며 가상환경 자동 활성화
function cdenv() {
    cd "$1"
    if [ -d "venv" ]; then
        source venv/bin/activate
    fi
}


 - 가상환경 백업 및 복원

가상환경 백업
# pip freeze > backup_requirements.txt

새 환경에서 복원
# python3 -m venv new_venv
# source new_venv/bin/activate
# pip install -r backup_requirements.txt


 - Python 버전 지정

특정 Python 버전으로 가상환경 생성
# virtualenv --python=/usr/bin/python2.7 py27_env
# virtualenv --python=/usr/bin/python3.8 py38_env

venv의 경우
# /usr/bin/python3.8 -m venv py38_env


8. 문제 해결

 - 일반적인 오류

  > ModuleNotFoundError: 가상환경이 활성화되지 않은 상태에서 패키지를 찾을 수 없는 경우

해결: 가상환경 활성화 확인
# source venv/bin/activate


  > Permission denied: 시스템 전역에 패키지 설치하려 할 때

해결: 가상환경에서 설치하거나 --user 플래그 사용
# pip install --user package_name


  > pip 버전 호환성: Python2에서 최신 pip 버전 사용 시 오류

해결: Python2 호환 pip 버전 설치
# python2 get-pip.py "pip<21.0"


 - 이러한 가상환경 관리 방법을 통해 Python 프로젝트의 의존성을 효과적으로 관리하고 개발 환경의 안정성을 확보할 수 있습니다.

 - 그리고 각 프로젝트마다 독립된 환경을 유지하여 버전 충돌 없이 개발할 수 있습니다.

1. 개요

  • 파이썬은 “읽기 쉬운 문법”과 “방대한 생태계”를 강점으로 하는 범용 프로그래밍 언어입니다.
  • 빠른 프로토타이핑, 데이터 과학, 자동화, 웹 백엔드, 교육 영역에서 최고의 생산성을 제공합니다.
  • 반면, 초저지연·초고성능이 필요한 시스템, 하드 실시간, 모바일 네이티브 앱 등에는 부적합하거나 추가 도구가 필요합니다.

 

2. 파이썬이란 무엇인가

  • 파이썬은 1991년 귀도 반 로섬이 설계한 고수준, 인터프리터 기반의 범용 프로그래밍 언어입니다.
  • “읽기 쉬운 코드가 유지보수성과 생산성을 높인다”는 설계 철학 하에 들여쓰기 기반 블록, 간결한 문법, 일관된 표준 라이브러리를 채택합니다.
  • 동적 타이핑과 거대한 패키지 생태계(PyPI)를 결합해, 스크립팅부터 대규모 서비스 개발, 데이터 과학, 머신러닝, 자동화에 이르기까지 폭넓게 사용됩니다.

 

 - 핵심 철학(파이선 오브 젠)에 담긴 가치:

  • 명시적, 단순, 가독성 중심
  • 실용주의와 일관성 추구
  • “한 가지—그리고 가급적 한 가지만—명백한 방법”

 

3. 언어적 특징과 개발자 경험

 - 가독성 중심 문법

  • 중괄호 대신 들여쓰기를 사용합니다.
  • 불필요한 보일러플레이트를 줄여 코드량과 인지 부담을 낮춥니다.
  • 리스트 컴프리헨션, 제너레이터 표현식으로 선언적이고 간결한 데이터 처리 코드를 작성할 수 있습니다.

 

 - 동적 타이핑 + 선택적 정적 타입 힌트

  • 런타임 유연성이 높아 실험과 프로토타이핑이 빠릅니다.
  • 대규모 코드베이스에서는 타입 힌트와 정적 분석(mypy, pyright)로 안정성과 퀄리티를 보완할 수 있습니다.

 

 - 풍부한 표준 라이브러리

  • 파일·네트워크·직렬화·동시성(asyncio) 등 범용 기능을 내장합니다.
  • “배터리 포함(batteries included)” 철학으로 외부 의존성 없이도 많은 작업을 시작할 수 있습니다.

 

 - 강력한 패키지 생태계(PyPI)

  • 데이터 과학/AI: NumPy, Pandas, SciPy, scikit-learn, PyTorch, TensorFlow
  • 웹: Django, Flask, FastAPI
  • 자동화/스크립팅: Click, Typer, Rich, Fabric
  • 테스트/품질: pytest, Hypothesis, Black, Ruff
  • 배포/패키징: pip, build, uv, Poetry, Hatch

 

 - 멀티 패러다임

  • 절차적, 객체지향, 함수형 스타일을 모두 수용합니다.
  • 데코레이터, 컨텍스트 매니저, 제너레이터 등 고급 추상화 도구로 깔끔한 API 설계가 가능합니다.

 

 - 동시성 모델

  • I/O 바운드에 적합한 asyncio(코루틴)와 고수준 동시성(Concurrent Futures)을 제공합니다.
  • CPU 바운드 작업은 멀티프로세싱 또는 C 확장/네이티브 가속과 병행하는 것이 일반적입니다.

 

4.  파이썬의 강점

 - 생산성·개발 속도

  • 문법이 단순하고 도구 체인이 성숙해 MVP, PoC, 내부 자동화에 최적화되어 있습니다.
  • 풍부한 예제와 문서, 커뮤니티 Q&A로 문제 해결 시간을 단축합니다.

 

 - 데이터 과학·AI 1급 시민

  • NumPy 기반의 과학 계산, Pandas의 데이터 프레임 모델, Jupyter 노트북 워크플로는 분석-실험-시각화-보고를 하나로 묶습니다.
  • PyTorch/TensorFlow와의 긴밀한 통합으로 연구·프로덕션 양쪽을 지원합니다.

 

 - 웹 백엔드와 API

  • Django로 “올인원” 프레임워크(ORM/관리자/보안 베스트프랙티스), FastAPI로 고성능 타입 주도 API 서버 구축이 가능합니다.
  • ASGI 생태계(Uvicorn, Starlette)로 현대적 비동기 서버 구성이 용이합니다.

 

 - 자동화·DevOps·SRE

  • CLI 도구 작성이 쉽고, 크로스플랫폼 스크립팅에 강합니다.
  • 인프라 제어, 로그 처리, 보안 오케스트레이션과 같은 운영 자동화에서 표준 도구 역할을 합니다.

 

 - 교육 친화성

  • 문법 장벽이 낮아 컴퓨팅 사고, 알고리즘 교육에 적합합니다.
  • 풍부한 시각화·실습 환경(Jupyter, Colab)이 있습니다.

 

 - 확장성·상호운용성

  • Cython, Numba, PyO3(러스트), C/C++ 확장으로 병목을 네이티브로 이전해 성능을 보완할 수 있습니다.
  • FFI를 통해 레거시 라이브러리·시스템과 연결하기 쉽습니다.

 

 - 크로스플랫폼

  • 리눅스, macOS, 윈도우 전반에 동일한 코드베이스를 배치하기 용이합니다.

 

5.  파이썬의 단점과 한계

 - 실행 성능

  • 인터프리터와 GIL(Global Interpreter Lock) 구조로 인해 순수 파이썬 CPU 바운드 작업은 C/C++/Rust 대비 느립니다.
  • 고성능이 필요한 경우 벡터화(NumPy), JIT(Numba), C 확장, 멀티프로세싱, 분산 프레임워크(Ray, Dask)로 우회가 필요합니다.

 

 - 하드 실시간·초저지연 부적합

  • GC 일시중지, 인터프리터 오버헤드로 마이크로초 단위 지연 예산이 빡빡한 시스템 트레이딩, 임베디드 하드 RT에는 부적합합니다.

 

 - 모바일 네이티브/프론트엔드 약세

  • iOS/Android 네이티브 앱 주류 스택이 아니다. Kivy, BeeWare 같은 대안이 있으나 생태계 성숙도는 낮습니다.
  • 브라우저 내 직접 실행은 제한적(Pyodide, WASM 기반은 실험적·특수 목적)입니다.

 

 - 배포·런타임 관리의 복잡성

  • 가상환경, 의존성 충돌, 네이티브 바이너리 의존 패키지(과학계산 스택)의 플랫폼별 빌드 이슈가 초심자에게 어렵습니다.
  • 최근 uv, maturin, prebuilt wheels 확대로 상황은 개선되는 중이나, 팀 규모가 커질수록 정책화가 필요합니다.

 

 - 멀티스레딩 한계

  • I/O 바운드는 좋지만 CPU 바운드에서는 GIL로 스케일링 이득이 제한적입니다.
  • 프로세스 기반 병렬화 또는 네이티브 확장 설계가 필요합니다.

 

6.  어떤 문제에 잘 맞는가

  • 데이터 분석/머신러닝: 탐색적 분석, 피처 엔지니어링, 모델 연구, 배치 추론 파이프라인.
  • 웹/백엔드 API: 스타트업 MVP, 내부 서비스, 관리 도구, 데이터 플랫폼 게이트웨이.
  • 자동화·스크립팅: 로그 파이프라인, 보안 업무 자동화(IOC 수집/정규화), 테스트/빌드 파이프라인.
  • 교육/프로토타이핑: 아이디어 검증, 도메인 지식 모델링, 리서치 퀵샷.

 

 - 조건부로 적합한 영역(보완책 전제)

  • 고성능 계산: NumPy 벡터화, Numba/Cython, GPU 가속(CUDA, PyTorch/TensorRT), 분산(Polars+Ray/Dask/Spark).
  • 데스크톱 앱: Qt(PySide6/PyQt), Electron + PyBridge, BeeWare—요구사항에 따라 실현 가능하나 배포 체계 설계가 핵심.

 

 - 적합하지 않은 영역(대체 기술 고려)

  • 하드 실시간 제어, 초저지연 HFT, 커널/드라이버, 모바일 네이티브 UI, 브라우저 프론트엔드(전통적 SPA).

 

7. 파이썬 3.x 동향과 실무 팁

 - 성능 개선 흐름

  • 버전 3.11/3.12에서 바이트코드 최적화·인터프리터 내부 개선으로 전반적인 속도가 상승했습니다.
  • Numpy/Polars 등 컬럼너 기반·벡터화 엔진과 결합하면 RDBMS 수준의 분석 워크로드도 메모리 내에서 빠르게 처리할 수 있습니다.

 

 - 타입 힌트 적극 활용

  • 팀 협업에서는 타입 주석과 정적 검사(mypy/pyright), 포맷터(Black), 린터(Ruff)를 표준 파이프라인에 포함합니다.
  • FastAPI, Pydantic과 결합하면 스키마-검증-문서화를 한 번에 처리할 수 있습니다.

 

 - 의존성과 가상환경

  • 표준 venv, Poetry/Hatch/uv 중 하나를 팀 규범으로 정하고 락파일 관리로 재현성을 확보합니다.
  • 네이티브 의존 패키지는 사전에 호환 가능한 파이썬 버전/플랫폼 매트릭스를 문서화합니다.

 

 - 테스트와 품질

  • pytest를 기반으로 속성 기반 테스트(Hypothesis), 커버리지, 타입 검사, 포맷/린팅을 CI에 통합합니다.
  • 성능이 중요한 구간은 프로파일링(py-spy, cProfile, scalene)로 병목을 식별해 국소 최적화합니다.

 

 - 배포 전략

  • 서버: 컨테이너(Docker) 이미지 최적화, 멀티스테이지 빌드, slim 베이스, 런타임 OS 패키지 고정.
  • 데스크톱/CLI: PyInstaller/Briefcase/pex/uvx 등으로 단일 바이너리·앱 번들 제공.
  • 서버리스: AWS Lambda/Cloud Functions에서 콜드스타트·사이즈 제한을 고려해 경량화.

 

8. 파이썬 vs 대안 언어 간 간단 비교

  • Go: 단일 바이너리, 쉬운 배포, 우수한 동시성, 빠른 실행. 반면 데이터 과학·ML 생태계는 상대적으로 약함.
  • Rust: 메모리 안전·고성능·제로코스트 추상화. 학습 곡선이 가파르며 빠른 프로토타이핑은 불리.
  • Java/Kotlin: JVM 생태계·성숙한 엔터프라이즈 스택, 강한 타입 안정성. 초기 개발 속도·스크립팅 유연성은 파이썬이 유리.
  • JavaScript/TypeScript: 웹 프론트엔드의 표준, Node.js로 풀스택 가능. 수치·과학 계산은 파이썬 우위.
  • R: 통계·시각화 강점, 연구·분석에 적합. 범용 개발·배포·서비스화는 파이썬 우위.

 

9. 보안·포렌식 관점의 활용 포인트

  • DFIR 자동화: 아티팩트 수집, 로그 정규화·파서, IOC 매칭, 타임라인링(예: plaso, Timesketch와의 연계).
  • 메모리/파일 분석: YARA 바인딩, Volatility 플러그인, PE/ELF 파서, 문자열 추출·정규화 파이프라인.
  • 위협 헌팅: Sigma 룰 변환, Sigma→SIEM 쿼리 변환기, 샌드박스 API 오케스트레이션.
  • 대규모 로그 처리: Polars/Pandas + PyArrow + DuckDB로 스케일업 분석, 필요 시 Spark/Ray로 스케일아웃.

 

10.  결론과 실전 가이드

  • 아이디어 검증이 빠르고, 생태계가 넓으며, 운영 자동화·데이터 과학·웹 API에서 최고의 생산성을 제공한다.
  • 성능이 필요한 부분만 네이티브·분산·벡터화로 보완하는 하이브리드 접근이 가장 현실적이다.
  • 팀 차원에서는 타입 힌트, 포맷/린팅, 테스트, 프로파일링, 패키징 정책을 표준화해 재현성과 품질을 끌어올리는 것이 핵심이다.

 

 - 추천 시작 스택

  • 런타임/관리: Python 3.11+, venv 또는 uv/Poetry
  • 품질: Black, Ruff, mypy/pyright, pytest, pre-commit
  • 웹/API: FastAPI + Pydantic + Uvicorn
  • 데이터: NumPy/Pandas/Polars + PyArrow + DuckDB + Jupyter
  • 성능: Numba/Cython, PyO3(Rust), multiprocessing/Ray

11. 파이썬 2 개발 중단 경과

 - 공식 지원 종료 날짜: 2020년 1월 1일

  • 파이썬 소프트웨어 재단(PSF)이 공식적으로 파이썬 2에 대한 지원을 중단한 날짜입니다.
  • 최종 릴리즈: 파이썬 2.7.18 (2020년 4월 20일 출시)
  • 지원 종료일 이후에도 커뮤니티를 위한 서비스 차원에서 마지막 버전을 출시했습니다.
  • 2020년 1월에 코드 동결(code freeze)이 되었고, 4월에 최종 출시되었습니다.

 

 - 주요 타임라인

  • 2008년: 당초 2015년 지원 종료 예정으로 발표
  • 2010년 7월 3일: 파이썬 2.7.0 출시
  • 2014년: 지원 종료를 2020년으로 5년 연장
  • 2020년 1월 1일: 공식 지원 종료
  • 2020년 4월 20일: 파이썬 2.7.18 최종 출시로 개발 완전 중단

 

 - 파이썬 2.7.18 이후로는 보안 패치를 포함해 어떠한 버그 수정이나 개선 사항도 제공되지 않으며, 새로운 보안 취약점이 발견되더라도 공식적으로는 패치되지 않습니다.

 - 따라서 현재는 파이썬 3.x 버전으로 마이그레이션하는 것이 강력히 권장됩니다.

 

12. 파이썬 3 첫 발표 및 파이썬 2 대비 주요 개선점

 - 파이썬 3 첫 발표 날짜

  • 파이썬 3.0은 2008년 12월 3일에 첫 발표되었습니다. 
  • "파이썬 3000" 또는 "Py3k"라는 코드명으로도 불렸으며, 파이썬 2와는 하위 호환성이 없는 메이저 버전으로 설계되었습니다.

 

 - 파이썬 3의 핵심 설계 철학

  • 파이썬 3.0의 주요 설계 원칙은 "중복된 기능을 제거하여 오래된 방식을 없애는 것"이었습니다.
  • 이는 파이썬의 젠(Zen of Python) 원칙인 "명백한 방법은 하나—그리고 가급적 하나만—있어야 한다"를 구현한 것입니다.

 

 - 파이썬 2 대비 주요 개선점

  > Print 문에서 함수로 변경

  • 파이썬 2: print "Hello World" (문장 형태)
  • 파이썬 3: print("Hello World") (함수 형태)

 

   >> 이 변경으로 일관된 함수 사용법과 더 나은 확장성을 제공합니다.

 

  > 정수 나눗셈 개선

  • 파이썬 2: 7 / 2 = 3 (정수 결과)
  • 파이썬 3: 7 / 2 = 3.5 (실수 결과), 7 // 2 = 3 (정수 나눗셈)
  • 더 직관적이고 수학적으로 정확한 결과를 제공합니다.

 

  > 유니코드 지원 대폭 개선

  • 파이썬 2: 기본적으로 ASCII 인코딩, 유니코드는 별도 정의 필요
  • 파이썬 3: 기본적으로 UTF-8/유니코드 지원
  • 국제화와 다국어 텍스트 처리가 훨씬 쉬워졌습니다.

 

  > 반복자(Iterator) 최적화

  • 파이썬 2: range(), map(), filter() 등이 리스트 반환
  • 파이썬 3: 제너레이터 객체 반환으로 메모리 효율성 증대
  • xrange()가 제거되고 range()가 제너레이터 방식으로 동작.

 

  > 예외 처리 구문 개선

  • 파이썬 2: except IOError, e: (쉼표 사용)
  • 파이썬 3: except IOError as e: (as 키워드 사용)
  • 더 명확하고 일관된 예외 처리 문법을 제공합니다.

 

  > 함수형 프로그래밍 도구 재정리

  • 파이썬 2: reduce() 함수가 전역 네임스페이스에 존재
  • 파이썬 3: functools 모듈로 이동하여 더 체계적 관리

 

  > 변수 스코프 개선

  • 파이썬 2: 루프 변수가 전역 스코프에 누출(variable leakage)
  • 파이썬 3: 변수 스코프가 더 엄격하게 관리됨

 

 - 성능 및 기타 개선사항
  > 성능 향상

  • 문자열 처리, I/O 연산에서 성능 개선
  • 특히 유니코드 처리에서 메모리 효율성이 크게 향상되었습니다.

 

  > 라이브러리 생태계

  • 새로운 라이브러리들이 파이썬 3 우선으로 개발됨
  • 머신러닝, AI, 데이터 분석 등 현대적 애플리케이션에 필요한 라이브러리 지원 강화.

 

  > 보안 및 안정성

  • 더 나은 보안 기능과 안정성 제공
  • 지속적인 버그 수정과 새로운 기능 도입.

 

 - 마이그레이션 도구

  • 파이썬 개발팀은 하위 호환성 문제를 해결하기 위해 2to3 변환 도구를 제공했습니다. 
  • 이 도구는 파이썬 2 코드를 파이썬 3으로 자동 변환하는 기능을 제공하지만, 완벽한 변환은 아니므로 수동 검토와 수정이 필요합니다.

 

 - 파이썬 3의 도입은 처음에는 하위 호환성 문제로 논란이 있었지만, 현재는 파이썬 개발자의 95% 이상이 파이썬 3를 사용하고 있으며, 파이썬 2는 2020년 1월 1일부로 공식 지원이 종료되었습니다.

+ Recent posts