Чтение данных из SysListView32 со стилем LVS_OWNERDATA
От: Kirhog  
Дата: 19.12.13 12:52
Оценка:
Здравствуйте,

Столкнулся с проблемой, с которой не получилось разобраться: необходимо реализовать периодическое получение содержимого ячеек с компонента SysListView32, у которого выставлен стиль LVS_OWNERDATA.

Были испробованы различные способы:
1. Стандартное чтение через SendMessage(LVM_GETITEMTEXT). Данный способ работает в этой же программе, но только на компонентах без стиля LVS_OWNERDATA. В данном же случае возвращается пустой текст. В конце сообщения привел фрагмент моего кода, который использую для других компонент.
2. Detours ( http://research.microsoft.com/en-us/projects/detours/ ), EasyHook ( http://easyhook.codeplex.com/ ). Пытался отлавливать TextOut(A,W) и DrawText(A,W,Ex), но почему-то не работало.
3. .NET Automation
4. Очень много решений из интернета, ни одно из которых не работает.
(Возможно что-то еще, чего уже не помню — дело было два месяца назад)

В общем, после одной-двух недель экспериментов я сдался и сделал в лоб — получаю расположение компонента и делаю снимок экрана в данной области, потом распознаю. Но данное решение не очень стабильно, так как нет надежного способа вывести окно в Foreground, плюс есть и другие ограничения.

Я работаю в основном с Java'ой и в WinAPI оценил бы себя где-то на 3 (из 10), и есть большая вероятность, что где-то я пошел по неверному пути или просто допустил ошибку.

Поэтому прошу у вас совета, что еще можно предпринять? Было бы просто идеально, если вы уже сталкивались с такой проблемой и решили ее.

Заранее спасибо.

За рабочий код готов заплатить (как договоримся, но точно не меньше $100).

int GetListViewItemText(HWND handle, int row, int column, LPTSTR text, int size) {
    const int SIZE = 512;

    int result = 0;

    LVITEM lvi, *_lvi;
    TCHAR item[SIZE], subitem[SIZE];
    LPTSTR itemText;
    LPTSTR _subitem;
    unsigned long pid;
    HANDLE process;

    GetWindowThreadProcessId(handle, &pid);
    process = OpenProcess(
        PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, 
        FALSE, 
        pid);
    //log(string("process: ") +  to_string((llong) process));

    _lvi = (LVITEM*) VirtualAllocEx(process, NULL, sizeof(LVITEM), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
    itemText = (LPTSTR) VirtualAllocEx(process, NULL, SIZE, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
    
    memset(&lvi, 0, sizeof(LVITEM));
    memset(item, 0, SIZE);
    
    lvi.cchTextMax = SIZE;

    lvi.mask = LVIF_TEXT;
    lvi.iSubItem = column;
    lvi.pszText = itemText;
    lvi.cchTextMax = SIZE;

    SIZE_T* bytes = new SIZE_T;
    *bytes = 0;

    LRESULT res = WriteProcessMemory(process, _lvi, &lvi, sizeof(LVITEM), bytes);
    if (!res) {
        DWORD error = GetLastError();
        log(string("WriteProcessMemory error: ") + to_string((llong) error));
    }

    res = SendMessage(handle, LVM_GETITEMTEXT, (WPARAM) row, (LPARAM) _lvi);
    //log(string("chars: ") +  to_string((llong) res));
    
    res = ReadProcessMemory(process, itemText, item, SIZE, bytes);
    if (!res) {
        DWORD error = GetLastError();
        log(string("ReadProcessMemory error: ") + to_string((llong) error));
    }

    res = ReadProcessMemory(process, itemText, item, SIZE, bytes);
    if (!res) {
        DWORD error = GetLastError();
        log(string("ReadProcessMemory error: ") + to_string((llong) error));
    }
    
    //log(string("item: ") + string(item));

    // TODO: refactor
    int i;
    for (i = 0; i + 1 < size && item[i]; ++i) {
        text[i] = item[i];
    }
    text[i] = 0;

    result = wcslen(item);
    
    VirtualFreeEx(process, _lvi, 0, MEM_RELEASE);
    VirtualFreeEx(process, itemText, 0, MEM_RELEASE);
    
    CloseHandle(process);

    return result;
}
winapi syslistview32 lvs_ownerdata c++ с# .net
Re: Чтение данных из SysListView32 со стилем LVS_OWNERDATA
От: saf_e  
Дата: 19.12.13 16:00
Оценка: 1 (1)
Здравствуйте, Kirhog, Вы писали:

K>Здравствуйте,


K>Столкнулся с проблемой, с которой не получилось разобраться: необходимо реализовать периодическое получение содержимого ячеек с компонента SysListView32, у которого выставлен стиль LVS_OWNERDATA.


Раз вы столько времени занимались этой задачей, то хелп уже читали.

LVN_GETDISPINFO пробовали отсылать?
Re[2]: Чтение данных из SysListView32 со стилем LVS_OWNERDATA
От: Kirhog  
Дата: 20.12.13 02:19
Оценка:
Здравствуйте, saf_e, Вы писали:

_>Раз вы столько времени занимались этой задачей, то хелп уже читали.


_>LVN_GETDISPINFO пробовали отсылать?


Спасибо вам за совет, но снова безуспешно: с данным сообщением даже обычные (не виртуальные) таблицы не читаются.

Странно... первый раз с таким сталкиваюсь — задача выглядит не такой уж специфичной, тем не менее в сети нет ни одного рабочего решения, а только описание данной проблемы (некоторым сообщениям уже по 10 лет).
Re: Чтение данных из SysListView32 со стилем LVS_OWNERDATA
От: visual_wind  
Дата: 20.12.13 08:00
Оценка: 1 (1)
Здравствуйте, Kirhog, Вы писали:

K>Здравствуйте,


K>Столкнулся с проблемой, с которой не получилось разобраться: необходимо реализовать периодическое получение содержимого ячеек с компонента SysListView32, у которого выставлен стиль LVS_OWNERDATA.


Я думаю, что вы наверняка читали в MSDN эту статью. Для SysListView32 в сочетании с LVS_OWNERDATA список является виртуальным. Это означает, что сам SysListView32 несет ответственность только за отрисовку элементов, а ответственность за обработку содержимого несет владелец SysListView32. Это оправдано для списков большого размера, и на пользователя возлагается ответственность по повышению скорости их работы, например, с помощью кэширования.

Естественно LVM_GETITEMTEXT не работает, ибо SysListView32 отрисовал и забыл. Напротив — именно SysListView32 посылает LVN_GETDISPINFO своему владельцу и именно владелец SysListView32 должен обрабатывать у себя LVN_GETDISPINFO и снабжать SysListView32 информацией для отображения, если SysListView32 требуется свои айтемы перерисовать. Я не использовал SysListView32 из WinAPI, а из MFC, и, к сожалению, это было много лет назад. Помнится, тогда для стиля LVS_OWNERDATA я наследовался от CListView, добавлял помимо всего прочего в члены класса-потомка список отображаемых элеметов и функции для загрузки из файла — выгрузки в файл, а также обработчик LVN_GETDISPINFO, который пересылался в мой класс-потомок из родительского диалога. В итоге эти функции загрузки — выгрузки вызывались исходя из логики работы программы, а не действий пользователя, и если в результате загрузки содержимое менялось, я перерисовывал CListView. Если пользователь прокручивал список, я в обработчике LVN_GETDISPINFO снабжал CListView айтемами для отображения. Больше, увы, уже не припомню. Как вы сделаете под WinApi, уже вам виднее.
Re[3]: Чтение данных из SysListView32 со стилем LVS_OWNERDATA
От: Alexander G Украина  
Дата: 20.12.13 10:08
Оценка: 2 (1)
Здравствуйте, Kirhog, Вы писали:

K>Спасибо вам за совет, но снова безуспешно: с данным сообщением даже обычные (не виртуальные) таблицы не читаются.


Невиртуальные и не должны. Всё ли правильно делаете?
  • Сообщение WM_NOTIFY отсылается родителю SysListView32, а не ему самому, сам SysListView32 в параметрах сообщения
  • перед отправкой заполняются соответствующие поля NMLVDISPINFO
  • это сообщение нельзя слать из другого процесса
  • Русский военный корабль идёт ко дну!
    Re[4]: Чтение данных из SysListView32 со стилем LVS_OWNERDATA
    От: Kirhog  
    Дата: 20.12.13 12:58
    Оценка:
    Здравствуйте, Alexander G, Вы писали:

    AG>Невиртуальные и не должны. Всё ли правильно делаете?

    AG>
  • Сообщение WM_NOTIFY отсылается родителю SysListView32, а не ему самому, сам SysListView32 в параметрах сообщения
    AG>
  • перед отправкой заполняются соответствующие поля NMLVDISPINFO
    AG>
  • это сообщение нельзя слать из другого процесса

    Привожу код, с помощью которого я это делал.

    Первые два пункта выполнил, а вот про третий не знал (в статье про LVN_GETDISPINFO на MSDN'e про это не говорится, но наверное подразумевается). Я так понимаю, что необходимо встроить свою Dll в другой процесс и посылать это сообщение из нее?
    Спасибо за наводку, вечером попробую.

    int GetListViewItemText2(HWND handle, int row, int column, LPTSTR text, int size) {
        const int SIZE = 512;
    
        TCHAR item[SIZE];
        LPTSTR _item;
        unsigned long pid;
        HANDLE process;
        NMLVDISPINFO info, *_info;
        
        GetWindowThreadProcessId(handle, &pid);
        process = OpenProcess(
            PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, 
            FALSE, 
            pid);
        //log(string("process: ") +  to_string((llong) process));
    
        _info = (NMLVDISPINFO*) VirtualAllocEx(process, NULL, sizeof(NMLVDISPINFO), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
        _item = (LPTSTR) VirtualAllocEx(process, NULL, SIZE, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
        
        memset(&info, 0, sizeof(NMLVDISPINFO));
        memset(item, 0, SIZE);
    
        int controlId = GetDlgCtrlID(handle);
        
        info.hdr.code = LVN_GETDISPINFO;
        info.hdr.idFrom = controlId;
        info.hdr.hwndFrom = handle;
    
        info.item.iItem = row;
        info.item.mask = LVIF_TEXT;
        info.item.iSubItem = column;
        info.item.pszText = _item;
        info.item.cchTextMax = SIZE;
        
        SIZE_T* bytes = new SIZE_T;
        *bytes = 0;
    
        LRESULT res = WriteProcessMemory(process, _info, &info, sizeof(NMLVDISPINFO), bytes);
        if (!res) {
            DWORD error = GetLastError();
            log(string("WriteProcessMemory error: ") + to_string((llong) error));
        }
    
        HWND parent = (HWND) GetWindowLongPtr(handle, GWL_HWNDPARENT);
    
        res = SendMessage(parent, WM_NOTIFY, (WPARAM) controlId, (LPARAM) _info);
        
        res = ReadProcessMemory(process, _item, item, SIZE, bytes);
        if (!res) {
            DWORD error = GetLastError();
            log(string("ReadProcessMemory error: ") + to_string((llong) error));
        }
        
        // TODO: refactor
        int i;
        for (i = 0; i + 1 < size && item[i]; ++i) {
            text[i] = item[i];
        }
        text[i] = 0;
    
        int result = _tlen(item);
        
        VirtualFreeEx(process, _info, 0, MEM_RELEASE);
        VirtualFreeEx(process, _item, 0, MEM_RELEASE);
        delete bytes;
        
        CloseHandle(process);
    
        return result;
    }
  • Re[5]: Чтение данных из SysListView32 со стилем LVS_OWNERDATA
    От: Alexander G Украина  
    Дата: 20.12.13 14:10
    Оценка:
    Здравствуйте, Kirhog, Вы писали:

    K>Первые два пункта выполнил, а вот про третий не знал (в статье про LVN_GETDISPINFO на MSDN'e про это не говорится, но наверное подразумевается). Я так понимаю, что необходимо встроить свою Dll в другой процесс и посылать это сообщение из нее?


    Я бы это попробовал.

    дело в том, что про WM_NOTIFY пишут:

    For Windows 2000 and later systems, the WM_NOTIFY message cannot be sent between processes.


    Способ с выделением в чужом процессе через VirtualAllocEx и передавать эти указатели работает для сообщений, номер которых больше или равен WM_USER.

    Стандартные сообщения, которые меньше WM_USER, маршалятся самой Windows.

    Т.к. WM_NOTIFY меньше WM_USER, она в том диапазоне, который маршаллится Windows.

    У меня подозрение, что процитированный комментарий означает, что это сообщение Windows отказывается маршалить, и просто не передаёт его между процессами.
    Русский военный корабль идёт ко дну!
    Re[6]: Чтение данных из SysListView32 со стилем LVS_OWNERDATA
    От: saf_e  
    Дата: 20.12.13 14:12
    Оценка:
    Здравствуйте, Alexander G, Вы писали:

    AG>У меня подозрение, что процитированный комментарий означает, что это сообщение Windows отказывается маршалить, и просто не передаёт его между процессами.


    Что легко проверяется "на кошках"
    Re[7]: Чтение данных из SysListView32 со стилем LVS_OWNERDATA
    От: Alexander G Украина  
    Дата: 20.12.13 14:19
    Оценка:
    Здравствуйте, saf_e, Вы писали:

    AG>>У меня подозрение, что процитированный комментарий означает, что это сообщение Windows отказывается маршалить, и просто не передаёт его между процессами.


    _>Что легко проверяется "на кошках"


    У меня нет под рукой кошки достаточной длины.
    Русский военный корабль идёт ко дну!
    Re[6]: Чтение данных из SysListView32 со стилем LVS_OWNERDATA
    От: Kirhog  
    Дата: 20.12.13 17:39
    Оценка:
    Здравствуйте, Alexander G, Вы писали:

    AG>Я бы это попробовал.


    Вы знаете? Сработало, но только с тем компонентом, который не представляет для меня интереса (он тоже LVS_OWNERDATA).
    Спасибо вам! Хоть какое-то продвижение.

    Какие еще могут быть сравнительно простые способы попытаться решить эту проблему?

    Вот, кстати, что выдал API Monitor v2. Видно, что родительскому компоненту посылается сообщение на отрисовку элемента, потом он запрашивает данный элемент у таблицы, а после этого рисует содержимое ячеек на DC (это первое упоминание интересующего меня текста "192682093").
    Я пробовал перехватывать LVM_GETITEMA сообщение, но это мне ничего не дало: текст в LVITEM структуре пустой. Или в этой структуре где-то спрятан указатель на содержимое элемента; или родительский компонент хранит их у себя, а LVM_GETITEMA запрашивает по другим причинам; или ...

    Можно было бы поставить точку останова и как-то изучить содержимое памяти, но API Monitor не поддерживает для них условия, а вызовов SendMessage слишком много, чтобы добраться до нужного. Не знаете ли вы о каком-нибудь средстве, у которого была такая возможность?

    // List: 0x001a1766
    // Parent: 0x001a15e4
    // Window: 0x001a15e4
    
    SendMessageW ( 0x001a15e4, WM_DRAWITEM, 33217, 4320304 )
        CallNextHookEx ( 0x02d6198f, 0, 0, 4319580 )
        CreateCompatibleBitmap ( 0xa0014f03, 789, 21 )
        CreateCompatibleDC ( 0xa0014f03 )
        SelectObject ( 0xa70159e8, 0x5f0559c0 )
        SendMessageA ( 0x001a1766, LVM_GETITEMA, 0, 4318044 )
            CallNextHookEx ( 0x02d6198f, 0, 0, 4317448 )
            CallWindowProcA ( 0xffff1c63, 0x001a1766, LVM_GETITEMA, 0, 4318044 )
                GetWindowLongW ( 0x001a1766, DWL_MSGRESULT )
                memset ( 0x0041db58, 0, 68 )
                GetParent ( 0x001a1766 )
                GetWindow ( 0x001a1766, GW_OWNER )
                GetDlgCtrlID ( 0x001a1766 )                                 // -> 33217
                GetWindowThreadProcessId ( 0x001a15e4, 0x0041da14 )
                GetCurrentProcessId (  )
                SendMessageW ( 0x001a15e4, WM_NOTIFY, 33217, 4315988 )
                    CallNextHookEx ( 0x02d6198f, 0, 0, 4315124 )
                    GetDlgCtrlID ( 0x001a1766 )
                    GetParent ( 0x001c18d2 )
                    SendMessageA ( 0x001c18d2, WM_NOTIFY, 33217, 4315988 )
                        CallNextHookEx ( 0x02d6198f, 0, 0, 4314232 )
                        GetDlgCtrlID ( 0x001e1714 )
                        GetDlgCtrlID ( 0x001a1766 )
                        IsWindow ( 0x0014170e )
                        GetWindowLongA ( 0x0014170e, GWL_STYLE )
                        SendMessageA ( 0x554b19e2, WM_MDIGETACTIVE, 0, 0 )
                            CallNextHookEx ( 0x02d6198f, 0, 0, 4312948 )
                            CallWindowProcA ( 0x77ede5f1, 0x554b19e2, WM_MDIGETACTIVE, 4313020, 4313024 )
                        DefFrameProcA ( 0x001c18d2, 0x554b19e2, 78, 33217, 4315988 )
                MultiByteToWideChar ( CP_ACP, 0, "", -1, NULL, 0 )
                MultiByteToWideChar ( CP_ACP, MB_PRECOMPOSED, "", -1, 0x0611eb58, 1 )
                WideCharToMultiByte ( CP_ACP, 0, "", -1, 0x0041e3b8, 251, NULL, NULL )
        SendMessageA ( 0x001a1766, LVM_GETITEMRECT, 0, 4317968 )
            ...
        ...
        SendMessageA ( 0x001a1766, LVM_GETITEMCOUNT, 0, 0 )
            ...
        ...
        SelectObject ( 0xa70159e8, 0x500a4c93 )
        SetBkMode ( 0xa70159e8, TRANSPARENT )
        CopyRect ( 0x0041e320, 0x0041e5f8 )
    
        lstrlenA ( "192682093" )                                            /// <----!!!
    
        GetTextExtentPoint32A ( 0xa70159e8, "192682093", 9, 0x0041e2d8 )
        ...
        lstrcatA ( "192", "..." )
        DrawTextA ( 0xa70159e8, "192...", -1, 0x0041e320, DT_NOCLIP | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER )
    Re[2]: Чтение данных из SysListView32 со стилем LVS_OWNERDATA
    От: Kirhog  
    Дата: 20.12.13 17:45
    Оценка:
    Здравствуйте, visual_wind, Вы писали:

    _>Я думаю, что вы наверняка читали в MSDN....


    Спасибо за объяснение. Да, я читал эту статью, но, видимо, в то время до конца не разобрался.

    К сожалению, и с LVN_GETDISPINFO не сработало (вы можете видеть это из сообщения для Alexander G: http://rsdn.ru/forum/winapi/5405204.1
    Автор: Kirhog
    Дата: 20.12.13
    ). Возможно в той программе для данной таблицы реализован какой-нибудь специальный обработчик, что не получается подступиться к нему стандартными средствами.
    Re[7]: Чтение данных из SysListView32 со стилем LVS_OWNERDATA
    От: Alexander G Украина  
    Дата: 20.12.13 18:07
    Оценка:
    Здравствуйте, Kirhog, Вы писали:

    K>Здравствуйте, Alexander G, Вы писали:


    AG>>Я бы это попробовал.


    K>Вы знаете? Сработало, но только с тем компонентом, который не представляет для меня интереса (он тоже LVS_OWNERDATA).

    K>Спасибо вам! Хоть какое-то продвижение.

    K>Какие еще могут быть сравнительно простые способы попытаться решить эту проблему?


    Могу вообразить что там в интересующем owner draw или custom draw какой нибудь.

    Если он рисует в ответ на NM_CUSTOMDRAW, то в NMLVCUSTOMDRAW передаётся dc, на котором он рисует, вот передать свой DC.
    У DRAWITEMSTRUCT для WM_DRAWITEM тоже dc есть.

    Можно попробовать передать свой dc, и, если код обработчика не игнорирует этот параметр, текст будет отрисован на нём.

    Дальнейшие действия в зависимости от успеха этой акции.
    Русский военный корабль идёт ко дну!
     
    Подождите ...
    Wait...
    Пока на собственное сообщение не было ответов, его можно удалить.