Win32 File-ID APIs

Windows Vista/2008からは、File IDによるファイル処理が標準で実装されている。これは、NTFSとかでGetFileInformationByHandleを使って取得できるBY_HANDLE_FILE_INFORMATION::nFileIndex*と同じもの。ファイルIDがわかっていれば、IDとドライブ識別のためのハンドルだけでファイルが開けるし、当然、ファイルIDからファイル名が取得できる。
そして、このAPIは、Windows XP/2003でも、Win32 FileID API Libraryでダウンロードできるfileextd.libをリンクすれば利用できる。
なので、このAPIはXP以降のOSならば使えると考えてよい。以下は、適当に書いたサンプル。コマンドラインの第1引数に与えられたファイルをCreateFileで開いた後に、ハンドルから元のファイル名を復元してみる。バッファのサイズは本来なら、ちゃんと確保しないといけないと思われるが手を抜いている。

興味深いのは、同時にファイルハンドルからファイル名を取得するAPIも提供されていること。今まで、これはやりたくてもなかなかできなかった。この機能を使うには、最初にkernel32からエクスポートされているGetFileInformationByHandleEx関数を取得してみて、それが取得できたら(Vistaなら取得できる)そっちを使って、そうでなければ、fileextd.libによって提供されている実装を利用する。でも、本当は、常にfileextd.libの実装を使うようにしても今のところは動く。また、fileextd.hは、Windows SDKの定義と衝突するので、あえてインクルードしない。

#include <windows.h>
//#include <fileextd.h>
#include <stdio.h>
#include <locale.h>

typedef BOOL (WINAPI *GetFileInformationByHandleExFunc)(
  HANDLE, FILE_INFO_BY_HANDLE_CLASS, LPVOID, DWORD);
  
int wmain(int argc, wchar_t* argv[])
{
  setlocale(LC_ALL, "");
  
  HANDLE hFile = CreateFileW(
    argv[1], GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
  
  GetFileInformationByHandleExFunc gfibhx
    = (GetFileInformationByHandleExFunc)GetProcAddress(
      GetModuleHandle("kernel32"), "GetFileInformationByHandleEx");
  if(!gfibhx)
    gfibhx = GetFileInformationByHandleEx;
  
  char buf[2048];
  if(gfibhx(hFile, FileNameInfo, buf, sizeof(buf)))
  {
    FILE_NAME_INFO& info = *(FILE_NAME_INFO*)buf;
    info.FileName[info.FileNameLength / 2] = 0;
    wprintf(L"%s\n", info.FileName);
  }
  else
  {
    printf("err=%u\n", GetLastError());
  }
  
  const DWORD PATHSIZE = 2047;
  WCHAR path[PATHSIZE + 1];
  DWORD ret = GetFinalPathNameByHandleW(hFile, path, PATHSIZE, FILE_NAME_NORMALIZED);
  if(ret > PATHSIZE || ret == 0)
  {
    printf("err=%u\n", GetLastError());
  }
  else
  {
    wprintf(L"%s\n", path);
  }
  
  CloseHandle(hFile);
  return 0;
}

動かしてみた結果。

C:\Users\dummy\Desktop\test>test test.exe
\Users\dummy\Desktop\test\test.exe
\\?\C:\Users\dummy\Desktop\test\test.exe

取得されるファイル名にはドライブレターがないが、GetFinalPathNameByHandleだと、普通にドライブレター込みのパスを取得できる。これはどうやってドライブレターを取得すればいいのだろうか・・・。