notepad2 컴파일 삽질기 4 : Visual C++ 6.0 SP6 컴파일

step4. Visual C++ 6.0 SP6에서 컴파일

Visual Studio .Net2003/2005/2008 등으로 Notepad2를 컴파일하면 MSVCR**.dll 파일을 필요로 합니다.
물론, Visual Studio Redistribution을 설치하면 간단히 해결되는 문제(?)이긴 하죠.

하지만, 윈도우를 설치하자마자 기본 편집기로 지정하려고 하면 상황이 좀 복잡해집니다.
또, 단독실행버전을 가볍게 만들어 갖고 다니려고 해보면 뭔가 이상해집니다.

그래서! Visual C++ 6.0에서 컴파일해봤습니다.
이렇게 컴파일하면 무엇보다, 실행파일만 달랑 있으면 모든 것이 해결된다는 점입니다.

제작자 홈페이지에 링크된 패치 자료실에 가면 이런 저런 패치 자료가 올라와있습니다.
그 중 np2-3.0.20-build_vc6.patch는 Notepad2를 Visual C++ 6.0(이하 VC6)에서 컴파일할 수 있도록 해주는 패치입니다.

이 패치는 사실 VC6용 dsp 파일을 생성해주는 역할만을 담당합니다.

그런데… 문제는 이것만으로는 정상적으로 컴파일이 되지 않습니다.
notepad2 컴파일 삽질기 1, 삽질기 2, 삽질기 3의 내용을 모두 수정해도 말입니다.

아래의 수정과정을 거쳐야 VC6에서 컴파일할 수 있습니다.


1. VC6 설치

당연히, VC6을 먼저 설치해야 합니다. 그리고, Service Pack 6도 함께 설치합니다.


2. Platform SDK 설치

VC6 만으로는 제대로 컴파일되지 않습니다.
Platform SDK를 함께 설치해야 됩니다.
Windows® Server 2003 R2 Platform SDK ISO Download에서 다운받으면 됩니다.
(Server 2008용 PSDK도 공개되었는데, VC6에서 정식으로 지원하지 않습니다. ㅡㅡ;;
공식적으로는 2003년 2월 버전까지만 지원하는데, 이 버전에서도 제대로 동작하더군요)


3. VC6용 dsp 파일 만들기

np2-3.0.20-build_vc6.patch를 다운받아 Notepad2.dsp 파일을 만듭니다.
patch에 필요한 도구는 같은 페이지에 있는 np2-3.0.20-allpatches.7z에 함께 압축되어 있습니다.





Platform SDK 2003년 2월 버전을 설치해보니 아래의 패치가 필요하지 않더군요.
아래의 패치는 VC6에 Windows® Server 2003 R2 Platform SDK를 설치한 경우에만 필요합니다.
괜히 불필요한 코드 수정에 시간을 낭비했군요. ㅡㅡ;;;


이것으로 일반적인 준비가 끝났습니다.
하지만, 이것만으로는 충분하지 않고, 소스를 조금 수정해야 됩니다.

VC6+PSDK2003R2은 이후의 버전과 달리 유니코드를 완벽하게 지원하지 않습니다.
일부 메쏘드에서는 윈도우에서 처리된 유니코드를 WCHAR로 명시적으로 변환해주지 않으면 깨져나옵니다.

사용자 삽입 이미지

뭡니까, 이게~


4. Dlapi.c 수정

int DirList_Fill()에서 아래 부분을 찾아 몇 줄을 추가합니다.

szDisplayName를 선언합니다.
파일의 확장자가 .cpp/.cxx가 아니라 .c이기 때문에 반드시 맨 앞부분에 추가해야 합니다.
WCHAR szDisplayName[MAX_PATH*10];

다음으로 아래 코드를 찾습니다.
    lvi.iImage = (dwAttributes & SFGAO_FOLDER) ?
    lpdl->iDefIconFolder : lpdl->iDefIconFile;

    ListView_InsertItem(hwnd,&lvi);


이 코드를 아래와 같이 수정합니다.
ListView_InsertItem() 메써드 위에 4줄이 추가된 것입니다.
    lvi.iImage = (dwAttributes & SFGAO_FOLDER) ?
      lpdl->iDefIconFolder : lpdl->iDefIconFile;

    SHGetPathFromIDList(pidlEntry, szDisplayName);
    lvi.pszText = szDisplayName + wcslen(szDisplayName);
    while (lvi.pszText >= szDisplayName && (*(lvi.pszText) != L'\\')) lvi.pszText--;
    if (*(lvi.pszText) == L'\\') lvi.pszText++;

    ListView_InsertItem(hwnd,&lvi);


