새로 받은 프로그램을 더블 클릭했더니 "시스템 오류"라는 제목의 창이 뜬다. "VCRUNTIME140.dll이(가) 없어 코드 실행을 진행할 수 없습니다. 프로그램을 다시 설치하면 이 문제가 해결될 수 있습니다." 다시 설치해도 똑같다. DLL 이름을 그대로 검색하면 그 파일을 "무료로 받으라"는 사이트가 먼저 나온다. 거기서 멈추는 게 맞다.

이 창은 "DLL 을 찾지 못했다"는 뜻이다

이 창의 문장은 NTSTATUS 0xc0000135(STATUS_DLL_NOT_FOUND)의 메시지다. ntdll 의 메시지 표에서 0xc0000135 를 꺼내면 "%hs이(가) 없어 코드 실행을 진행할 수 없습니다"가 나오고, %hs 자리에 찾지 못한 DLL 이름이 들어간다. Win32 오류 번호로는 126(ERROR_MOD_NOT_FOUND)이다.

같은 창을 직접 띄워 보았다. %TEMP% 아래 실험용 폴더에 where.exe 사본을 만들고, 그 사본이 불러오는 VERSION.dll 이라는 이름을 Windows 에 없는 VERSIOX.dll 로 한 글자 바꿨다. 실행하자 "wherex.exe - 시스템 오류" 창에 "VERSIOX.dll이(가) 없어 코드 실행을 진행할 수 없습니다"가 떴고, 종료 코드는 0xc0000135 였다. VCRUNTIME140.dll 창도 이것과 같은 구조다. 프로그램이 시작할 때 묶여 있는 DLL 을 프로그램 폴더, System32 같은 검색 위치에서 끝내 찾지 못한 것이다.

VCRUNTIME140·MSVCP140 은 Visual C++ 재배포 패키지의 파일

C++ 로 만든 프로그램은 대부분 Microsoft 의 C++ 런타임을 쓴다. 메모리 할당, 예외 처리 같은 기본 기능은 vcruntime140.dll, C++ 표준 라이브러리는 msvcp140.dll 에 들어 있다. 이 파일들은 Windows 에 처음부터 들어 있는 것이 아니라 "Microsoft Visual C++ 재배포 가능 패키지"를 설치해야 생긴다.

이름의 140 은 버전 14.0 을 뜻한다. Visual C++ 2015, 2017, 2019, 2022 는 런타임 버전이 모두 14.x 로 이어져서 하나의 패키지로 합쳐졌다. Microsoft 문서는 최신 14.x 패키지가 그 이전 14.x 로 만든 프로그램도 지원한다고 설명하니, 2015 용 프로그램에 최신 패키지를 깔아도 된다. 반대로 2013(msvcr120.dll), 2010(msvcr100.dll) 같은 이전 세대는 파일 이름부터 달라서 따로 깔아야 한다.

이 PC 에서 이 런타임 파일을 확인해 보니 이렇게 들어 있었다.

파일 System32 (64비트) SysWOW64 (32비트)
vcruntime140.dll 14.51.36247.0, 174KB 14.51.36247.0, 120KB
vcruntime140_1.dll 14.51.36247.0, 49KB 없음
msvcp140.dll 14.51.36247.0, 628KB 14.51.36247.0, 604KB
msvcp140_1.dll, _2.dll 14.51.36247.0 14.51.36247.0

vcruntime140_1.dll 이 64비트 쪽에만 있는 건 정상이다. 64비트 프로그램의 새 예외 처리 방식에 쓰이는 파일이라 32비트용이 따로 없는 것으로 알려져 있다. 창에 VCRUNTIME140_1.dll 이 없다고 나오면 64비트 패키지가 오래됐거나 빠진 경우다.

설치된 버전은 레지스트리에서 확인한다

"설정 > 앱 > 설치된 앱"에서 "Visual C++"를 검색해도 되지만 이름이 버전마다 달라 헷갈린다. 이 PC 에서는 "Microsoft Visual C++ v14 Redistributable (x64) - 14.51.36247" 로 보였고, 예전 글의 "2015-2022" 라는 이름이 아니었다. 이름보다 버전 번호 14.x 를 보는 편이 확실하다.

재배포 패키지는 설치할 때 레지스트리에 버전을 적어 두는 것으로 알려져 있다. 읽기만 하는 명령이라 일반 권한으로 실행해도 된다.

'HKLM:\SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64',
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\VisualStudio\14.0\VC\Runtimes\x86' |
    ForEach-Object {
        if (Test-Path $_) { Get-ItemProperty $_ | Select-Object Installed, Version }
        else { "$_ 없음" }
    }

