Новости:

Форум на данный момент в стадии обновления. Если у Вас возникли проблемы со входом в свою учетную запись - просьба писать на email: info@excel-vba.ru

Главное меню

Время срабатывания API

Автор McConst, 08.07.2014, 16:16:38

« назад - далее »

McConst

Здравствуйте. Ниже у меня имеется код, который с помощью API функции ReadProcessMemory читает из памяти  сначала указатель на текстовую строку, затем этой же функцией эту строку извлекает. По большому счету код работает. Есть только один нюанс, который мне не понятен, и часто встречается при работе с API. Если идут два вызова функции подряд (как у меня), то первый вызов работает без проблем, второй - сразу отказывается выполняться корректно и требуется некоторое время (специально ставлю задержку в коде перед вторым вызовом), чтобы функция отработала правильно. Без задержки функция вместо текста возвращает из памяти нули. В режиме пошаговой отладки всё вообще работает замечательно. Почему для ReadProcessMemory нужна задержка? Там же кроме считывания ничего не происходит. Причем одной задержки в виде DoEvents явно не достаточно. Нужно делать цикл. Время задержки приходится подбирать эмпирически. Вот с этим эмпирическим подбором времени и проблемы. Хочется сделать код максимально быстрым и задержку минимальной. Как организовать проверку, что функция начала считывать данные корректно? И ещё вопрос. Допустим я получил в переменную адрес, по которому находится текст в памяти открытого стороннего процесса. Фактически указатель. Можно ли в бейсике как-то прочитать текст сразу через указатель, а не организовывать вызов API? Интуиция подсказывает, что для стороннего процесса нельзя, но спросить надо.

'Модуль работы с оперативной памятью

'Пример работы с памятью есть здесь
'server2009.ucoz.ru/chtivo/Info2.htm#29 - имеются ошибки в объявлении ReadProcessMemory и переменной в закрытии процесса
'www.vbnet.ru/forum/show.aspx?id=211693 - в конце темы работающий пример

Option Explicit


'Блок объявления констант модуля
Private Const SolutionNameLength As Long = 32 'Константа длины имени раствора, считываемого из оперативной памяти

'------------------------------------------------------------------------------------------------------------------
'Блок объявления API функций и их констант
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Const WM_SETTEXT = &HC
Const WM_LBUTTONDOWN = &H201
Const WM_LBUTTONUP = &H202
Const WM_KEYDOWN = &H100

Const WM_KEYUP = &H101
Const WM_SETFOCUS = &H7
Const WM_KILLFOCUS = &H8
Const WM_GETTEXT = &HD
Const WM_GETTEXTLENGTH = &HE

Const VK_ENTER = &HD
Const VK_DOWN = &H28
Const VK_UP = &H26
Const VK_LEFT = &H25
Const VK_RIGHT = &H27
Const VK_F5 = &H74

Const EM_GETHANDLE As Long = &HBD

'API функции для доступа к оперативной памяти стороннего процесса
Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hwnd As Long, lpdwProcessId As Long) As Long

