Что не так с этим неуправляемым кодом на C #?

Я пытался получить текст из каждого элемента управления в иерархии. Следующий код работает нормально, если я использую метод unsafe. Однако использование неуправляемой версии, похоже, нарушает hWnd, в результате чего hWnd = GetAncestor(hWnd, GetAncestorFlags.GA_PARENT) жалуется:

System.AccessViolationException: 'Попытка чтения или записи в защищенную память. Это часто указывает на то, что другая память повреждена.

Я проверил, что hWnd не был изменен после возврата из функции GetWindowTextRaw, и если я закомментирую второй SendMessage в этой функции, это не вызовет проблемы (хотя он явно не получит текст окна).

(PS: я использую PInvoke.User32 в NuGet)

// using static PInvoke.User32;

public static string GetWindowTextRaw(IntPtr hWnd) {
    // Allocate correct string length first
    int length = (int)SendMessage(hWnd, WindowMessage.WM_GETTEXTLENGTH, IntPtr.Zero, IntPtr.Zero);
    char[] buff = new char[length + 1];
    IntPtr iptr = Marshal.AllocHGlobal(buff.Length);
    SendMessage(hWnd, WindowMessage.WM_GETTEXT, (IntPtr)(length + 1), iptr);
    Marshal.Copy(iptr, buff, 0, length + 1);
    Marshal.FreeHGlobal(iptr);
    //unsafe
    //{
    //    fixed (char* p = buff)
    //        SendMessage(hWnd, WindowMessage.WM_GETTEXT, (IntPtr)(length + 1), (IntPtr)p);
    //}
    return new string(buff).TrimEnd('\0');
}

private void button1_Click(object sender, EventArgs {
    POINT p;
    IntPtr hWnd;
    //while (true)
    if (GetCursorPos(out p)) {
        hWnd = WindowFromPoint(p); ;
        Debug.Print($"{p.x} {p.y} 0x{(int)hWnd:x8}");
        while (hWnd != IntPtr.Zero) {
            Debug.Print($"{GetWindowTextRaw(hWnd)}");
            hWnd = GetAncestor(hWnd, GetAncestorFlags.GA_PARENT); 
        }
        Thread.Sleep(500);
    }
}

person Tide Gu    schedule 03.05.2017    source источник
comment
@ Ðаn Как? Есть ссылки? Спасибо!   -  person Tide Gu    schedule 03.05.2017
comment
@ThePerplexedOne Спасибо за ссылку, но ничего общего не вижу. Не могли бы вы дать мне лучший намек? Интересно, что я пробовал сборку Release, и у нее нет этой проблемы. Вы знаете почему? Благодарить!   -  person Tide Gu    schedule 03.05.2017
comment
Ваш маршалинг выглядит слишком сложным и подверженным ошибкам. Немного вдохновения. В зависимости от вашего варианта использования вы также можете рассмотреть возможность использования UI Automation вместо низкоуровневого WM_GETTEXT унижение.   -  person Jeroen Mostert    schedule 03.05.2017
comment
@JeroenMostert Да, если я перегружу SendMessage, я могу передать StringBuilder в качестве параметра. Однако я пытался сохранить согласованность и использовать только PInvoke.User32, который принимает в качестве параметров только IntPtr. Если есть способ преобразовать StringBuilder в IntPtr, я был бы более чем счастлив использовать это, но переопределение его снова не кажется мне хорошей идеей (возможно, я ошибался). Я не пробовал МАУ, но это однозначно стоит прочитать! Спасибо!   -  person Tide Gu    schedule 03.05.2017
comment
@TideGu: Согласованность - это хорошо, а правильность - лучше. В PInvoke.User32 нет ничего святого. В частности, настоящая функция SendMessage. Управляемые оболочки вокруг него - это всего лишь управляемые оболочки. Имейте один, имейте тысячу, если это делает вызов неуправляемого кода безболезненным (а StringBuilder бесконечно безболезненнее, чем передача указателей). Вы даже можете объявить его как частный метод, если не хотите подвергать остальной мир этой версии.   -  person Jeroen Mostert    schedule 03.05.2017
comment
@ Ðаn Спасибо. Я всегда гуглю перед тем, как спросить, но на этот раз не могу найти ничего полезного. Вы хоть представляете, в чем проблема? Особенно это не удается при отладке, но успех при выпуске?   -  person Tide Gu    schedule 03.05.2017
comment
@JeroenMostert Спасибо, что побудил меня обернуть еще одно переопределение, и я сделаю это. Но можете ли вы помочь мне обнаружить проблемы в коде? Нет смысла терпеть неудачу на Debug, но не на Release вообще :(   -  person Tide Gu    schedule 03.05.2017
comment
@TideGu: Ханс Пассант уже объяснил, что это повреждение кучи. На самом деле очень часто можно увидеть проблемы только в отладке, а не в выпуске, или наоборот, в зависимости от того, что вы ошибаетесь, потому что макет памяти и проверки, выполняемые маршалером и самим распределителем кучи, в разных режимах различаются. . Вот почему вы обычно не хотите вообще вручную выделять буферы - слишком легко ошибиться.   -  person Jeroen Mostert    schedule 03.05.2017
comment
@JeroenMostert Спасибо!   -  person Tide Gu    schedule 03.05.2017


Ответы (1)


IntPtr iptr = Marshal.AllocHGlobal(buff.Length);

Неправильный размер, вам нужно buff.Length * sizeof(char). В два раза больше, чем сейчас. Как написано, код портит ту же самую кучу, которую использует ОС, дальше может случиться что угодно. AVE - это нормальный и счастливый результат, но не гарантированный.

person Hans Passant    schedule 03.05.2017
comment
Спасибо! Вот и проблема решена! Я сосредоточился на значении самого hWnd, но никогда не думал, что это может быть переполнение! Хуже того, у меня было мало опыта в маршалинге, и многие другие сообщения были AllocHGlobal байтами, которые не нуждаются в множителе, поэтому я не думал о размере. Большое спасибо за вашу огромную помощь! - person Tide Gu; 03.05.2017