다음으로, BOOL IL_GetDisplayName()를 찾아 수정합니다.
새로 추가된 함수를 이용하는 경우에 공백을 정상적으로 인식하지 못하는 문제를 해결하기 위한 수정입니다.
일단, 아래 부분을 찾습니다.
    // Shlwapi.dll provides new function:
    return StrRetToBuf(&str,pidl,lpszDisplayName,nDisplayName);
    // ...but I suppose my version is faster ;-)
    /*switch (str.uType)
    {

      case STRRET_WSTR:
        WideCharToMultiByte(CP_ACP,
                            0,
                            str.pOleStr,
                            -1,
                            lpszDisplayName,
                            nDisplayName,
                            NULL,
                            NULL);
        g_lpMalloc->lpVtbl->Free(g_lpMalloc,str.pOleStr);
        break;

      case STRRET_OFFSET:
        lstrcpyn(lpszDisplayName,
                 ((WCHAR *)(pidl)) + str.uOffset,
                 nDisplayName);
        break;

      case STRRET_CSTR:
        lstrcpyn(lpszDisplayName,str.cStr,nDisplayName);
        break;

    }
    return TRUE;*/



이 부분을 아래와 같이 수정합니다.
    // Shlwapi.dll provides new function:
    //return StrRetToBuf(&str,pidl,lpszDisplayName,nDisplayName);
    // ...but I suppose my version is faster ;-)
    switch (str.uType)
    {

      case STRRET_WSTR:
        WideCharToMultiByte(CP_ACP,
                            0,
                            str.pOleStr,
                            -1,
                            lpszDisplayName,
                            nDisplayName,
                            NULL,
                            NULL);
        g_lpMalloc->lpVtbl->Free(g_lpMalloc,str.pOleStr);
        break;

      case STRRET_OFFSET:
        lstrcpyn(lpszDisplayName,
                 ((WCHAR *)(pidl)) + str.uOffset,
                 nDisplayName);
        break;

      case STRRET_CSTR:
        lstrcpyn(lpszDisplayName,str.cStr,nDisplayName);
        break;

    }
    return TRUE;
보시면 알겠지만, 단지 새로운 함수를 적용하며 주석처리한 옛 코드를 다시 살릴 뿐입니다.


5. Dialogs.c 수정

step4.에서 수정한 내용만으로도 다이얼로그의 글자는 정상적으로 표시됩니다.

사용자 삽입 이미지

짜잔~

하지만, 표시가 정상적으로 될 뿐 기능을 사용할 수는 없습니다.
(아이템을 선택한 뒤 OK 버튼을 클릭해보면 정상적으로 동작하지 않습니다)

정상적으로 동작하게 하려면 아래의 수정을 거쳐야 합니다.

일단, BOOL OpenWithDlg() 메쏘드의 앞부분에서 아래 부분을 찾습니다.
  if (IDOK == DialogBoxParam(g_hInstance,MAKEINTRESOURCE(IDD_OPENWITH),
                             hwnd,OpenWithDlgProc,(LPARAM)&dliOpenWith))
  {
    SHELLEXECUTEINFO sei;
    WCHAR szParam[MAX_PATH];

    ZeroMemory(&sei,sizeof(SHELLEXECUTEINFO));
    sei.cbSize = sizeof(SHELLEXECUTEINFO);
    sei.fMask = 0;
    sei.hwnd = hwnd;
    sei.lpVerb = NULL;
    sei.lpFile = dliOpenWith.szFileName;
    sei.lpParameters = szParam;
    sei.lpDirectory = NULL;

이 부분을 아래의 코드로 바꿔줍니다. 유니코드로 받은 데이터를 명시적으로 WCHAR로 변환해주는 것입니다.
  if (IDOK == DialogBoxParam(g_hInstance,MAKEINTRESOURCE(IDD_OPENWITH),
                             hwnd,OpenWithDlgProc,(LPARAM)&dliOpenWith))
  {
    SHELLEXECUTEINFO sei;
    WCHAR szFilename[MAX_PATH];
    WCHAR szParam[MAX_PATH];

    MultiByteToWideChar(CP_ACP,
        0,
        dliOpenWith.szFileName,
        -1,
        szFilename,
        MAX_PATH);

    ZeroMemory(&sei,sizeof(SHELLEXECUTEINFO));
    sei.cbSize = sizeof(SHELLEXECUTEINFO);
    sei.fMask = 0;
    sei.hwnd = hwnd;
    sei.lpVerb = NULL;
    sei.lpFile = szFilename;
    sei.lpParameters = szParam;
    sei.lpDirectory = NULL;
    sei.nShow = SW_SHOWNORMAL;


다음으로, BOOL FavoritesDlg(HWND hwnd,LPWSTR lpstrFile) 메쏘드에서 다음 부분을 찾습니다.
  lstrcpyn(lpstrFile,dliFavorite.szFileName,MAX_PATH);

이 부분을 아래의 코드로 바꿔줍니다.
  MultiByteToWideChar(CP_ACP,
    0,
    dliFavorite.szFileName,
    -1,
    lpstrFile,
    MAX_PATH);
이 부분 역시 유니코드로 받은 데이터를 명시적으로 WCHAR로 변환해주는 것입니다.

이렇게 변환된 소스는 VC6에서 정상적으로 컴파일되며, 독립실행버전을 만들거나 윈도우를 막 포맷한 상태에서도 정상적으로 사용할 수 있습니다.
(윈도우 98 및 그 이전 버전에서는 테스트해보지도 않았고 해볼 생각도 없습니다. ^^;;;)