Desde Vista, Microsoft tiene prevista la API GetFinalPathNameByHandle pero en WinXP no existe ninguna API con esa funcionalidad salvo este ejemplo con una técnica no funcionante en el entorno que yo necesitaba. Decidí investiguar APIs indocumentadas y conseguí mi propósito.
La función GetFilePathByHandle recibe como parámetro un Handle de archivo y devuelve un WideString con el nombre completo del archivo. Comparto con vosotros el código:
uses Windows; type NTSTATUS = DWORD; OBJECT_INFORMATION_CLASS = (ObjectNameInformation = 1); FILE_INFORMATION_CLASS = (FileNameInformation = 9); FILE_NAME_INFORMATION = record FileNameLength: ULONG; FileName: array [0..0] of WCHAR; end; PFILE_NAME_INFORMATION = ^FILE_NAME_INFORMATION; IO_STATUS_BLOCK = record Dummy: Pointer; Information: int64; end; PIO_STATUS_BLOCK = ^IO_STATUS_BLOCK; UNICODE_STRING = record Length: WORD; MaximumLength: WORD; Buffer: PWCHAR; end; PUNICODE_STRING = ^UNICODE_STRING; MOUNTMGR_TARGET_NAME = record DeviceNameLength: WORD; DeviceName: array [0..0] of WCHAR; end; PMOUNTMGR_TARGET_NAME = ^MOUNTMGR_TARGET_NAME; MOUNTMGR_VOLUME_PATHS = record MultiSzLength: ULONG; MultiSz: array [0..0] of WCHAR; end; PMOUNTMGR_VOLUME_PATHS = ^MOUNTMGR_VOLUME_PATHS; WBuffer = array [0..511] of WCHAR; const IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH = $6d0030; function NtQueryObject(Handle: THANDLE; ObjectInformationClass: OBJECT_INFORMATION_CLASS; ObjectInformation: Pointer; ObjectInformationLength: ULONG; ReturnLength: PULONG): NTSTATUS ;stdcall; external 'ntdll.dll'; function NtQueryInformationFile(Handle: THANDLE; IoStatusBlock: PIO_STATUS_BLOCK; FileInformation: Pointer; Length: ULONG; FileInformationClass: FILE_INFORMATION_CLASS): NTSTATUS; stdcall; external 'ntdll.dll'; implementation //------------------------------------------------------------------------------ function GetFilePathByHandle(hFile: THANDLE): WideString; var returnedLength: ULONG; iosb: IO_STATUS_BLOCK; PFullName, PRelName, PPathName: WBuffer; FullName: PUNICODE_STRING; RelName: PFILE_NAME_INFORMATION; PathName: PMOUNTMGR_TARGET_NAME; hMount: THANDLE; bytesReturned: DWORD; begin Result:= ''; // Preparando los buffer FullName:= PUNICODE_STRING(@PFullName[0]); RelName:= PFILE_NAME_INFORMATION(@PRelName[0]); PathName:= PMOUNTMGR_TARGET_NAME(@PPathName[0]); // Encontrando el nombre completo del archivo if 0 = NtQueryObject(hFile, ObjectNameInformation, FullName, sizeof(PFullName), @returnedLength) then begin // El nombre relativo if 0 = NtQueryInformationFile(hFile, @iosb, RelName, sizeof(PRelName), FileNameInformation) then begin if FullName.Length >= RelName.FileNameLength then begin // Aseguro que las cadenas termine en 0 FullName.Buffer[FullName.Length div sizeof(WCHAR)]:= WCHAR(0); RelName.FileName[RelName.FileNameLength div sizeof(WCHAR)]:= WCHAR(0); // Preparo PathName con el nombre del dispositivo PathName.DeviceNameLength:= WORD(FullName.Length - RelName.FileNameLength); lstrcpyW(PathName.DeviceName, FullName.Buffer); // Buscando el nombre de unidad a partir del diapositivo hMount:= CreateFile('\\.\MountPointManager', 0, FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE, nil, OPEN_EXISTING, 0, 0); try if DeviceIoControl(hMount, IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH, PathName, sizeof(WBuffer), PathName, sizeof(WBuffer), bytesReturned, nil) then begin if PMOUNTMGR_VOLUME_PATHS(PathName).MultiSzLength > 0 then begin lstrcpyW(FullName.Buffer, PMOUNTMGR_VOLUME_PATHS(PathName).MultiSz); lstrcatW(FullName.Buffer, RelName.FileName); Result:= FullName.Buffer; end; end; finally CloseHandle(hMount); end; end; end; end; end;
Un ejemplo de uso:
var hFile: THANDLE; begin hFile:= CreateFile('C:\Windows\Notepad.exe', 0, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0); if hFile <> INVALID_HANDLE_VALUE then begin Label1.Caption:= GetFilePathByHandle(hFile); CloseHandle(hFile); end; end;
Espero que le encontréis utilidad.
Saludos.