'Функция открытия процесса и её константы
Private Declare Function OpenProcess Lib "Kernel32.dll" (ByVal dwDesiredAccessas As Long, ByVal bInheritHandle As Long, ByVal dwProcId As Long) As Long
'Private Const PROCESS_ALL_ACCESS = &HF0000 Or &H100000 Or &HFFF
Private Const PROCESS_VM_READ = &H10
Private Const PROCESS_VM_WRITE = &H20
Private Const PROCESS_VM_OPERATION = &H8
Private Const PROCESS_VM = PROCESS_VM_OPERATION Or PROCESS_VM_READ Or PROCESS_VM_WRITE

Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Declare Function ReadProcessMemory Lib "kernel32" _
   (ByVal hProcess As Long, ByVal lpBaseAddress As Long, ByRef lpBuffer As Byte, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long
'----------------------------------------------------------------------------------------------------------

Public Sub ReadMemory(hwnd As Long)
'Процедура чтения данных из стороннего объекта с заданным описателем

Dim Pid As Long ' Переменная для идентификатора процесса
Dim pHandle As Long ' Описатель процесса
Dim bt() As Byte 'массив для считывания данных
Dim Addr As Long ' Переменная адреса для работы с памятью
Dim i As Long ' Счетчик
Dim text As String ' Считаный текст
Dim res As Long ' Результат выполнения функции API

GetWindowThreadProcessId hwnd, Pid 'Получаем идентификатор процесса, которому принадлежит объект
pHandle = OpenProcess(PROCESS_VM, 0&, Pid) 'Получаем описатель процесса
ReDim bt(0 To 3) 'Резервируем 4 байта для считывания указателя
ReadProcessMemory pHandle, &H4DE38120, bt(0), 4, 0&   'Читаем указатель на имена растворов в массив.
Addr = bt(0) + CLng(bt(1)) * 256 + CLng(bt(2)) * 65536 + CLng(bt(3)) * 16777216 'Преобразуем массив в адрес
Addr = Addr + 36404 'Добавили смещение
ReDim bt(0 To SolutionNameLength - 1) 'Резервируем фиксированное число байт под считываемый текст

res = 2 'Обращаемся к функции пока не считает данные без ошибки
Do While res <> 1
   DoEvents
   res = ReadProcessMemory(pHandle, ByVal Addr, bt(0), ByVal SolutionNameLength, 0&)    'Читаем указатель в массив.
Loop

'Преобразуем массив данных в текст
text = ""
'Читаем имя раствора, выпавшего первым в списке
For i = 0 To SolutionNameLength - 1
   If bt(i) = 0 Then Exit For 'Выход для нуль-строки
   text = text & Chr(bt(i))
Next i
CloseHandle pHandle 'Закрываем процесс
End Sub



В данном коде цикл задержки некорректный - бесконечный. Но при прерывании кода данные в массиве bt считаны правильно. Без цикла данные не считываются - в массиве нули.

Дмитрий Щербаков(The_Prist)

Не пробовали bt(0) так же как ByVal передавать в функцию?
И, если не изменяет память, надо в VarPtr это обрамлять:
ReadProcessMemory(pHandle, ByVal Addr, ByVal VarPtr(bt(0)), ByVal SolutionNameLength, 0&)
И, вроде как, lpBuffer должен декларироваться As Any.
могу ошибаться, пример не тестировал и его глубинная суть мне не ясна...
Даже самый простой вопрос можно превратить в огромную проблему. Достаточно не уметь формулировать вопросы...

McConst

#2
Про As Any спасибо, хотя в моем случае работает и так, и так. Видимо потому, что массив bt, принимающий данные из памяти процесса, объявлен как Byte.  С VarPtr - пробовал, не надо. Эмпирически выяснил, что с VarPtr данные вообще не считываются, а без него считываются.  Перефразирую суть проблемы на примере.

Если точку останова поставить в строке 69, то код считает в массив bt(0 to 3) 4 байта, которые являются адресом, по которому находится искомый текст. Байты адреса из массива bt()  преобразую в нормальный вид типа long, который понимает бейсик и присваиваю в переменную Addr. Эта переменная Addr содержит адрес, по которому я следующим вызовом ReadProcessMemory буду считывать уже нужные мне байты информации (текст 32 байта с нулями в конце). До этого момента всё работает отлично. Теперь если поставить следующую точку останова в строку 85 и проверить содержание переменных bt(), text - всё считано правильно и всё работает. В данном случае строки 71, 72, 73, 75 типа лишние. Но если точку останова в 69-й строке не делать, а сразу в 85-й, то анализ переменных показывает, что при формировании адреса переменная bt прочитана корректно и Addr преобразована правильно, а при работе 74 строки в переменную bt данные считаны неправильно - получены нули вместо нужных байт информации. Для того, чтобы проверить корректность завершения API в строке 74, проверил переменную res, принимающую результат API. Думал, что при некорректном выполнении будет возвращаться какой-нибудь код ошибки, для этого ввёл строки 71, 72, 73, 75. Но практика показала, что res всегда 1. Если в строках 72-75 сделать бесконечный цикл и принудительно прервать выполнение программы, то в массив bt всё же приходит нужная мне информация. Из чего я сделал вывод, что для правильного считывания памяти 2-мя подряд API функциями ReadProcessMemory, между ними  должна быть какая-то пауза, за время которой в винде что-то расклинивает и она начинает считывать второй раз правильно.

К сожалению данный пример протестить не получится, так как процедура заточена под считывание процесса от базы данных в локальной сети (адрес &H4DE38120 в первом вызове API подобран под базу данных). Но попробую немного переписать код, который можно будет протестить здесь на форуме.

McConst

Переделал код под последовательное считывание переменных Excel через API, всё отлично работает. Никаких проблем. Получается, что  процесс базы данных как-то влияет на считывание памяти. Т.е. не дает быстро считать память. ArtMoney показывает, что оба адреса находятся в разных модулях (библиотеках) процесса. Может это как-то влияет. По факту пытаюсь через API считать результат поиска, который выдала база. Этот результат спокойно висит себе на экране, ничего с базой не делаю, и периодически запускаю макрос: с точками останова и без. И получаю разные значения от второго вызова ReadProcessMemory. Какие есть идеи с чем это связано и как это красиво победить. Скорее всего нужно ставить задержку по условию (например ненулевых значений, и по таймеру, если результат поиска в базе данных всё же нулевой). Может можете предложить более красивые решения?

McConst

Подумал и понял. Процесс не висит в памяти. Вернее должен висеть на момент выполнения проблемной процедуры. Но перед этим код макроса, здесь не представленный, создает поисковый запрос и отправляет команду на поиск. Просто результат поиска ещё не пришел, а первое считывание указателя идет из статической области памяти процесса. :-)

Тему можно закрывать.

Яндекс.Метрика Рейтинг@Mail.ru