C++のプログラムでショートカットファイルの実体を自力で解析
C++でショートカットファイル(.LNK)の実体を参照しようとすると、普通は
のようにCOM(IShellLink/IPersistFile)を使うのが普通なのでしょうが、このやり方だとLOCALSYSTEMアカウントで動作するサービスプログラムが別アカウントのデスクトップ上のファイルを指すショートカットファイルを正しく解決できない(C:\Windows\System32\config\systemprofile\Desktop\になってしまう)という問題があったので、
辺りを参考にして自力で解析するプログラムを書いてみました。
/* * GetPath from LNK file directly(without COM). */ #define bytes2short(bytes, off) ((bytes[off+1] << 8) | bytes[off]) #define bytes2int(bytes, off) ((bytes[off+3] << 24) | (bytes[off+2] << 16) | (bytes[off+1] << 8) | bytes[off]) TCHAR *GetPathFromLnk(const TCHAR *path, TCHAR *link, int size) { TCHAR *ret = NULL; HANDLE hFile = INVALID_HANDLE_VALUE; HANDLE hMap = NULL; LPBYTE buff = NULL; // map the entire file into a byte buffer hFile = CreateFile(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (INVALID_HANDLE_VALUE == hFile) goto finally; hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); if (NULL == hMap) goto finally; buff = (LPBYTE)MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0); if (NULL == buff) goto finally; // check the magic number const int shell_offset = 0x4c; if (bytes2int(buff, 0) != shell_offset) goto finally; const byte clsid[] = {0x01, 0x14, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}; if (memcmp(buff + 4, clsid, sizeof(clsid)) != 0) goto finally; // check the flags const int flags_offset = 0x14; const int has_shell_mask = 0x01; const int has_linkinfo_mask = 0x02; const int force_no_linkinfo_mask = 0x0100; int flags = bytes2int(buff, flags_offset); if (flags & force_no_linkinfo_mask) { flags &= ~has_linkinfo_mask; } if (!(flags & (has_shell_mask | has_linkinfo_mask))) goto finally; // if the shell settings are present, skip them int shell_len = 0; if (flags & has_shell_mask) { // the plus 2 accounts for the length marker itself shell_len = bytes2short(buff, shell_offset) + 2; // handle without LinkInfo(Advertise shortcut?) if (!(flags & has_linkinfo_mask)) { bool result = SHGetPathFromIDList((LPCITEMIDLIST)(buff + shell_offset + 2), link); if (result) ret = link; goto finally; } } // get to the file settings int linkinfo_start = shell_offset + shell_len; const int file_location_info_flag_offset_offset = 0x08; const int basename_offset_offset = 0x10; const int networkVolumeTable_offset_offset = 0x14; const int finalname_offset_offset = 0x18; // get the local volume and local system values int basename_offset = bytes2int(buff, linkinfo_start + basename_offset_offset) + linkinfo_start; int finalname_offset = bytes2int(buff, linkinfo_start + finalname_offset_offset) + linkinfo_start; int file_location_info_flag = bytes2int(buff, linkinfo_start + file_location_info_flag_offset_offset); const int is_local_mask = 0x01; const int is_remote_mask = 0x02; if (!(file_location_info_flag & (is_local_mask | is_remote_mask))) goto finally; if (file_location_info_flag & is_local_mask) { //XXX - should check first strncpy(link, (TCHAR *)(buff + basename_offset), size); } else { // get remote share name int networkVolumeTable_offset = bytes2int(buff, linkinfo_start + networkVolumeTable_offset_offset) + linkinfo_start; int shareName_offset_offset = 0x08; int shareName_offset = bytes2int(buff, networkVolumeTable_offset + shareName_offset_offset) + networkVolumeTable_offset; strncpy(link, (TCHAR *)(buff + shareName_offset), size); strncat(link, "\\", size); } strncat(link, (TCHAR *)(buff + finalname_offset), size); ret = link; finally: if (NULL != buff) UnmapViewOfFile(buff); if (NULL != hMap) CloseHandle(hMap); if (INVALID_HANDLE_VALUE != hFile) CloseHandle(hFile); return ret; }