Well, yes, there is a way, but you're not going to like it. (Appearantly, I need a disclaimer like this, to prevent my otherwise perfectly helpfull comment getting downvoted by the oh-so knowledgable, but not so forgiving 'senior' SO members.)
FYI : The following description is a high-level overview of a piece of code I actually wrote when Delphi 5 was the latest & greatest. Since then, that code was ported over to newer Delphi versions (currently up until Delphi 2010) and still works!
For starters, you need to know that a class is nothing more than a combination of a VMT and the accompanying functions (and maybe some type-info, depending on compiler-version and -settings). As you probably know, a class - as identified by the type TClass - is just a pointer to the memory address of that classes' VMT. In other words : If you known the address of the VMT of a class, that's the TClass pointer as well.
With that piece of knowledge stuck firmly in your mind, you can actually scan your executable memory, and for each address test if it 'looks like' a VMT. All addresses that seem to be a VMT can than be added to a list, resulting in a complete overview of all classes contained in your executable! (Actually, this even gives you access to classes declared solely in the implementation-section of a unit, and classes linked-in from components & libraries that are distributed as binaries!)
Sure, there's a risk that some addresses seem to be a valid VMT, but are actually some random other data (or code) - but with the tests I've come up with, this has never happened to me yet (in about 6 years running this code in more than ten actively maintained applications).
So here's the checks you should do (in this exact order!) :
- Is the address equal to the address of TObject? If so, this address is a VMT and we're done!
- Read TClass(address).ClassInfo; If it's assigned :
- it should fall inside a code-segment (no, I won't go into details on that - just google it up)
- the last byte of this ClassInfo (determined by adding SizeOf(TTypeInfo) + SizeOf(TTypeData)) should also fall inside that code-segment
- this ClassInfo (which is of type PTypeInfo) should have it's Kind field set to tkClass
- Call GetTypeData on this ClassInfo, resulting in a PTypeData
- This should also fall inside a valid code segment
- It's last byte (determined by adding SizeOf(TTypeData)) should also fall inside that code-segment
- Of this TypeData it's ClassType field should be equal to the address being tested.
- Now read the VMT-to-be at the offset vmtSelfPtr and test if this results in the address being tested (should point to itself)
- Read vmtClassName and check if that points to a valid classname (check pointer to reside in a valid segment again, that the string length is acceptable, and IsValidIdent should return True)
- Read vmtParent - it should also fall in a valid code segment
- Now cast to a TClass and read ClassParent - it should also fall in a valid code segment
- Read vmtInstanceSize, it should be >= TObject.InstanceSize and <= MAX_INSTANCE_SIZE (yours to determine)
- Read vmtInstanceSize from it's ClassParent, it should also be >= TObject.InstanceSize and <= the previously read instance size (parent classes can never be larger than child classes)
- Optionally, you could check if all VMT entries from index 0 and upwards are valid code pointers (although it's a bit problematic to determine the number of entries in a VMT... there's no indicator for this).
- Recurse these checks with the ClassParent. (This should reach the TObject test above, or fail miserably!)
If all these checks hold, the test-address is a valid VMT (as far as I'm concerned) and can be added to the list.
Good luck implementing this all, it took me about a week to get this right.
Please tell how it works out for you. Cheers!