윈도우 운영체제가 제공하는 파일 목록 탐색 API로는 FindFirstFile, FindNextFile가 있다.
사실, 도스 시절에도 C언어에는 내부적으로 도스 API를 사용하는 _findfirst, _findnext 같은 함수가 있었는데, 윈도우 API 역시 그 인터페이스를 거의 그대로 차용했다.
파일을 탐색하는 동작은 state가 존재하는 costly한 작업이기 때문에, 파일을 여닫는 것처럼 핸들을 주고받는 과정이 수반되며, 탐색이 끝나고 나면 그 핸들을 반드시 닫아 줘야 한다.
state가 존재하는 덕분에, 파일 탐색을 하는 도중에 다른 디렉터리에 대해 다른 파일 탐색 작업을 시작할 수도 있다. 이게 가능해야 재귀적으로 하위 디렉터리 다단계 탐색을 할 수 있을 것이다. 참고로 C 표준 함수 중 strtok 함수는, state가 존재함에도 불구하고 state 핸들값을 별도로 받지 않아서 디자인상 문제가 있는 함수라고 까였음..
본인은 운영체제가 제공하는 파일 탐색 함수의 인터페이스에 대해 다음과 같은 불만이 있다.
먼저, 파일 탐색 동작을 식별하는 핸들값 HANDLE과, 파일이 계속 존재하는지를 판단하는 BOOL값을 따로 관리해야 한다는 것이다. FindFirstFile은 HANDLE을 되돌리고, FindNextFile은 BOOL을 되돌린다. 그래서 이들을 가지고 for문이라도 만들려면 두 변수를 모두 갖고 있어야 한다. (말만으로는 실감이 잘 안 갈 테니, 관심 있으신 분은 파일 탐색 루틴을 직접 짜 보기 바란다.)
MFC의 CFileFind는 기존 API 함수를 거의 그대로 캡슐화했지만 다행히 FindFirstFile에 해당하는 FindFile 함수도 동일하게 FindNextFile과 마찬가지로 BOOL을 되돌려서 그나마 낫다.
또한 소멸자는 자동으로 FindClose를 호출해 주며, 지금 찾은 파일에 대한 정보를 별도의 GetFilePath 같은 멤버 함수를 통해 얻어 올 수 있다. 그래서 아래와 같은 형태로 loop을 작성하면 된다.
CFileFind fnd; BOOL b;
for(b=fnd.FindFile(L"*.txt"); b; b=fnd.FindNextFile())
Use(fnd.GetFilePath());
본인은 한술 더 떠서 이렇게 독자적으로 만든 클래스를 즐겨 사용한다. 생성자와 소멸자를 빼면 다들 연산자 오버로딩이다.
class CMyFileFind {
public:
CMyFileFind(PCTSTR pszFile);
~CMyFileFind();
const WIN32_FIND_DATA *operator ->() const;
operator bool() const;
void operator++(int);
};
for(CMyFileFind fnd(L"*.txt"); fnd; fnd++)
Use(fnd->cFileName);
짠~
파일 탐색을 생성자에서 바로 시작할 수 있고, WIN32_FIND_DATA에 파일 정보가 존재하는지의 여부를 bool 형변환 연산자가 바로 알려준다. 그리고 ++ 연산자가 다음 파일 탐색을 의미하며, -> 연산자를 통해 찾은 파일 정보를 곧바로 얻을 수 있다. 깔끔하지 않은가? ㄲㄲ
개인적으로, FindNextFile 함수는 더 발견된 파일이 없는 경우 주어진 찾기 핸들을 자동으로 close해 버리는 기능도 있으면 좋겠다.
파일 탐색 기능에 앞으로 되돌아가는 기능이 있는 것도 아닌데(=PrevFile 같은 거라도..;;), 더 찾을 파일이 없으면 이 핸들은 닫아 버리는 것 말고 도대체 다른 용도가 있는가? 놔 둘 이유가 전혀 없다.
이렇게 되면 파일을 찾다가 중간에 멈추는 게 아닌 이상, FindClose를 번거롭게 또 호출해야 할 필요가 없어져서 좋을 것이다.
이 찾기 핸들의 자료형은 HANDLE이다. 하지만 파일이나 스레드 같은 커널 오브젝트가 아니어서 그런지, CloseHandle이 아니라 반드시 FindClose 함수로 닫아야 한다. 그리고 실패를 의미하는 값이 NULL이 아니라, 마치 CreateFile의 실패값처럼 INVALID_HANDLE_VALUE (-1)이다. 이런 인터페이스가 뒤죽박죽인 건 윈도우 API의 디자인 결함인 것 같다. memory-mapped file을 만드는 CreateFileMapping의 실패값은 또 NULL임.. -_-;;
또한, 파일과 디렉터리를 구분 없이 찾는 것도 개인적으로 무척 불만이다.
그래서 이 탐색 결과를 담고 있는 구조체에 대해서 dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY 체크부터 꼭 해 줘야 한다.
또한, 이런 디자인으로 인해, 어떤 디렉터리 내부에서 파일은 *.txt 같은 와일드카드로 찾고 디렉터리는 와일드카드 없이 다 찾으려면 검색을 두 번 수행해야 한다. 디렉터리 이름은 언제나 전체 검색이지 이걸 와일드카드로 찾는 일은 오늘날 전혀에 가깝게 없기 때문이다. DIR *.txt /S 같은 걸 구현하는 걸 생각해 보면 쉽게 이해가 될 것이다.
와일드카드를 해석하는 작업은 보통 운영체제가 알아서 해 준다. 하지만 도스와 윈도우는 전통적으로 이 알고리즘이 굉장히 단순하기 그지없어서 * 같은 경우 문자열의 뒤에만 붙일 수 있다. A*T.*P 같은 식의 패턴을 쓸 수는 없다는 뜻.
하지만 프로그래밍 언어나 런타임의 제작사에 따라서는 파일 탐색 기능을 제공하면서 와일드카드 해석은 독자적으로 하는 경우도 있다. 가령, 파이썬은 운영체제의 와일드카드 해석 루틴을 사용하지 않으며, 도스에서 구동되던 DJGPP도 디렉터리 아예 구분자로 \ 대신 유닉스처럼 /를 쓰는 등, 파일 경로 해석 자체를 독자적으로 한다.
이상 파일 탐색 관련 잡설이었다.
파일에서 뭔가 검색, 탐색을 한다고 하면 파일 내부에 있는 특정 문자열을 검색하는 것과, 파일 목록을 추출하는 것, 그리고 열어 놓은 파일 내부에서 읽거나 쓰는 지점을 이동하는 seek가 모두 가능하다.
그리고 특정 파일에 대해서 크기나 날짜 같은 부가 정보를 얻는 기능은, 열어 놓은 파일 핸들을 상대로 수행하는 것과 파일을 열지 않고 수행하는 것이라는 두 양상으로 나뉜다는 특징이 있다.
Posted by 사무엘