584 lines
18 KiB
C
584 lines
18 KiB
C
/*
|
|
* Process Hacker Extra Plugins -
|
|
* Wait Chain Traversal (WCT) Plugin
|
|
*
|
|
* Copyright (C) 2014 dmex
|
|
*
|
|
* This file is part of Process Hacker.
|
|
*
|
|
* Process Hacker is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Process Hacker is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Process Hacker. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "main.h"
|
|
|
|
BOOLEAN NTAPI WepWindowTreeNewCallback(
|
|
_In_ HWND hwnd,
|
|
_In_ PH_TREENEW_MESSAGE Message,
|
|
_In_opt_ PVOID Parameter1,
|
|
_In_opt_ PVOID Parameter2,
|
|
_In_opt_ PVOID Context
|
|
);
|
|
|
|
BOOLEAN WepWindowNodeHashtableEqualFunction(
|
|
_In_ PVOID Entry1,
|
|
_In_ PVOID Entry2
|
|
)
|
|
{
|
|
PWCT_ROOT_NODE windowNode1 = *(PWCT_ROOT_NODE *)Entry1;
|
|
PWCT_ROOT_NODE windowNode2 = *(PWCT_ROOT_NODE *)Entry2;
|
|
|
|
return windowNode1->Node.Index == windowNode2->Node.Index;
|
|
}
|
|
|
|
ULONG WepWindowNodeHashtableHashFunction(
|
|
_In_ PVOID Entry
|
|
)
|
|
{
|
|
return (*(PWCT_ROOT_NODE*)Entry)->Node.Index;
|
|
}
|
|
|
|
VOID WepDestroyWindowNode(
|
|
_In_ PWCT_ROOT_NODE WindowNode
|
|
)
|
|
{
|
|
PhDereferenceObject(WindowNode->Children);
|
|
|
|
if (WindowNode->TimeoutString)
|
|
PhDereferenceObject(WindowNode->TimeoutString);
|
|
|
|
if (WindowNode->ProcessIdString)
|
|
PhDereferenceObject(WindowNode->ProcessIdString);
|
|
|
|
if (WindowNode->ThreadIdString)
|
|
PhDereferenceObject(WindowNode->ThreadIdString);
|
|
|
|
if (WindowNode->WaitTimeString)
|
|
PhDereferenceObject(WindowNode->WaitTimeString);
|
|
|
|
if (WindowNode->ContextSwitchesString)
|
|
PhDereferenceObject(WindowNode->ContextSwitchesString);
|
|
|
|
if (WindowNode->ObjectNameString)
|
|
PhDereferenceObject(WindowNode->ObjectNameString);
|
|
|
|
PhFree(WindowNode);
|
|
}
|
|
|
|
VOID WtcInitializeWindowTree(
|
|
_In_ HWND ParentWindowHandle,
|
|
_In_ HWND TreeNewHandle,
|
|
_Out_ PWCT_TREE_CONTEXT Context
|
|
)
|
|
{
|
|
HWND hwnd;
|
|
PPH_STRING settings;
|
|
|
|
memset(Context, 0, sizeof(WCT_TREE_CONTEXT));
|
|
|
|
Context->NodeHashtable = PhCreateHashtable(
|
|
sizeof(PWCT_ROOT_NODE),
|
|
WepWindowNodeHashtableEqualFunction,
|
|
WepWindowNodeHashtableHashFunction,
|
|
100
|
|
);
|
|
Context->NodeList = PhCreateList(100);
|
|
Context->NodeRootList = PhCreateList(30);
|
|
|
|
Context->ParentWindowHandle = ParentWindowHandle;
|
|
Context->TreeNewHandle = TreeNewHandle;
|
|
hwnd = TreeNewHandle;
|
|
PhSetControlTheme(hwnd, L"explorer");
|
|
|
|
TreeNew_SetCallback(hwnd, WepWindowTreeNewCallback, Context);
|
|
|
|
PhAddTreeNewColumn(hwnd, TREE_COLUMN_ITEM_TYPE, TRUE, L"Type", 80, PH_ALIGN_LEFT, 0, 0);
|
|
PhAddTreeNewColumn(hwnd, TREE_COLUMN_ITEM_THREADID, TRUE, L"ThreadId", 50, PH_ALIGN_LEFT, 1, 0);
|
|
PhAddTreeNewColumn(hwnd, TREE_COLUMN_ITEM_PROCESSID, TRUE, L"ProcessId", 50, PH_ALIGN_LEFT, 2, 0);
|
|
PhAddTreeNewColumn(hwnd, TREE_COLUMN_ITEM_STATUS, TRUE, L"Status", 80, PH_ALIGN_LEFT, 3, 0);
|
|
PhAddTreeNewColumn(hwnd, TREE_COLUMN_ITEM_CONTEXTSWITCH, TRUE, L"Context Switches", 70, PH_ALIGN_LEFT, 4, 0);
|
|
PhAddTreeNewColumn(hwnd, TREE_COLUMN_ITEM_WAITTIME, TRUE, L"WaitTime", 60, PH_ALIGN_LEFT, 5, 0);
|
|
PhAddTreeNewColumn(hwnd, TREE_COLUMN_ITEM_TIMEOUT, TRUE, L"Timeout", 60, PH_ALIGN_LEFT, 6, 0);
|
|
PhAddTreeNewColumn(hwnd, TREE_COLUMN_ITEM_ALERTABLE, TRUE, L"Alertable", 50, PH_ALIGN_LEFT, 7, 0);
|
|
PhAddTreeNewColumn(hwnd, TREE_COLUMN_ITEM_NAME, TRUE, L"Name", 100, PH_ALIGN_LEFT, 8, 0);
|
|
|
|
TreeNew_SetTriState(hwnd, TRUE);
|
|
TreeNew_SetSort(hwnd, 0, NoSortOrder);
|
|
|
|
settings = PhGetStringSetting(SETTING_NAME_TREE_LIST_COLUMNS);
|
|
PhCmLoadSettings(hwnd, &settings->sr);
|
|
PhDereferenceObject(settings);
|
|
}
|
|
|
|
VOID WtcDeleteWindowTree(
|
|
_In_ PWCT_TREE_CONTEXT Context
|
|
)
|
|
{
|
|
PPH_STRING settings;
|
|
|
|
settings = PhCmSaveSettings(Context->TreeNewHandle);
|
|
PhSetStringSetting2(SETTING_NAME_TREE_LIST_COLUMNS, &settings->sr);
|
|
PhDereferenceObject(settings);
|
|
|
|
for (ULONG i = 0; i < Context->NodeList->Count; i++)
|
|
{
|
|
WepDestroyWindowNode(Context->NodeList->Items[i]);
|
|
}
|
|
|
|
PhDereferenceObject(Context->NodeHashtable);
|
|
PhDereferenceObject(Context->NodeList);
|
|
PhDereferenceObject(Context->NodeRootList);
|
|
}
|
|
|
|
PWCT_ROOT_NODE WeAddWindowNode(
|
|
_Inout_ PWCT_TREE_CONTEXT Context
|
|
)
|
|
{
|
|
PWCT_ROOT_NODE windowNode;
|
|
|
|
windowNode = PhAllocate(sizeof(WCT_ROOT_NODE));
|
|
memset(windowNode, 0, sizeof(WCT_ROOT_NODE));
|
|
PhInitializeTreeNewNode(&windowNode->Node);
|
|
|
|
memset(windowNode->TextCache, 0, sizeof(PH_STRINGREF) * TREE_COLUMN_ITEM_MAXIMUM);
|
|
windowNode->Node.TextCache = windowNode->TextCache;
|
|
windowNode->Node.TextCacheSize = TREE_COLUMN_ITEM_MAXIMUM;
|
|
|
|
windowNode->Children = PhCreateList(1);
|
|
|
|
PhAddEntryHashtable(Context->NodeHashtable, &windowNode);
|
|
PhAddItemList(Context->NodeList, windowNode);
|
|
|
|
//TreeNew_NodesStructured(Context->TreeNewHandle);
|
|
|
|
return windowNode;
|
|
}
|
|
|
|
VOID WctAddChildWindowNode(
|
|
_In_ PWCT_TREE_CONTEXT Context,
|
|
_In_opt_ PWCT_ROOT_NODE ParentNode,
|
|
_In_ PWAITCHAIN_NODE_INFO WctNode,
|
|
_In_ BOOLEAN IsDeadLocked
|
|
)
|
|
{
|
|
PWCT_ROOT_NODE childNode = NULL;
|
|
|
|
childNode = WeAddWindowNode(Context);
|
|
|
|
childNode->IsDeadLocked = TRUE;
|
|
childNode->ObjectType = WctNode->ObjectType;
|
|
childNode->ObjectStatus = WctNode->ObjectStatus;
|
|
childNode->Alertable = WctNode->LockObject.Alertable;
|
|
childNode->ThreadId = UlongToHandle(WctNode->ThreadObject.ThreadId);
|
|
childNode->ProcessIdString = PhFormatString(L"%lu", WctNode->ThreadObject.ProcessId);
|
|
childNode->ThreadIdString = PhFormatString(L"%lu", WctNode->ThreadObject.ThreadId);
|
|
childNode->WaitTimeString = PhFormatString(L"%lu", WctNode->ThreadObject.WaitTime);
|
|
childNode->ContextSwitchesString = PhFormatString(L"%lu", WctNode->ThreadObject.ContextSwitches);
|
|
|
|
if (WctNode->LockObject.ObjectName[0] != L'\0')
|
|
childNode->ObjectNameString = PhFormatString(L"%s", WctNode->LockObject.ObjectName);
|
|
|
|
if (WctNode->LockObject.Timeout.QuadPart > 0)
|
|
{
|
|
SYSTEMTIME systemTime;
|
|
PPH_STRING dateString = NULL;
|
|
PPH_STRING timeString = NULL;
|
|
|
|
PhLargeIntegerToLocalSystemTime(&systemTime, &WctNode->LockObject.Timeout);
|
|
|
|
dateString = PhFormatDate(&systemTime, NULL);
|
|
timeString = PhFormatTime(&systemTime, NULL);
|
|
|
|
childNode->TimeoutString = PhFormatString(L"%s %s", dateString->Buffer, timeString->Buffer);
|
|
|
|
PhDereferenceObject(dateString);
|
|
PhDereferenceObject(timeString);
|
|
}
|
|
|
|
if (ParentNode)
|
|
{
|
|
childNode->HasChildren = FALSE;
|
|
|
|
// This is a child node.
|
|
childNode->Parent = ParentNode;
|
|
PhAddItemList(ParentNode->Children, childNode);
|
|
}
|
|
else
|
|
{
|
|
childNode->HasChildren = TRUE;
|
|
childNode->Node.Expanded = TRUE;
|
|
|
|
// This is a root node.
|
|
PhAddItemList(Context->NodeRootList, childNode);
|
|
}
|
|
}
|
|
|
|
PWCT_ROOT_NODE WeFindWindowNode(
|
|
_In_ PWCT_TREE_CONTEXT Context,
|
|
_In_ HWND WindowHandle
|
|
)
|
|
{
|
|
WCT_ROOT_NODE lookupWindowNode;
|
|
PWCT_ROOT_NODE lookupWindowNodePtr = &lookupWindowNode;
|
|
PWCT_ROOT_NODE *windowNode;
|
|
|
|
lookupWindowNode.Node.Index = HandleToUlong(WindowHandle);
|
|
|
|
windowNode = (PWCT_ROOT_NODE*)PhFindEntryHashtable(
|
|
Context->NodeHashtable,
|
|
&lookupWindowNodePtr
|
|
);
|
|
|
|
if (windowNode)
|
|
return *windowNode;
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
VOID WeRemoveWindowNode(
|
|
_In_ PWCT_TREE_CONTEXT Context,
|
|
_In_ PWCT_ROOT_NODE WindowNode
|
|
)
|
|
{
|
|
ULONG index = 0;
|
|
|
|
// Remove from hashtable/list and cleanup.
|
|
PhRemoveEntryHashtable(Context->NodeHashtable, &WindowNode);
|
|
|
|
if ((index = PhFindItemList(Context->NodeList, WindowNode)) != -1)
|
|
{
|
|
PhRemoveItemList(Context->NodeList, index);
|
|
}
|
|
|
|
WepDestroyWindowNode(WindowNode);
|
|
|
|
TreeNew_NodesStructured(Context->TreeNewHandle);
|
|
}
|
|
|
|
|
|
|
|
BOOLEAN NTAPI WepWindowTreeNewCallback(
|
|
_In_ HWND hwnd,
|
|
_In_ PH_TREENEW_MESSAGE Message,
|
|
_In_opt_ PVOID Parameter1,
|
|
_In_opt_ PVOID Parameter2,
|
|
_In_opt_ PVOID Context
|
|
)
|
|
{
|
|
PWCT_TREE_CONTEXT context;
|
|
PWCT_ROOT_NODE node;
|
|
|
|
context = Context;
|
|
|
|
switch (Message)
|
|
{
|
|
case TreeNewGetChildren:
|
|
{
|
|
PPH_TREENEW_GET_CHILDREN getChildren = Parameter1;
|
|
|
|
node = (PWCT_ROOT_NODE)getChildren->Node;
|
|
|
|
if (context->TreeNewSortOrder == NoSortOrder)
|
|
{
|
|
if (!node)
|
|
{
|
|
getChildren->Children = (PPH_TREENEW_NODE *)context->NodeRootList->Items;
|
|
getChildren->NumberOfChildren = context->NodeRootList->Count;
|
|
}
|
|
else
|
|
{
|
|
getChildren->Children = (PPH_TREENEW_NODE *)node->Children->Items;
|
|
getChildren->NumberOfChildren = node->Children->Count;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!node)
|
|
{
|
|
getChildren->Children = (PPH_TREENEW_NODE *)context->NodeList->Items;
|
|
getChildren->NumberOfChildren = context->NodeList->Count;
|
|
}
|
|
}
|
|
}
|
|
return TRUE;
|
|
case TreeNewIsLeaf:
|
|
{
|
|
PPH_TREENEW_IS_LEAF isLeaf = (PPH_TREENEW_IS_LEAF)Parameter1;
|
|
|
|
node = (PWCT_ROOT_NODE)isLeaf->Node;
|
|
|
|
if (context->TreeNewSortOrder == NoSortOrder)
|
|
isLeaf->IsLeaf = !node->HasChildren;
|
|
else
|
|
isLeaf->IsLeaf = TRUE;
|
|
}
|
|
return TRUE;
|
|
case TreeNewGetCellText:
|
|
{
|
|
PPH_TREENEW_GET_CELL_TEXT getCellText = (PPH_TREENEW_GET_CELL_TEXT)Parameter1;
|
|
|
|
node = (PWCT_ROOT_NODE)getCellText->Node;
|
|
|
|
switch (getCellText->Id)
|
|
{
|
|
case TREE_COLUMN_ITEM_TYPE:
|
|
{
|
|
switch (node->ObjectType)
|
|
{
|
|
case WctCriticalSectionType:
|
|
PhInitializeStringRef(&getCellText->Text, L"CriticalSection");
|
|
break;
|
|
case WctSendMessageType:
|
|
PhInitializeStringRef(&getCellText->Text, L"SendMessage");
|
|
break;
|
|
case WctMutexType:
|
|
PhInitializeStringRef(&getCellText->Text, L"Mutex");
|
|
break;
|
|
case WctAlpcType:
|
|
PhInitializeStringRef(&getCellText->Text, L"Alpc");
|
|
break;
|
|
case WctComType:
|
|
PhInitializeStringRef(&getCellText->Text, L"Com");
|
|
break;
|
|
case WctComActivationType:
|
|
PhInitializeStringRef(&getCellText->Text, L"ComActivation");
|
|
break;
|
|
case WctProcessWaitType:
|
|
PhInitializeStringRef(&getCellText->Text, L"ProcWait");
|
|
break;
|
|
case WctThreadType:
|
|
PhInitializeStringRef(&getCellText->Text, L"Thread");
|
|
break;
|
|
case WctThreadWaitType:
|
|
PhInitializeStringRef(&getCellText->Text, L"ThreadWait");
|
|
break;
|
|
case WctSocketIoType:
|
|
PhInitializeStringRef(&getCellText->Text, L"Socket I/O");
|
|
break;
|
|
case WctSmbIoType:
|
|
PhInitializeStringRef(&getCellText->Text, L"SMB I/O");
|
|
break;
|
|
case WctUnknownType:
|
|
case WctMaxType:
|
|
default:
|
|
PhInitializeStringRef(&getCellText->Text, L"Unknown");
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case TREE_COLUMN_ITEM_STATUS:
|
|
{
|
|
switch (node->ObjectStatus)
|
|
{
|
|
case WctStatusNoAccess:
|
|
PhInitializeStringRef(&getCellText->Text, L"No Access");
|
|
break;
|
|
case WctStatusRunning:
|
|
PhInitializeStringRef(&getCellText->Text, L"Running");
|
|
break;
|
|
case WctStatusBlocked:
|
|
PhInitializeStringRef(&getCellText->Text, L"Blocked");
|
|
break;
|
|
case WctStatusPidOnly:
|
|
PhInitializeStringRef(&getCellText->Text, L"Pid Only");
|
|
break;
|
|
case WctStatusPidOnlyRpcss:
|
|
PhInitializeStringRef(&getCellText->Text, L"Pid Only (Rpcss)");
|
|
break;
|
|
case WctStatusOwned:
|
|
PhInitializeStringRef(&getCellText->Text, L"Owned");
|
|
break;
|
|
case WctStatusNotOwned:
|
|
PhInitializeStringRef(&getCellText->Text, L"Not Owned");
|
|
break;
|
|
case WctStatusAbandoned:
|
|
PhInitializeStringRef(&getCellText->Text, L"Abandoned");
|
|
break;
|
|
case WctStatusError:
|
|
PhInitializeStringRef(&getCellText->Text, L"Error");
|
|
break;
|
|
case WctStatusUnknown:
|
|
case WctStatusMax:
|
|
default:
|
|
PhInitializeStringRef(&getCellText->Text, L"Unknown");
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case TREE_COLUMN_ITEM_NAME:
|
|
getCellText->Text = PhGetStringRef(node->ObjectNameString);
|
|
break;
|
|
case TREE_COLUMN_ITEM_TIMEOUT:
|
|
getCellText->Text = PhGetStringRef(node->TimeoutString);
|
|
break;
|
|
case TREE_COLUMN_ITEM_ALERTABLE:
|
|
{
|
|
if (node->Alertable)
|
|
{
|
|
PhInitializeStringRef(&getCellText->Text, L"true");
|
|
}
|
|
else
|
|
{
|
|
PhInitializeStringRef(&getCellText->Text, L"false");
|
|
}
|
|
}
|
|
break;
|
|
case TREE_COLUMN_ITEM_PROCESSID:
|
|
{
|
|
if (node->ObjectType == WctThreadType)
|
|
{
|
|
getCellText->Text = PhGetStringRef(node->ProcessIdString);
|
|
}
|
|
}
|
|
break;
|
|
case TREE_COLUMN_ITEM_THREADID:
|
|
{
|
|
if (node->ObjectType == WctThreadType)
|
|
{
|
|
getCellText->Text = PhGetStringRef(node->ThreadIdString);
|
|
}
|
|
}
|
|
break;
|
|
case TREE_COLUMN_ITEM_WAITTIME:
|
|
{
|
|
if (node->ObjectType == WctThreadType)
|
|
{
|
|
getCellText->Text = PhGetStringRef(node->WaitTimeString);
|
|
}
|
|
}
|
|
break;
|
|
case TREE_COLUMN_ITEM_CONTEXTSWITCH:
|
|
{
|
|
if (node->ObjectType == WctThreadType)
|
|
{
|
|
getCellText->Text = PhGetStringRef(node->ContextSwitchesString);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
getCellText->Flags = TN_CACHE;
|
|
}
|
|
return TRUE;
|
|
case TreeNewGetNodeColor:
|
|
{
|
|
PPH_TREENEW_GET_NODE_COLOR getNodeColor = (PPH_TREENEW_GET_NODE_COLOR)Parameter1;
|
|
node = (PWCT_ROOT_NODE)getNodeColor->Node;
|
|
|
|
if (node->IsDeadLocked)
|
|
{
|
|
getNodeColor->ForeColor = RGB(255, 0, 0);
|
|
}
|
|
|
|
getNodeColor->Flags = TN_CACHE;
|
|
}
|
|
return TRUE;
|
|
case TreeNewSortChanged:
|
|
{
|
|
TreeNew_GetSort(hwnd, &context->TreeNewSortColumn, &context->TreeNewSortOrder);
|
|
// Force a rebuild to sort the items.
|
|
TreeNew_NodesStructured(hwnd);
|
|
}
|
|
return TRUE;
|
|
case TreeNewKeyDown:
|
|
case TreeNewLeftDoubleClick:
|
|
case TreeNewNodeExpanding:
|
|
return TRUE;
|
|
case TreeNewContextMenu:
|
|
{
|
|
PPH_TREENEW_MOUSE_EVENT mouseEvent = (PPH_TREENEW_MOUSE_EVENT)Parameter1;
|
|
|
|
SendMessage(context->ParentWindowHandle, WM_COMMAND, ID_WCTSHOWCONTEXTMENU, MAKELONG(mouseEvent->Location.x, mouseEvent->Location.y));
|
|
}
|
|
return TRUE;
|
|
case TreeNewHeaderRightClick:
|
|
{
|
|
PH_TN_COLUMN_MENU_DATA data;
|
|
|
|
data.TreeNewHandle = hwnd;
|
|
data.MouseEvent = Parameter1;
|
|
data.DefaultSortColumn = 0;
|
|
data.DefaultSortOrder = AscendingSortOrder;
|
|
PhInitializeTreeNewColumnMenu(&data);
|
|
|
|
data.Selection = PhShowEMenu(data.Menu, hwnd, PH_EMENU_SHOW_LEFTRIGHT,
|
|
PH_ALIGN_LEFT | PH_ALIGN_TOP, data.MouseEvent->ScreenLocation.x, data.MouseEvent->ScreenLocation.y);
|
|
PhHandleTreeNewColumnMenu(&data);
|
|
PhDeleteTreeNewColumnMenu(&data);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
VOID WeClearWindowTree(
|
|
_In_ PWCT_TREE_CONTEXT Context
|
|
)
|
|
{
|
|
ULONG i;
|
|
|
|
for (i = 0; i < Context->NodeList->Count; i++)
|
|
WepDestroyWindowNode(Context->NodeList->Items[i]);
|
|
|
|
PhClearHashtable(Context->NodeHashtable);
|
|
PhClearList(Context->NodeList);
|
|
PhClearList(Context->NodeRootList);
|
|
}
|
|
|
|
PWCT_ROOT_NODE WeGetSelectedWindowNode(
|
|
_In_ PWCT_TREE_CONTEXT Context
|
|
)
|
|
{
|
|
PWCT_ROOT_NODE windowNode = NULL;
|
|
ULONG i;
|
|
|
|
for (i = 0; i < Context->NodeList->Count; i++)
|
|
{
|
|
windowNode = Context->NodeList->Items[i];
|
|
|
|
if (windowNode->Node.Selected)
|
|
return windowNode;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
VOID WeGetSelectedWindowNodes(
|
|
_In_ PWCT_TREE_CONTEXT Context,
|
|
_Out_ PWCT_ROOT_NODE **Windows,
|
|
_Out_ PULONG NumberOfWindows
|
|
)
|
|
{
|
|
PPH_LIST list;
|
|
ULONG i;
|
|
|
|
list = PhCreateList(2);
|
|
|
|
for (i = 0; i < Context->NodeList->Count; i++)
|
|
{
|
|
PWCT_ROOT_NODE node = (PWCT_ROOT_NODE)Context->NodeList->Items[i];
|
|
|
|
if (node->Node.Selected)
|
|
{
|
|
PhAddItemList(list, node);
|
|
}
|
|
}
|
|
|
|
*Windows = PhAllocateCopy(list->Items, sizeof(PVOID) * list->Count);
|
|
*NumberOfWindows = list->Count;
|
|
|
|
PhDereferenceObject(list);
|
|
} |