이 PC 에서는 두 키 모두 Installed 가 1, Version 이 v14.51.36247.00 이었다. x86 키가 WOW6432Node 아래에 있는 점에 주의한다. 32비트 쪽 레지스트리는 이 아래로 돌려지기 때문에, 이 PC 에서도 WOW6432Node 를 뺀 x86 경로는 없다고 나왔다.

프로그램 제거 목록(Uninstall 키)으로 전체를 보려면 이렇게 한다.

'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' |
    ForEach-Object { Get-ItemProperty $_ -ErrorAction SilentlyContinue } |
    Where-Object DisplayName -match 'Visual C\+\+' |
    Sort-Object DisplayName | Select-Object DisplayName, DisplayVersion

이 PC 에는 2008, 2010, 2013 패키지와 14.x 패키지가 함께 있었다. 세대마다 파일 이름이 달라 서로를 대신하지 못하니, 오래된 것도 지우지 않는 편이 좋다.

x86 과 x64 를 둘 다 깔아야 하는 이유

64비트 Windows 라서 x64 만 깔면 될 것 같지만, 앞 편에서 본 것처럼 32비트 프로그램은 32비트 DLL 만 불러올 수 있다. 32비트 프로그램은 SysWOW64 의 vcruntime140.dll 을 찾고, x64 패키지는 거기에 아무것도 넣지 않는다. x64 패키지만 있는 PC 에서 32비트 프로그램을 실행하면 VCRUNTIME140.dll 이 "없다"는 창이 뜬다. 64비트 쪽에는 분명히 있는데도 그렇다.

이 PC 의 Program Files 두 폴더에서 실행 파일의 가져오기 목록을 읽어 보니, VCRUNTIME140 이나 MSVCP140 을 쓰는 실행 파일이 554개였다. 64비트가 424개, 32비트가 121개, 나머지는 ARM 용이었다. 32비트가 생각보다 많다. 그중 385개는 자기 폴더에 vcruntime140.dll 을 직접 넣어 두고 있었고, 160개는 Windows 에 설치된 패키지에 기대고 있었다. 이 160개 가운데 32비트 36개는 x86 패키지가 없으면 바로 이 창을 띄울 프로그램들이다.

그래서 해결은 간단하다. Microsoft Learn 의 "지원되는 최신 Visual C++ 재배포 가능 패키지 다운로드" 문서에서 x64 와 x86 설치 파일을 둘 다 받아 설치한다. 이미 깔려 있는데 창이 뜬다면 "설치된 앱"에서 해당 패키지의 "수정"을 눌러 복구하거나, 같은 설치 파일을 다시 실행해 "복구"를 고른다. 설치에는 관리자 권한이 필요하다. 받은 설치 파일은 실행 전에 서명을 확인해 두면 안심이 된다.

Get-AuthenticodeSignature "$env:USERPROFILE\Downloads\vc_redist.x64.exe" |
    Select-Object Status, @{n='Signer'; e={ $_.SignerCertificate.Subject }}

Status 가 Valid 이고 서명자가 Microsoft Corporation 이면 된다.

DLL 다운로드 사이트에서 받지 말 것

검색 결과 위쪽의 DLL 다운로드 사이트는 파일 하나만 내려받아 System32 에 넣으라고 안내한다. 이 방법을 피해야 하는 이유는 세 가지다.

첫째, 출처를 확인할 수 없다. 인기 있는 DLL 이름은 악성코드를 끼워 넣기 좋은 포장이고, 프로그램이 시작하자마자 자기 안에 올리는 파일이라 피해가 바로 이어진다. 둘째, 비트와 버전이 맞는다는 보장이 없다. 32비트 vcruntime140.dll 을 System32 에 넣으면 앞 편의 0xc000007b 로 문제가 바뀌고, 같은 DLL 을 쓰는 다른 64비트 프로그램까지 시작하지 못한다. 셋째, 한 파일로 끝나지 않는다. vcruntime140.dll 을 넣고 나면 msvcp140.dll, 그다음엔 vcruntime140_1.dll 이 없다는 창이 차례로 뜨기 쉽다. 패키지는 이 파일들을 같은 버전으로 한꺼번에 넣고 복구할 수 있게 기록도 남긴다.

이미 받아서 System32 에 넣었다면 직접 지우기보다 정식 패키지로 복구 설치를 하는 편이 안전하다. 관리자 권한의 sfc /scannow 는 Windows 구성 요소 파일을 점검하지만, 재배포 패키지 파일은 대상이 아니라서 고쳐 주지 않는 것으로 보인다. 결국 정식 설치 파일로 x86, x64 를 모두 다시 까는 것이 가장 짧은 길이다.

다음 편은 숫자 모양이 조금 다른 0x80070005 "액세스가 거부되었습니다"다. 0x8007 뒤에 붙은 숫자로 Win32 오류 번호를 읽어 내는 법을 보면, 다른 0x8007xxxx 오류도 같은 방법으로 풀 수 있다.