Classes/ISOHelper.cs
|
// custom class declaration (C#); contains the Create method for writing an ISO file to disk. public class ISOHelper { // Writes an ISO file to disk from an IMAPI2 image stream. // IMAPI2 builds the ISO in memory via MsftFileSystemImage.CreateResultImage(), which returns // the image data as a COM IStream together with the block size and total block count. // This method reads that stream block by block and writes the blocks to the output file. // IStream.Read (COM interface method) reports the number of bytes read by writing to a raw // memory address. Marshal.AllocHGlobal allocates that memory and returns an IntPtr to it, // and Marshal.ReadInt32 reads the value back. this avoids the need for unsafe code and pointers. // method parameters: // 'path' is the output file path for the ISO image to create. // 'isoImageSource' is the input COM IStream containing the ISO image data to write to disk. // 'blockSize' is the size of one block in bytes; the method reads and writes one block at a time until the // entire image is written. // 'blockCount' is the total number of blocks in the image; used to determine how many blocks to read/write in // total. // 'bufferSizeMultiplier' controls how many blocks are read per IStream.Read call. 1 reads one block at a time // (default); higher values read multiple blocks per call, reducing COM interop overhead. public static void Create(string path, object isoImageSource, int blockSize, int blockCount, int bufferSizeMultiplier) { // calculate the read chunk size: how many bytes to read per IStream.Read call. int chunkSize = blockSize * bufferSizeMultiplier; // allocate 4 bytes of unmanaged memory (size of int) for IStream.Read to write the byte count into. // IStream.Read expects a raw memory address; it cannot write to a regular C# variable. // Marshal.AllocCoTaskMem returns an IntPtr pointing to that memory. System.IntPtr bytesReadPtr = System.Runtime.InteropServices.Marshal.AllocCoTaskMem(sizeof(System.Int32)); // declare 'buffer' array of type byte. // IStream.Read requires a caller-supplied byte array to write data into; it cannot write directly to a // file, so data is read from the stream into 'buffer' first, then written from 'buffer' to the file. // sized to hold 'bufferSizeMultiplier' blocks per read call. // the array is allocated once here and reused on every loop iteration to avoid unnecessary memory // allocation. byte[] buffer = new byte[chunkSize]; // declare 'isoImageSourceStream' variable of type 'IStream' (the COM interface that exposes the Read method). // 'isoImageSource' is an input parameter received as type 'object' in the method declaration above, as that is // how COM objects are passed from PowerShell into C# code. // expression 'isoImageSource as IStream' casts 'isoImageSource' from type 'object' to type 'IStream' so C# can // call IStream.Read() on it. System.Runtime.InteropServices.ComTypes.IStream isoImageSourceStream = isoImageSource as System.Runtime.InteropServices.ComTypes.IStream; // try/finally ensures the unmanaged memory allocated above is always freed, even if an error occurs. try { // declare 'fileStream' variable of type FileStream. // 'File.OpenWrite(path)' opens the destination ISO file at 'path' for writing. // the 'using' keyword ensures 'fileStream' is automatically closed when the block exits. using (System.IO.FileStream fileStream = System.IO.File.OpenWrite(path)) { // pre-allocate the full file size on disk to prevent fragmentation and avoid // repeated file extension during writing. cast to long to avoid int overflow. fileStream.SetLength((long)blockSize * blockCount); // read from 'isoImageSourceStream' until the stream returns 0 bytes (end of data). int bytesRead; do { // read up to 'chunkSize' bytes from the image stream. // 'bytesReadPtr' is the memory address where it writes back the actual number of bytes read. isoImageSourceStream.Read(buffer, chunkSize, bytesReadPtr); bytesRead = System.Runtime.InteropServices.Marshal.ReadInt32(bytesReadPtr); // write the bytes actually read to the destination ISO file. if (bytesRead > 0) fileStream.Write(buffer, 0, bytesRead); } while (bytesRead > 0); } } finally { // free the unmanaged memory allocated by Marshal.AllocCoTaskMem above. System.Runtime.InteropServices.Marshal.FreeCoTaskMem(bytesReadPtr); } } } |