Qual técnica o Snoop usa para inspecionar um aplicativo WPF
-
21-12-2019 - |
Pergunta
Snoop, o utilitário espião, usa uma técnica poderosa (provavelmente algum tipo de reflexão) para inspecionar um aplicativo WPF em execução.O mais interessante é o fato de que o Snnop é capaz de ler toda a estrutura do objeto.
Há alguns dias baixei o código-fonte do Snoop e passei algum tempo estudando o comportamento interno.Infelizmente, ainda não consegui descobrir como o Snoop está fazendo essas coisas, então espero que alguém possa me ajudar.
No trabalho, estou escrevendo uma estrutura de teste de UI codificada e seria fantástico se eu tivesse acesso às estruturas de objetos do aplicativo, porque isso me permitiria não apenas afirmar o estado da UI.
ATUALIZAR:
Este é o código necessário:
string filePath = "WpfApp.exe";
AppDomain appDomain = AppDomain.CurrentDomain;
byte[] bytes = System.IO.File.ReadAllBytes(filePath);
Assembly ass = appDomain.Load(bytes);
ass.EntryPoint.Invoke(null, new object[] { });
IntPtr handle = Process.GetCurrentProcess().MainWindowHandle;
Window w = System.Windows.Interop.HwndSource.FromHwnd(handle).RootVisual as Window;
Isso já é uma grande ajuda para mim, mas também é interessante descobrir como o Snoop se injeta em outro processo.
Solução
Você pode realizar o que o Snoop faz usando o WPF VisualTreeHelper e/ou o LogicalTreeHelper.Depois de controlar qualquer elemento visual, você pode percorrer praticamente toda a sua árvore visual para ver todos os elementos que ele contém.Ajudante de árvore visual aqui
Portanto, em seu teste de UI, pegue a janela principal e percorra sua árvore visual para encontrar qualquer elemento desejado e, em seguida, execute quaisquer validações ou operações desejadas nesse elemento.
Além disso, você pode usar System.Diagnostics.Process.MainWindowHandle para obter o identificador do Windows de um processo existente e, em seguida, usar o identificador da janela para criar uma janela wpf.Já faz um tempo, então não me lembro dos detalhes sem fazer mais pesquisas.O código abaixo pode ajudar:
Window window = (Window)System.Windows.Interop.HwndSource.FromHwnd(process.MainWindowHandle).RootVisual;
Outras dicas
ATUALIZAR:
Ok, encontrei a localização básica do código, que é usado pelo Snoop para fornecer a capacidade de injeção.Para minha surpresa, esse código está escrito em C.Provavelmente há uma razão para isso.
E esse é o código (espero que não haja problema em publicá-lo aqui):
//-----------------------------------------------------------------------------
//Spying Process functions follow
//-----------------------------------------------------------------------------
void Injector::Launch(System::IntPtr windowHandle, System::String^ assembly, System::String^ className, System::String^ methodName)
{
System::String^ assemblyClassAndMethod = assembly + "$" + className + "$" + methodName;
pin_ptr<const wchar_t> acmLocal = PtrToStringChars(assemblyClassAndMethod);
HINSTANCE hinstDLL;
if (::GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCTSTR)&MessageHookProc, &hinstDLL))
{
LogMessage("GetModuleHandleEx successful", true);
DWORD processID = 0;
DWORD threadID = ::GetWindowThreadProcessId((HWND)windowHandle.ToPointer(), &processID);
if (processID)
{
LogMessage("Got process id", true);
HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, processID);
if (hProcess)
{
LogMessage("Got process handle", true);
int buffLen = (assemblyClassAndMethod->Length + 1) * sizeof(wchar_t);
void* acmRemote = ::VirtualAllocEx(hProcess, NULL, buffLen, MEM_COMMIT, PAGE_READWRITE);
if (acmRemote)
{
LogMessage("VirtualAllocEx successful", true);
::WriteProcessMemory(hProcess, acmRemote, acmLocal, buffLen, NULL);
_messageHookHandle = ::SetWindowsHookEx(WH_CALLWNDPROC, &MessageHookProc, hinstDLL, threadID);
if (_messageHookHandle)
{
LogMessage("SetWindowsHookEx successful", true);
::SendMessage((HWND)windowHandle.ToPointer(), WM_GOBABYGO, (WPARAM)acmRemote, 0);
::UnhookWindowsHookEx(_messageHookHandle);
}
::VirtualFreeEx(hProcess, acmRemote, 0, MEM_RELEASE);
}
::CloseHandle(hProcess);
}
}
::FreeLibrary(hinstDLL);
}
}
Snoop não inspeciona um WPF externamente.Ele se injeta no aplicativo e, na verdade, adiciona a janela de ampliação ou espionagem a ele.É também por isso que quando você sai do Snoop, as janelas de inspeção permanecem abertas.
Portanto, o código de 'inspeção' simplesmente inspeciona a janela desejada e pode usar todas as funções WPF disponíveis para fazer isso.Como VisualTreeHelper e LogicalTreeHelper conforme mencionado aqui anteriormente.
Para uma pequena estrutura de teste que construo, injetei código para adicionar um pequeno objeto proxy para que eu possa controlar o aplicativo facilmente (pressionar botões, alterar valores, executar funções em modelos de visualização, etc.).
A resposta acima não funciona para mim.Parece um pouco vago.Ampliei um pouco a resposta aceita com este código:
var allProcesses = Process.GetProcesses();
var filteredProcess = allProcesses.Where(p => p.ProcessName.Contains(ProcessSearchText)).First();
var windowHandle = filteredProcess.MainWindowHandle;
var hwndSource = HwndSource.FromHwnd(windowHandle);
Esta resposta parece mais completa e funcionará para outras pessoas se a resposta aceita funcionar para alguém.No entanto, esta última linha deste código retorna nulo para mim.