Ну, да, есть способ, но он тебе не понравится. (По-видимому, мне нужен такой отказ от ответственности, чтобы не допустить, чтобы мой в противном случае совершенно полезный комментарий был отвергнут ой-таким знающим, но не таким прощающим «старшим» SO-членом.)
К вашему сведению: Следующее описание представляет собой общий обзор фрагмента кода, который я действительно написал, когда Delphi 5 был последним и лучшим. С тех пор этот код был перенесен на более новые версии Delphi (в настоящее время до Delphi 2010) и все еще работает!
Для начала вам нужно знать, что класс - это не что иное, как комбинация VMT и сопутствующих функций (и, возможно, некоторой информации типа, в зависимости от версии компилятора и -settings). Как вы, вероятно, знаете, класс, идентифицируемый типом TClass, - это просто указатель на адрес памяти VMT этого класса. Другими словами: если вы знаете адрес VMT класса, это также указатель TClass.
Имея эти знания, которые прочно удерживаются в памяти, вы можете сканировать свою исполняемую память и для каждого теста адреса, если он «выглядит» как VMT. Все адреса, которые кажутся VMT, могут быть добавлены в список, что дает полный обзор всех классов, содержащихся в вашем исполняемом файле! (На самом деле, это даже дает вам доступ к классам, объявленным исключительно в разделе реализации модуля, и классам, связанным из компонентов и библиотек, которые распространяются как двоичные файлы!)
Конечно, есть риск, что некоторые адреса кажутся действительными VMT, но на самом деле это какие-то случайные другие данные (или код) - но с тестами, которые я придумал, со мной такого еще никогда не случалось (в около 6 лет работает с этим кодом в более чем десяти активно поддерживаемых приложениях).
Итак, вот проверки, которые вы должны сделать (в этом точном порядке!):
- Адрес равен адресу TObject? Если это так, этот адрес является VMT, и мы закончили!
- Чтение TClass (адрес) .ClassInfo; Если это назначено:
- он должен попадать в сегмент кода (нет, я не буду вдаваться в подробности - просто погуглите его)
- последний байт этого ClassInfo (определяется путем добавления SizeOf (TTypeInfo) + SizeOf (TTypeData)) также должен попадать в этот сегмент кода
- для этого ClassInfo (типа PTypeInfo) в поле Kind должно быть установлено значение tkClass
- Вызовите GetTypeData для этого ClassInfo, что приведет к PTypeData
- Это также должно попадать в допустимый сегмент кода
- Последний байт (определяется путем добавления SizeOf (TTypeData)) также должен попадать в этот сегмент кода
- Из этого TypeData его поле ClassType должно быть равно адресу проверяемого.
1036 **
- Теперь прочитайте существующий VMT со смещением vmtSelfPtr и проверьте, приводит ли это к проверяемому адресу (должен указывать на себя)
- Прочитайте vmtClassName и проверьте, указывает ли это на допустимое имя класса (проверьте, чтобы указатель снова находился в допустимом сегменте, чтобы длина строки была приемлемой, а IsValidIdent должен вернуть True)
- Чтение vmtParent - оно также должно попадать в допустимый сегмент кода
- Теперь приведите к TClass и прочитайте ClassParent - он также должен попадать в допустимый сегмент кода
- Прочитайте vmtInstanceSize, это должно быть> = TObject.InstanceSize и <= MAX_INSTANCE_SIZE (ваш, чтобы определить) </li>
- Считайте vmtInstanceSize из его ClassParent, оно также должно быть> = TObject.InstanceSize и <= ранее прочитанный размер экземпляра (родительские классы никогда не могут быть больше дочерних классов) </li>
- При желании вы можете проверить, являются ли все записи VMT от индекса 0 и выше действительными указателями кода (хотя определить количество записей в VMT немного проблематично ... для этого нет индикатора).
- Заполните эти проверки с ClassParent. (Это должно пройти тест TObject выше или потерпеть неудачу!)
Если все эти проверки выполняются, тест-адрес является действительным VMT (насколько мне известно) и может быть добавлен в список.
Удачи в реализации всего этого, мне потребовалось около недели, чтобы понять это правильно.
Пожалуйста, расскажите, как это работает для вас. Ура!