Эта статья впервые напечатана в ClarionOnline ( http://www.clariononline.com ) в volume 2 issue 4 (November 1998)
Техники отладки и IPC
Отладка кода, как и (в первую очередь) написание кода, является очень личным процессом. Мы все разрабатываем собственные техники отладки, и прибегаем ним когда возникает необходимость.
Те из вас, кто имел дело с последними столбовыми дорогами Clarion, прибегали как минимум к трём техникам, которые были описаны на этом пути. А для тех кто не имел с ними дел, или уже забыл, это были:
В этой статье я собираюсь описать четвертый способ.
Хотя, перед тем как начать, требуется немного истории и пояснений. Во времена когда Windows 95 только мелькала в наших глазах и 386-я машина была верхом совершенства, мы использовали Windows 3.1. Для разработки кода мы использовали Windows Software Development Kit, который был (честно говоря) громоздким набором инструментов и утилит, которые использовались вместе с другими продуктами типа Microsoft C 6.0 или Borland Turbo C.
Частю этого SDK была малоизвестная утилита с именем DBWin. Ее прямым назначением была отладка и она имела, кроме всего прочего, способность вывода результатов вызовов функции Windows API OutputDebugString.
Эта малоизвестная функция имеет довольно простое назначение - она принимает строковый параметр и выводит его в программу, способную вывести результат, и которой обычно был отладчик.
DBWin и является такой программой, но она не является отдадчиком - она просто имеет способность перехватывать вывод OutputDebugString и выводить его SDI-окно, перенаправлять в последовательный порт, или выводить на второй монитор.
Она может также показывать ошибки ядра, такие как освобождение неправильтых указателей (которые, кажется, среда Clarion делает мнжество).
Первое, что нам надо сделать, это описать OutputDebugString. Это легко (для 16-разрядов просто удалите атрибут NAME):
OutputDebugString(ULong),Raw,Pascal,Name('OutputDebugStringA')
Для ее использования, просто сделайте следующее:
Loc:TempCString = ‘Hello World!’ OutputDebugString(Address(Loc:TempCString))
И ваша отладочная строка появилась где вы хотели. Теперь, это все прекрастно но только пока вы пишите 16-разрядные приложения, но к сожалению, это не так для 32-разрядных приложений. Как показано в следующей таблице, при вызовеl OutputDebugString в 32-разрядном приложении работающим в 16-разрядной ОС, это не работает.
| Windows 95 | Windows 98 | Windows NT/Windows 2000 | |
| 16-разрядное приложение | OK | OK | OK |
| 32-разрядное | Fail | Fail | OK |
Причина этого довольно проста: разработчики Windows 95 (и Windows 98) не реализовали ее таким же образом как разработчики Windows NT.
Другая вещь, которую надо помнить, это появление новых сред разработки (IDE’s) таких как Visual Basic, Visual C и Visual J++, для которых нет необходимости в таких утилитах как DBWin. Используя эти языки и среды разработки, в которые встроены отладчики и другие инструменты, нет нужды прибегать к вызову OutputDebugString, и ее использование уменьшилось.
Для остальных использование OutputDebugString еще осталось средством избежать отладки. Со временем было написано несколько 32-разрядных эквивалентов DBWin для перехвата вывола из 32-разрядных приложений.
К этой статье приложены три подобных вьювера - DBMON, DBWIN32 и DBWIN32C - которые вы можете использовать для просмотра ваших отладочных сообщений.
DBMON и DBWIN32 обе перехватывают вывод из 32-разрядных приложений вызовы OutputDebugString под Windows NT. Первая является рудиментарной консольной программо, а вторая полноправная программа Windows, и они обе используют для работы IPC (междупроцессорные коммуникации) (inter-process communication).
DBWIN32C подобна DBMON, но использует VXD, который должен быть установлен на машине, на которой происходит отладка. Так как VXD работает только под Windows 95, и не нуждается в IPC для перехвата вывода из OutputDebugString.
So, if all you do is test under Windows 95 and you don’t mind installing yet another VXD, then you’re all ready to start using OutputDebugString in your application. If, on the other hand, you need to test under Windows 95 and Windows NT then you’ll have to use DBMON and DBWIN32.
Как это работает?
Если говорить просто, DBMON и DBWIN32 работают под Windows NT, потому что ядро NT использует IPC для коммуникаций. Когда вы вызываете OutputDebugString, ядро записывает строку в разделяемую память, и вьювер извлекает строку и выводит ее.
Для использования OutputDebugString под Windows 95/98, мы должны завернуть наш собственный интерфейс IPC к этим приложениям чтобы наша строка появлялась правильно. Мы хотим также вызывать OutputDebugString, только в случае если запущен отладчик (такой как Soft/ICE) который также перехватывает trap вывод.
Все что мы собираемся сделать это написать новую функцию, PrintDebugString, которая является оболочкой (wrapper-ом) к функции API. Наша новая функция принимает один строковый параметр (Prm:DataString), и не ничего не возвращает.
Первое, что нам нужно сделать, при вызове OutputDebugString - позаботится об 16-разрядных приложениях, и 32-разрядных приложениях работающих под Windows NT. Теперь наступает смешная часть - 32-разрядные приложенияя под Windows 95.
Когда мы передаем данные в программу вьювера, она ожидает данные в определенном формате. Он определен программой вьювера, которой в свою очередь реализацией Windows. Они ожидают 4-байтный идентификатор процесаа (PID), с последующей строкой, содержащей данные для вывода. В моей реализации, я ограничиваю выводимую строку 80-ю символами - хотя я надеюсь что вьювер может принимать строку до 512 байт, но это слишком много для моих целей. Вы можете поправить эту цифру если вам нужно выводить больше. Для передачи данных я инициализирую группу и использую функцию memcpy для копирования 84 байт по корректному адресу памяти.
SharedMemory Group,Pre(SM) ProcessID Long OutputString CString(80) End
Так как язык C имеет способность, которой нет у Clarion - указатели - мы можем использовать memcpy для копирования данных по правильному адресу памяти. Когда имеем дело с Windows API мы редко используем функцию Memcpy для инициализации указателя, или разименования возвращаемого значения, только так мы можем вызвать функцию API и передать/принять параметр корректно.
Первое что нам нужно - определить что мы работаем под Windows 95 или Windows NT, и мы способны послать строку данных. Сделаем это при помощи функции GetVersion():
GetVersion(),Long,Raw,Pascal
И мы используем это следующим образом:
Loc:Version = GetVersion()
If Band(Loc:Version,10000000000000000000000000000000B)
! Windows 95 or Windows 98
If Len(Clip(Prm:DataString)) < 80 Then
! Hand-roll our own interface
Do OurOwnInterface
Else
! The string is too long
Message(‘The string is too long!’)
End
Else
! Windows NT
! Ничего не делаем - мы уже вызвали OutputDebugString
End
Внутри подпрограммы OurOwnInterface имеется интересный код. Мы сначала иничиалижируем все хендлы, которые собираемся использовать, нулем. После завершения подпрограммы, закрываем все хендлы не равные нулю.
Loc:heventDBWIN = 0 Loc:heventData = 0 Loc:hSharedFile = 0 Loc:lpszSharedMem = 0
Далее, мы должны убедиться что вьювер событий готов принять данные. Делаем это получением хендла на обьект, который уже создан программой вьювера. Если мы не можем получить этот хендл, то очевидно вьювер не запущен.
Loc:DBBR = 'DBWIN_BUFFER_READY' Loc:DBBR_Ptr = Address(Loc:DBBR) Loc:heventDBWIN = OpenEvent(EVENT_MODIFY_STATE, FALSE, Loc:DBBR_Ptr)
Затем пытаемся получить хендл на обьект данных, который мы будем использовать для сообщения вьюверу о том что данные готовы для чтения.
Loc:DBDR = 'DBWIN_DATA_READY' Loc:DBDR_Ptr = Address(Loc:DBDR) Loc:heventData = OpenEvent(EVENT_MODIFY_STATE, FALSE, Loc:DBDR_Ptr);
После того как мы это сделали, нужно иничиализировать разделяемую память действительно передаваемыми данными. Разделяемая память на самом деле является файловым обьектом, который мы создаем и отображаем в наше собственное адресное пространство.
Loc:DBB = 'DBWIN_BUFFER' Loc:DBB_Ptr = Address(Loc:DBB) Loc:hSharedFile = CreateFileMapping(-1, 0, PAGE_READWRITE,0, 4096, Loc:DBB_Ptr) If Loc:hSharedFile <> 0 Then Loc:lpszSharedMem = MapViewOfFile(Loc:hSharedFile, FILE_MAP_WRITE, 0, 0,512); End
Теперь нужно дождаться чтобы вьювер принял данные. Программа вьювер постоянно находится в состоянии обьекта, так что она не должна слишком долго (IE < 1 секунды) принимать данные.
x# = WaitForSingleObject(Loc:heventDBWIN, INFINITE);
После того как вьювер принял данные, осталось только скопировать данные в разделяемую память и сообщить вьюверу что они уже там.
Мы уже иничиализировали структуру группы, так что осталось только заполнить ее правильными значениями и использовать MEMCPY для копирования в область разделяемой памяти.
SM:ProcessID = GetCurrentProcessID() SM:OutputString = Clip(Prm:DataString) & Chr(0) memcpy(Loc:lpszSharedMem,Address(SharedMemory),Size(SharedMemory)) x# = SetEvent(Loc:heventData)
Теперь осталось только освободить все хендлы и разделяемую память:
If Loc:lpszSharedMem <> 0 Then x# = UnmapViewOfFile(Loc:lpszSharedMem) End If Loc:hSharedFile <> 0 Then x# = CloseHandle(Loc:hSharedFile) End If Loc:heventData <> 0 Then x# = CloseHandle(Loc:heventData) End If Loc:heventDBWIN <> 0 Then x# = CloseHandle(Loc:heventDBWIN) End
Это в основном все. Конечно, я не показал все проверки ошибок, и выводимые сообщения об ошибках, но я уверен, что вы поняли основную идею.
Используя OutputDebugString вы имеете простой неназойливый способ определения состояния вашей программы. Он не "напрягает" жесткий диск подобно файлу трассировки, так что это быстрее. Вы можете, если хотите, оставить вызовы в вашем коде навсегда. Падение производительности пренебрежимо мало, и несмотря на то что ваш клиент также имеет вьювер (что мало вероятно) он даже не знает о его существовании. если возникла ошибка, просто пошлите ему вьювер и он может возвратить результат.
И последнее замечание. Вы можете также смотреть вывод OutputDebugString в 16-разрядном отладчике Clarion. В меню Options|Settings имеется переключатель ‘Disable Kernel messages’. Если вы его разрешите (сняв отметку), то все вызовы OutputDebugString появятся в окне сообщения. Это довольно (и даже очень) надоедливо, потому что вы должны закрывать эти сообщения для продолжения, но предоставляет ваи еще один путь вывода результатов. К сожалению я не знаю ничего подобного в 32-разрядном отладчике.
Выводы
OutputDebugString и связанные с ней вьюверы являются дополнительным средством для отслеживания ваших ошибок. Оно легко в использовании, не ухудшает производительность и не действует на основной код. Уже только по этим причинам, оно достойно рассмотрения.
В следующей статье
Фильтры сообщений и перехват нажатий на клавиши.
Back to my home page. Or send me mail at paula@attglobal.net
(c) 1999 Paul Attryde