Shell IDList Arrayについて
クリップボードフォーマットにおいて、Shell IDList Arrayを用いた通信について記事が少ないので一言。
CIDAのフォーマットの定義は以下の通りだ。
typedef struct _IDA { UINT cidl; // IDLISTの数 UINT aoffset[1]; // IDLIST構造体のリスト } CIDA, * LPIDA;
実際には可変長構造体でaoffsetは0~cidlの序数を持つ。定義ではそうは読み取れないが、Win32apiやDLL周りを扱う場合には頻繁に出てくる表現だ。
ここで、aoffsetの個数がcidl+1個なのは間違いではない。
aoffset[0]は、フォルダーのIDLISTへのポインター、aoffset[1]~aoffset[cidl]は、フォルダー内でドラッグアンドドロップされているファイルのIDLISTへのポインターだ。
前者は絶対参照、後者は相対参照なので、ファイルのIDLISTを使ってSHGetPathFromIDListを呼び出しても正しいパスは得られない。
ファイルを得るためのコードを以下に示す。
// Shell IDList Arrayのクリップボードフォーマットがあるか確認 CLIPFORMAT get_ShellIDListArray_ID( IDataObject *pDataObj ) { TCHAR szFormatName[256]; unsigned int nSize = sizeof(szFormatName) / sizeof(TCHAR); FORMATETC formatetc; IEnumFORMATETC *pEnumFromatetc; pDataObj->EnumFormatEtc( DATADIR_GET, &pEnumFromatetc ); while( S_OK == pEnumFromatetc->Next( 1, &formatetc, NULL ) ) { ::GetClipboardFormatName( formatetc.cfFormat, szFormatName, nSize ); if( 0 == ::lstrcmp( szFormatName, CFSTR_SHELLIDLIST ) ) break; formatetc.cfFormat = 0; } pEnumFromatetc->Release(); return formatetc.cfFormat; } // ポインター操作用補助関数 BYTE* byte_cast( LPIDA pIda, UINT p ) { return reinterpret_cast<BYTE*>(pIda)+pIda->aoffset[p]; } // IDropTargetの実装として定義したTDropTargetのメンバー関数 STDMETHODIMP TDropTarget::Drop(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { FORMATETC formatetc; formatetc.cfFormat = get_ShellIDListArray_ID( pDataObj ); formatetc.ptd = NULL; formatetc.dwAspect = DVASPECT_CONTENT; formatetc.lindex = -1; formatetc.tymed = TYMED_HGLOBAL; if( 0 == fotmatetc.cfFormat ) return E_FAIL; // クリップボードデータの取り込み STGMEDIUM medium; if( S_OK != pDataObj->GetData( &formatetc, &medium ) ) return E_FAIL; if( medium.tymed != TYMED_HGLOBAL ) { ::ReleaseStgMedium( &medium ); return E_FAIL; } // 内部データのメモリー展開 CIDA *pIda = static_cast<CIDA*>(::GlobalLock( medium.hGlobal )); // フォルダーIDLISTへのポインター PIDLIST_ABSOLUTE pidl_root = reinterpret_cast<PIDLIST_ABSOLUTE>( byte_cast( pIda, 0 ) ); // ファイルのリストアップ for( unsigned int i = 1; i <= pIda->cidl; ++i ) // 各ファイルの序数は1~cidl { // ファイルへのIDLISTへのポインター PIDLIST_RELATIVE pidl_file = reinterpret_cast<PIDLIST_RELATIVE>( byte_cast( pIda, i ) ); // ファイルへのフルパスのIDLISTを構成 PIDLIST_ABSOLUTE pidl = ::ILCombine( pidl_root, pidl_file ); // ファイル名を得る TCHAR szSrc[MAX_PATH]; ::SHGetPathFromIDList( pidl, szSrc ); // ・・・なにがしかの処理 // ファイルフルパスのIDLISTのメモリーを解放 ::ILFree( pidl ); } // メモリー展開を解除 ::GlobalUnlock(medium.hGlobal); // クリップボード構造体の解放 ::ReleaseStgMedium(&medium); }
ILCombineを用いて、絶対パスのPIDLIST_ABSOLUTEを得る。使用を終えたら解放を忘れずに。
あと、SHGetPathFromIDListではUWPプログラムへのパスを得られない。Microsoft Edgeのパスは得られないなど、UWPのプログラムへのパスを得るとか大変面倒だ。