#winapi
#winapi
Вопрос:
Я пишу приложение на C # для удаленного управления другим приложением. У меня пока нет доступа к этому приложению, но, скорее всего, оно написано либо на Visual C , либо на VB6. В настоящее время я удаленно управляю простой программой winforms на C #, которую я создал вместо этого. Пока что я могу получить все элементы управления способом, аналогичным Spy , используя PInvoke. Я также могу выполнять такие действия, как установка текста в текстовом поле. Проблема, с которой я сталкиваюсь, заключается в том, что я не могу однозначно идентифицировать конкретное текстовое поле между запусками приложения, которым я управляю. Я не могу использовать hWnd, например, потому что он отличается при каждом запуске приложения. GetWindowLong возвращает любой hWnd, так что это не поможет. В Интернете говорится об этом сообщении под названием WM_GETCONTROLNAME. Приведенный ниже пугающий код пытается использовать это сообщение, чтобы получить имя, которое разработчик использовал для уникальной идентификации элементов управления. Похоже, что SendMessage возвращает количество байтов в имени. Но буфер, содержащий это имя, возвращает все нули.
Итак, вот вопрос. Как я могу исправить приведенный ниже код, чтобы он правильно возвращал это имя? (Надеюсь, это глупая ошибка, которую легко исправить) Или, что не менее важно, есть ли какой-то другой идентификатор, который гарантированно будет одинаковым при каждом запуске программы? Без чего-либо, что могло бы однозначно идентифицировать элементы управления текстовым полем, мой код не может их отличить.
У меня действительно есть на примете способ взлома. Мне кажется, что использование положения текстовых полей для их разделения сработало бы. Но я бы предпочел, чтобы приведенный ниже код работал.
public static string GetWindowText(IntPtr Handle)
{
int BufferSize = 256;
uint Message = RegisterWindowMessage("WM_GETCONTROLNAME");
StringBuilder sb = new StringBuilder(BufferSize);
byte[] ControlName = new byte[BufferSize];
long BytesRead = 0;
IntPtr BytesReadPointer = new IntPtr(BytesRead);
IntPtr OtherMem = IntPtr.Zero;
try
{
OtherMem = VirtualAllocEx(Handle, IntPtr.Zero, new IntPtr(sb.Capacity), AllocationType.Commit, MemoryProtection.ExecuteReadWrite);
var Result = SendMessage(Handle, Message, new IntPtr(sb.Capacity), OtherMem);
//Result contains different numbers which seem like the correct lengths
var Result2 = ReadProcessMemory(Handle, OtherMem, ControlName, BufferSize, out BytesReadPointer);
//ControlName always comes back blank.
}
finally
{
var Result3 = VirtualFreeEx(Handle, OtherMem, BufferSize, FreeType.Release);
}
return ""; // Convert ControlName to a string
}
Комментарии:
1. Почему бы не использовать автоматизацию
2. В качестве альтернативы вы можете попробовать использовать
GetDlgCtrlID()
. Работает с любым типом родительского окна, а не только для диалоговых окон.3. Используйте инструмент Inspect , чтобы проверить, реализует ли ваше целевое приложение интерфейсы автоматизации. Если это произойдет, используйте автоматизацию пользовательского интерфейса (вместо ужасающе сложного и хрупкого взлома, который вы придумали).
Ответ №1:
Итак, после хорошего ночного сна и небольшой переделки мне удалось устранить проблему самостоятельно. Возникли две проблемы. Сначала я передавал дескриптор элемента управления в VirtualAllocEx. Но в документации к этой функции говорится, что она использует дескриптор для процесса. Итак, начал предоставлять это. Затем VirtualAllocEx сообщил мне, что у меня неверный дескриптор. Итак, оказывается, что дескриптор, который вы передаете VirualAllocEx, должен иметь PROCESS_VM_OPERATION, установленный. Итак, я вызвал OpenProcess, чтобы получить этот дескриптор, затем передал его VirtualAllocEx . После этого все заработало. Мне просто нужно было преобразовать возвращаемый массив байтов в строку и обрезать кучу нулей. Это возвращает свойство Name для элементов управления. Я опубликую код здесь на случай, если кому-то еще понадобится что-то подобное.
[Flags]
public enum ProcessAccessFlags : uint
{
All = 0x001F0FFF,
Terminate = 0x00000001,
CreateThread = 0x00000002,
VirtualMemoryOperation = 0x00000008,
VirtualMemoryRead = 0x00000010,
VirtualMemoryWrite = 0x00000020,
DuplicateHandle = 0x00000040,
CreateProcess = 0x000000080,
SetQuota = 0x00000100,
SetInformation = 0x00000200,
QueryInformation = 0x00000400,
QueryLimitedInformation = 0x00001000,
Synchronize = 0x00100000
}
public static string GetWindowText(IntPtr ControlHandle, IntPtr ProcessHandle)
{
int BufferSize = 256;
StringBuilder sb = new StringBuilder(BufferSize);
byte[] ControlNameByteArray = new byte[BufferSize];
long BytesRead = 0;
IntPtr BytesReadPointer = new IntPtr(BytesRead);
IntPtr RemoteProcessMemoryAddress = IntPtr.Zero;
IntPtr RemoteProcessHandle = IntPtr.Zero;
try
{
uint Message = RegisterWindowMessage("WM_GETCONTROLNAME");
RemoteProcessHandle = OpenProcess(ProcessAccessFlags.CreateThread |
ProcessAccessFlags.QueryInformation |
ProcessAccessFlags.VirtualMemoryOperation |
ProcessAccessFlags.VirtualMemoryWrite |
ProcessAccessFlags.VirtualMemoryRead, false, Process.Id);
RemoteProcessMemoryAddress = VirtualAllocEx(RemoteProcessHandle, IntPtr.Zero, new IntPtr(sb.Capacity), AllocationType.Commit, MemoryProtection.ExecuteReadWrite);
if (RemoteProcessMemoryAddress == IntPtr.Zero)
{
throw new Exception("GetWindowText: " GetLastWin32Error());
}
SendMessage(ControlHandle, Message, new IntPtr(sb.Capacity), RemoteProcessMemoryAddress);
ReadProcessMemory(RemoteProcessHandle, RemoteProcessMemoryAddress, ControlNameByteArray, BufferSize, out BytesReadPointer);
string ControlName = Encoding.Unicode.GetString(ControlNameByteArray).TrimEnd('');
return ControlName;
}
finally
{
VirtualFreeEx(RemoteProcessHandle, RemoteProcessMemoryAddress, BufferSize, FreeType.Release);
}
}