Le metatadaApi sono sicuramente tra le meno conosciute e sicuramente tra le meno utili nel bagaglio del programmatore .NET medio, ma per chi è appassionato di programmazione a basso livello oppure per chi vuole costruire un debugger o fare clr hosting, queste API entrano a far parte del bagaglio indispensabile. Leggedo il post di Adrian mi è tornato in mente quando mi dilettavo di MSIL leggendo il libro di Lidin. In sostanza le Metadata API permettono al codice unmanaged di conoscere ciò che si trova dentro la parte Metadata di un PE File che rappresenta un assembly .Net. La struttura binaria dei metadati può essere studiata in dettaglio nel libro di Lidin (è una cosa decisamente tediosa), ma le MetadataApi permettono di analizzare un assembly ignorando la sua struttura fisica. La prima cosa da fare è creare un paio di oggetti COM ed in particolare un'istanza di IMetaDataImport. Per fare questo ho creato una semplice classe chiamata Reflector
Nel costruttore vengono create le interfacce necessarie con un paio di chiamate, la prima è la classica CoCreateInstance() che ci fa ottenere un'istanza di IMetaDataDispenserEx dalla quale si ottiene poi la IMetaDataImport cercata.
A questo punto si deve procedere alla enumerazione, per questo è stata creata una funzione GetTypeName:
Nei metadati di una assembly le informazioni sono suddivise in tabelle in maniera analoga ad un database, per individuare i dati in una tabella si utilizza un intero che viene detto token il quale contiene sia l'indicazione della tabella sia l'"id" del record. (se vi interessa sapere in dettaglio il formato c'è sempre il libro di Lidin). per conoscere i nomi delle classi presenti in un assembly prima di tutto dobbiamo enumerare tutti i token di tipo TypeDef (relativi alla definizione dei tipi), per far questo prepariamo un bell'array che tiene 1024 elementi e lo passiamo al metodo IMetaDataImport::EnumTypeDef specificando quanti posti ha l'array. Al termine della chiamata nell'ultimo parametro troveremo il numero effettivo di token scritti nell'array. Queste API infatti nella migliore tradizione C++ non allocano memoria internamente, ma richiedono al chiamante di passare strutture grandi in maniera adeguata per contenere tutti i dati di output, benvenuti nel mondo unmanaged :D. A questo punto bisogna recuperare il valore descrittivo dei tipi andando a leggere nella tabella dei metadati che tiene memorizzati i tipi.
Anche in questo caso è necessario preparare lo spazio in anticipo, un array di 512 caratteri viene quindi dichiarato per contenere il nome del tipo, a questo punto basta chiamare la IMetaDataImport::GetTypeDefProps() che oltre a riempire l'array con il nome del tipo corrispondente al token, restituisce anche un puntatore agli attributi (public, abstract, etc) che modificano il tipo oltre ad un token relativo alla classe base da cui questo tipo eredita. In questo esempio si usa solamente il nome che viene messo in un vector<wstring> passato dal chiamante.
Sicuramente questo codice non è eccitante, ma una conoscenza più a basso livello del framework qualche volta è comoda.
Alk.