CCI provides two objects to handle reading and writing PE Data.

Reading Files

The PeReader object provides a fast memory-efficient way to incrementally read PE files. It converts the contents to a CCI Metadata object model and returns an IModule or IAssembly interface, depending on the file type. It is an ideal way for compilers written in managed code to import metadata, because it uses no unmanaged code and has no interop overhead.

The PeReader.OpenModule and PeReader.OpenAssembly methods take an IBinaryDocument interface to a module or assembly. They use the data to create an immutable representation of the file’s contents and return the associated IModule or IAssembly interface. Applications that load PE files from the file system typically use PeReader indirectly, by calling DefaultHost.LoadUnitFrom, which opens the file, passes the contents to OpenModule or OpenAssembly, and returns the appropriate interface.

The following example from PeToPe loads a PE file, specified by the first command-line argument. IModule is a child of IAssembly, so casting the return value to IModule allows the code to handle both modules and assemblies.
MetadataReaderHost host = new PeReader.DefaultHost();
var module = host.LoadUnitFrom(args[0]) as IModule;

To load a PDB file, create a new PdbReader object and pass the constructor a stream representing the file’s contents. PdbReader constructs an object model to represent the PDB file from the contents of the stream. Unlike PeReader, PdbReader does not return an interface to an object model. Instead, the PdbReader object itself represents the PDB file.

The following example from PeToPe loads a PDB file. It assumes that the file is in the same folder as the module or assembly, and has the same filename with a .pdb extension.
PdbReader/*?*/ pdbReader = null;
string pdbFile = Path.ChangeExtension(module.Location, "pdb");
if (File.Exists(pdbFile)) {
  Stream pdbStream = File.OpenRead(pdbFile);
  pdbReader = new PdbReader(pdbStream, host);

Writing Files

The PeWriter object’s WritePeToStream method provides a fast and memory-efficient way to convert an object model to the PE format and write it to a stream, typically a file stream. Unlike components such as System.Reflection.Emit, PeWriter can write any object model that implements the CCI interfaces. You do not need a compiler to construct objects. Like PeReader, PeWriter uses only managed code to write PE streams.

The following example from HelloIL writes an object model (assembly) to a PE file named Hello.exe.
Stream peStream = File.Create("hello.exe");
PeWriter.WritePeToStream(assembly, host, peStream);

To write a PdbReader object model to a stream, you create a PdbWriter object and pass it to PdbWriter.WritePeToStream. PeWriter interacts with PdbWriter to produce an output stream. At the moment, PdbWriter uses a managed wrapper for an unmanaged COM component for this purpose, so writing PDB files does use some unmanaged code.

The following example is a simplified excerpt from PeToPe. It converts a PdbReader object to the PDB format and writes the results to a file with the same filename as the associated assembly or module and a .pdb extension.
using (var pdbWriter = new PdbWriter(module.Location + ".pdb", pdbReader))
  PeWriter.WritePeToStream(module, host, peStream, pdbReader, pdbReader, pdbWriter);

PdbReader and PdbWriter both interact with the file system, which requires locking files. Both types support IDisposable, which allows you to ensure that files aren’t locked longer than necessary by disposing the objects.

You should dispose PeToPe PdbReader and PdbWriter as soon as you are finished with the objects. The example manages this task by wrapping the WritePeToStream call in a using block.

Last edited Mar 17, 2010 at 4:16 PM by Guy_Smith, version 1


No comments yet.