Как я могу однозначно идентифицировать элементы управления текстовым полем, полученные из другого приложения?

#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);
        }
    }