エクスプローラのジャンプリストは、とりわけ深い意味を持っている。サイドバーやクイックアクセスに表示される項目はエクスプローラーのジャンプリストの項目であり、クイックアクセスにアイテムをピン留めすることはジャンプリストのアイテムをピン留めすることと全く同じである。
ただ、内部実装は Windows Vista のジャンプリストの出現から今日の Windows 11 まで様々な実装は進化してきた。
windows.storage.dllAutomatic Destinations の保存層、parser、COM 実装windows.storage.dllCustom Destinations の COM 実装StartTileData.dllJump List 全体を構築する internal WinRT surfaceJumpViewUI.dllその internal WinRT を呼んで UI に落とし込むクライアント
この記事では、実 DLL の逆解析結果を元に、Jump List が内部でどの API を使って列挙・構築・表示されるのかを整理する。
TL;DR
公開 API だけを見ると、Jump List は ICustomDestinationList や IApplicationDocumentLists、あるいは UWP 系の Windows.UI.StartScreen.JumpList が使われているのかと考えるが、これらは全く使われていない。
内部では、より直接的な非公開 COM / WinRT API が使われている。先に行っておくと、これらの API はレジストリに登録されているため、私たちでも使うことができる。
もともとは、ジャンプリストの UI 自体は Windows 7 の頃は explorer.exe に実装されており、WinRT API もなく、直接 shell32.dll にあった IAutomaticDestinationList 系や IInternalCustomDestinationList を使っていた。今はより使い勝手のいい WinRT API が仲介し、それでも今でも COM API を低レベルで使っている。
つまり、WinRT が COM を完全に置き換えたわけではなく、WinRT は上位のオブジェクトモデルと broker を提供し、実データの取得は下の COM 層へ橋渡ししている。
Automatic Destinations と Custom Destinations は別物
Jump List を理解するうえでまず重要なのは、Automatic Destinations と Custom Destinations を分けることだ。
- Automatic Destinations
システム管理の
Recent/Frequent/Pinned - Custom Destinations アプリが自前でカテゴリを組んで保存する独自項目群
- Tasks Custom Destinations 側の特別なカテゴリ
内部実装も分かれている。
StartTileData.dll の JumpListCategoryKind は次の値を使っていた。
[flags]
enum JumpListCategoryKind : uint32_t
{
Pinned = 0x1,
Recent = 0x2,
Frequent = 0x4,
Tasks = 0x8,
Custom = 0x10,
};automatic 側 selector への変換も見えている。
Pinned(1) -> 0Recent(2) -> 1Frequent(4) -> 2
windows.storage.dll: Automatic Destinations の本体
Automatic Destinations の COM 実装本体は windows.storage.dll にある CAutomaticDestinationList だ。
実装している interface は次の通り。
IAutomaticDestinationListIAutomaticDestinationList2IAutomaticDestinationList3IAutomaticDestinationList4IAutomaticDestinationListPrivateIAutomaticDestinationListPropertyStore
このビルドでは 3 と 4 に追加 slot は見えず、ABI は 2 と同じだった。つまり実質的な拡張は IAutomaticDestinationListPrivate と IAutomaticDestinationListPropertyStore に存在する。
IAutomaticDestinationListPrivate は内部メンテナンス用 API を持つ。
SetDebounceSetInitializationSetUpgradeSetSuspendRibbonUpdatesGetDecayThresholdGetListSizeGetItemDataGetItemNameClearCacheSetMigrationOrderStreamGetItemTarget
IAutomaticDestinationListPropertyStore はアイテムやリストの IPropertyStore を読む層だ。
GetPropertyStorageForItemSetPropertyStorageForItemGetPropertyForItemSetPropertyForItemGetPropertyStorageForListSetPropertyStorageForListGetPropertyForListSetPropertyForList
ここから分かるのは、Automatic Destinations は単なる「最近使ったパスの配列」ではなく、IPropertyStore と ピン留め情報を持つかなり厚いデータモデルだということだ。
windows.storage.dll には Automatic Destinations 用 WinRT API もある
面白いのは、windows.storage.dll に internal WinRT も存在することだ。
公開 API ではなく Windows.Internal.* 名前空間だが、Automatic Destinations を WinRT ABI で扱うクラスがある。
見えているのはこれ。
Windows.Internal.AutomaticDestinationListStorageWindows.Internal.AutomaticDestinationListItemInfo
概念的な定義はこう見える。
namespace Windows.Internal
{
enum DestinationListAccess
{
ReadOnly = 0,
ReadWrite = 1,
};
runtimeclass AutomaticDestinationListItemInfo
{
Double UsagePoints;
DateTime LastUsed;
UInt32 ActionCount;
Int32 PinPosition;
ValueSet ExtendedProperties;
};
runtimeclass AutomaticDestinationListStorage
{
UInt32 ItemCount { get; }
UInt32 PinnedItemCount { get; }
IStorageItem GetItemAtIndex(UInt32 index);
AutomaticDestinationListItemInfo GetInfoAtIndex(UInt32 index);
void Load(DestinationListAccess access, String exePath, String appId, String customDestFilePath);
void Save();
void Close();
};
}ここで重要なのは、画像はこの層では返さない という点だ。
返るのは IStorageItem と metadata であって、アイコンバイト列ではない。
つまり icon を得たいなら、この storage 層ではなく、より上の Jump List 表示モデルか、別の shell image API を使う必要がある。
windows.storage.dll: Custom Destinations の本体
Custom Destinations は別の COM クラス CDestinationList で実装されている。こちらは Automatic Destinations とは別系統だ。
主な interface は以下。
ICustomDestinationList{6332DEBF-87B5-4670-90C0-5E57B408A49E}IInternalCustomDestinationList{507101CD-F6AD-46C8-8E20-EEB9E6BAC47F}
CLSID はこれ。
CLSID_DestinationList{77F10CF0-3DB5-4966-B520-B7C54FD35ED6}
IInternalCustomDestinationList は公開 ICustomDestinationList に対して、既存 list の読み出し・編集を足した内部 interface だ。
interface IInternalCustomDestinationList : IUnknown
{
HRESULT SetMinItems(UINT minItems);
HRESULT SetApplicationID(PCWSTR appId);
HRESULT GetSlotCount(UINT* slots);
HRESULT GetCategoryCount(UINT* count);
HRESULT GetCategory(UINT index, GETCATFLAG flags, APPDESTCATEGORY* outCategory);
HRESULT DeleteCategory(UINT index, int mode);
HRESULT EnumerateCategoryDestinations(UINT index, REFIID riid, void** ppv);
HRESULT RemoveDestination(IUnknown* item);
HRESULT ResolveDestination(HWND hwnd, UINT resolveFlags, IShellItem* item, REFIID riid, void** ppv);
HRESULT HasListEx(BOOL* hasAnyItems, int* hasTaskCategory);
HRESULT ClearRemovedDestinations();
};category type は実装上こう見えている。
0 = custom named category1 = known category2 = tasks
KNOWNDESTCATEGORY はこのビルドでは 1, 2, -1 が許容されていた。
1 = Frequent2 = Recent-1 = internal sentinel の可能性が高い
ここでのポイントは、Custom Destinations には WinRT runtime class が見えていない ことだ。
automatic は internal WinRT を持っているが、custom は COM が本体のまま残っている。
StartTileData.dll: Jump List の WinRT API
Jump List 全体のオブジェクトモデルは StartTileData.dll にある。ここが今回いちばん重要な層だ。
見えているランタイムクラスは次の通り。
WindowsInternal.Shell.JumpList.JumpListWindowsInternal.Shell.JumpList.JumpListCategoryWindowsInternal.Shell.JumpList.JumpListItemWindowsInternal.Shell.JumpList.JumpListTileWindowsInternal.Shell.JumpList.JumpListEnumerationContextWindowsInternal.Shell.JumpList.JumpListItemActivationContextWindowsInternal.Shell.JumpList.JumpListUpdatesAvailableEventArgsWindowsInternal.Shell.JumpList.Broker.JumpListBroker
概念的にはこういうモデルになっている。
ここで初めて、UI に必要な「category」「item」「tile」「logo」「activation」がまとまってくる。
JumpList は direct activation ではない
WindowsInternal.Shell.JumpList.JumpList は RoActivateInstance で直接 new する型ではない。
statics/factory 経由で使う。
statics IID はこれ。
IJumpListStatics{7758FE16-E384-453C-80D9-C07F90879C2C}
入口は EnumerateAsync(...) だ。
IAsyncOperation<JumpList> EnumerateAsync(
IUnifiedTileIdentifier tile,
hstring arg,
JumpListEnumerationContext context);概念的な呼び方はこうなる。
auto factory = RoGetActivationFactory(
L"WindowsInternal.Shell.JumpList.JumpList",
IID_IJumpListStatics);
auto op = factory->EnumerateAsync(tile, arg, context);ただしこれは公開 WinRT ではない。
さらに下で broker の caller check が入るので、普通のアプリからそのまま使える API とは見なせない。
StartTileData.dll は下の COM 実装をラップしている
StartTileData.dll は上位の WinRT object model を提供しているが、データ取得自体は下の COM 層を使う。
automatic 側では:
CreateAutomaticDestinationListIAutomaticDestinationList2
custom/tasks 側では:
CreateCustomDestinationListIInternalCustomDestinationList
つまり中の流れはこうだ。
逆に言うと、Jump List の上位 WinRT surface を見ていても、その下では今でも classic shell/COM 文化が生きている。
broker が caller を制限している
内部実装で特に重要なのが broker だ。
JumpListBroker::EnumerateAsync 側には access check がある。
見えている分岐は次の 2 系統。
FullTrustStartMenucapability を持つクライアントCallerIdentity::EnsureCallingProcessIsShellExperience()
このため、WindowsInternal.Shell.JumpList.* は「型定義さえ分かれば誰でも使える hidden WinRT API」ではない。
実際には Windows 内部クライアント向けの面であり、broker で gate される。
この構造は、internal WinRT に見せつつも、実行主体を shell 系プロセスに限定したいという設計に見える。
jumpviewui.dll は実際に何を呼んでいるのか
Jump List UI クライアントである jumpviewui.dll は、StartTileData.dll の lower-level broker ではなく、上位 JumpList statics を呼んでいる。
直接の呼び出し元は:
JumpViewUI::JumpListViewModel::InitializeAsync
流れはこうだった。
JumpListSessionから値を取るJumpListEnumerationContextを作るcontext.TargetAppId = session.TargetAppIdRoGetActivationFactory("WindowsInternal.Shell.JumpList.JumpList", IID_IJumpListStatics, ...)IJumpListStatics::EnumerateAsync(session.UnifiedTileId, session.TargetAppPath, context)IAsyncOperation<JumpList>を task 化- 完了後
InitializeList(...) UpdatesAvailableを購読UpdateContents(...)で ViewModel へ展開
図にするとこうなる。
ここで重要なのは、2 番目の string 引数が単なる曖昧な opaque arg ではなく、少なくとも jumpviewui.dll の caller では TargetAppPath である点だ。
JumpListSession は tile と app identity をまとめる
JumpListSession は Jump List query に必要な文脈を持つオブジェクトだった。
逆解析上の主要フィールドはこう見える。
UserUnifiedTileIdTargetAppIdTargetAppPathJumpViewSurfaceJumpViewInvocationMethod
さらに TargetAppId は単純な app id ではなく、UnifiedTileId から composite 化される。
- packaged tile:
AppUserModelId - secondary tile:
AppUserModelId:TileId - win32 tile:
win32 側の
AppUserModelId
これは、Jump List が単純な exe path ベースではなく、tile identity ベースでルーティングされていることを示している。
アイコンはどこから来るのか
automatic destination storage 層だけを見ていると、アイコンの出所が分かりにくい。
理由は単純で、windows.storage.dll の storage WinRT は画像を返さないからだ。
画像やロゴを扱うのは上位の JumpListItem / JumpListTile 側だ。
JumpListItem.LogoJumpListItem.LoadImageAsync(...)JumpListTile.LogoJumpListTile.LoadImageAsync(...)
つまり icon の取得は「automatic destination parser」の責務ではなく、「Jump List 表示モデル」の責務に寄っている。
内部で最終的にどの image pipeline を踏むかは別途さらに追えるが、少なくともレイヤ分割としてはそこが明確だ。
設計として見ると何が面白いのか
この構造で面白いのは、Windows が Jump List を完全に WinRT に作り替えたわけではないことだ。
実際には:
- 永続化と列挙は古い shell/COM モデルが強い
- その上に internal WinRT object model を載せている
- UI クライアントは internal WinRT を使う
- broker で caller を制限している
つまりアーキテクチャとしては「COM を捨てた」のではなく、「COM の上に internal WinRT façade を作った」と見るのが正確だ。
これは Windows 内部実装ではかなり自然な形で、既存 shell infrastructure を生かしつつ、新しい UI 側では WinRT 的な非同期オブジェクトモデルを使える。
まとめ
Jump List の内部実装をざっくりまとめるとこうなる。
- Automatic Destinations の本体は
windows.storage.dllのCAutomaticDestinationList - Custom Destinations の本体は
windows.storage.dllのCDestinationList - automatic 側には
Windows.Internal.AutomaticDestinationListStorageという internal WinRT もある - ただし Jump List 全体の object model は
StartTileData.dllのWindowsInternal.Shell.JumpList.* - UI クライアント
jumpviewui.dllはIJumpListStatics::EnumerateAsync(...)を使ってJumpListを取得する - その下では依然として
IAutomaticDestinationList2とIInternalCustomDestinationListが使われている - この internal WinRT は broker で gate されており、一般公開 API として使う想定ではない
結局のところ、Jump List は「WinRT で書き直された shell 機能」ではなく、
古い destination list 基盤の上に internal WinRT surface を被せ、UI からはそれを使うようにした多層構造 だと言える。