"이 PC 어딘가에 있는 특정 DLL이나 설정 파일의 경로"를 찾아야 할 때가 있습니다. 보통은 Dir이나 FileSystemObject로 폴더를 재귀 탐색하는 코드를 짜는데, Windows에 이미 이 일을 해 주는 API가 있습니다. imagehlp.dll에 들어 있는 SearchTreeForFile입니다. 시작 폴더와 파일 이름만 주면 하위 폴더까지 뒤져서 처음 찾은 파일의 전체 경로를 돌려줍니다.
Private Declare Function SearchTreeForFile Lib "imagehlp" (ByVal RootPath As String, ByVal InputPathName As String, ByVal OutputPathBuffer As String) As Long
Private Const MAX_PATH = 260
Function FilePath(fDV As String, fName As String) As String
Dim Buffer As String, Ret As Long
Buffer = String(MAX_PATH, 0)
Ret = SearchTreeForFile(fDV, fName, Buffer)
If Ret <> 0 Then FilePath = LeftB$(Buffer, InStrB(1, Buffer, Chr$(0)))
End Function
사용은 이렇게 합니다. C 드라이브 전체에서 mscomctl.dll을 찾아 경로를 메시지 상자로 보여 줍니다.
MsgBox FilePath("c:\", "mscomctl.dll")
어떻게 동작하나
API에 넘기는 세 번째 인자는 결과 경로를 받아 올 빈 버퍼입니다. Windows API는 문자열을 돌려줄 때 호출하는 쪽이 미리 공간을 만들어 줘야 해서, String(MAX_PATH, 0)으로 null 문자 260개짜리 문자열을 만들어 넘깁니다. 260은 Windows의 기본 최대 경로 길이입니다.
파일을 찾으면 함수가 0이 아닌 값을 돌려주고, 버퍼 앞쪽에 전체 경로가 채워지며 그 뒤는 null 문자가 남습니다. 그래서 첫 번째 null 문자 앞까지만 잘라 내면 경로가 됩니다. 못 찾으면 0이 돌아오고 FilePath는 빈 문자열이 됩니다. 호출하는 쪽에서는 If FilePath(...) = "" Then으로 찾았는지 확인하면 됩니다.
쓸 때 알아 둘 점
이 함수는 가장 먼저 발견한 파일 하나만 알려 줍니다. 같은 이름의 파일이 여러 폴더에 있어도 목록을 주지 않으니, 전부 찾아야 하는 용도라면 직접 재귀 탐색을 짜야 합니다. 그리고 C 드라이브 전체처럼 범위를 넓게 주면 다 뒤질 때까지 프로그램이 멈춘 것처럼 보입니다. 찾을 위치를 대략 안다면 "C:\Program Files\"처럼 시작 폴더를 좁혀 주는 게 좋습니다.
결과를 자르는 부분은 LeftB$와 InStrB로 바이트 단위로 계산하고 있어서, null 문자가 차지하는 바이트 하나까지 같이 잘려 들어옵니다. 환경에 따라 결과 끝에 알아볼 수 없는 문자가 하나 붙어 보일 수 있으니, 그럴 때는 글자 단위로 자르는 쪽이 안전합니다(아래는 수정한 형태입니다).
If Ret <> 0 Then FilePath = Left$(Buffer, InStr(1, Buffer, vbNullChar) - 1)
64비트 Office에서는 Declare 문에 PtrSafe가 없으면 컴파일 오류가 납니다. 이 함수는 포인터 인자가 없어서 키워드만 붙이면 됩니다.
Private Declare PtrSafe Function SearchTreeForFile Lib "imagehlp" (ByVal RootPath As String, ByVal InputPathName As String, ByVal OutputPathBuffer As String) As Long
배포한 프로그램이 필요한 런타임 파일(예제의 mscomctl.dll 같은 OCX·DLL)이 PC에 있는지 확인하거나, 사용자가 엉뚱한 위치에 설치한 프로그램의 실행 파일을 찾아 실행할 때 쓰기 좋습니다. 찾은 경로는 레지스트리나 설정 시트에 저장해 두고 다음부터는 그 경로를 먼저 확인하도록 만들면 매번 드라이브 전체를 뒤지지 않아도 됩니다.