public void Generate()
{
if (_resourceList == null)
throw new InvalidOperationException(SR.InvalidOperation_ResourceWriterSaved);
BinaryWriter bw = new BinaryWriter(_output, Encoding.UTF8);
List<string> typeNames = new List<string>();
// Write out the ResourceManager header
// Write out magic number
bw.Write(ResourceManager.MagicNumber);
// Write out ResourceManager header version number
bw.Write(ResourceManager.HeaderVersionNumber);
MemoryStream resMgrHeaderBlob = new MemoryStream(240);
BinaryWriter resMgrHeaderPart = new BinaryWriter(resMgrHeaderBlob);
// Write out class name of IResourceReader capable of handling
// this file.
resMgrHeaderPart.Write(ResourceReaderFullyQualifiedName);
// Write out class name of the ResourceSet class best suited to
// handling this file.
// This needs to be the same even with multi-targeting. It's the
// full name -- not the assembly qualified name.
resMgrHeaderPart.Write(ResSetTypeName);
resMgrHeaderPart.Flush();
// Write number of bytes to skip over to get past ResMgr header
bw.Write((int)resMgrHeaderBlob.Length);
// Write the rest of the ResMgr header
resMgrHeaderBlob.Seek(0, SeekOrigin.Begin);
resMgrHeaderBlob.CopyTo(bw.BaseStream, (int)resMgrHeaderBlob.Length);
// End ResourceManager header
// Write out the RuntimeResourceSet header
// Version number
bw.Write(ResSetVersion);
// number of resources
int numResources = _resourceList.Count;
if (_preserializedData != null)
numResources += _preserializedData.Count;
bw.Write(numResources);
// Store values in temporary streams to write at end of file.
int[] nameHashes = new int[numResources];
int[] namePositions = new int[numResources];
int curNameNumber = 0;
MemoryStream nameSection = new MemoryStream(numResources * AverageNameSize);
BinaryWriter names = new BinaryWriter(nameSection, Encoding.Unicode);
Stream dataSection = new MemoryStream(); // Either a FileStream or a MemoryStream
using (dataSection)
{
BinaryWriter data = new BinaryWriter(dataSection, Encoding.UTF8);
if (_preserializedData != null)
{
foreach (KeyValuePair<string, PrecannedResource> entry in _preserializedData)
{
_resourceList.Add(entry.Key, entry.Value);
}
}
// Write resource name and position to the file, and the value
// to our temporary buffer. Save Type as well.
foreach (var item in _resourceList)
{
nameHashes[curNameNumber] = FastResourceComparer.HashFunction(item.Key);
namePositions[curNameNumber++] = (int)names.Seek(0, SeekOrigin.Current);
names.Write(item.Key); // key
names.Write((int)data.Seek(0, SeekOrigin.Current)); // virtual offset of value.
object value = item.Value;
ResourceTypeCode typeCode = FindTypeCode(value, typeNames);
// Write out type code
Write7BitEncodedInt(data, (int)typeCode);
var userProvidedResource = value as PrecannedResource;
if (userProvidedResource != null)
{
data.Write(userProvidedResource.Data);
}
else
{
WriteValue(typeCode, value, data);
}
}
// At this point, the ResourceManager header has been written.
// Finish RuntimeResourceSet header
// The reader expects a list of user defined type names
// following the size of the list, write 0 for this
// writer implementation
bw.Write(typeNames.Count);
foreach (var typeName in typeNames)
{
bw.Write(typeName);
}
// Write out the name-related items for lookup.
// Note that the hash array and the namePositions array must
// be sorted in parallel.
Array.Sort(nameHashes, namePositions);
// Prepare to write sorted name hashes (alignment fixup)
// Note: For 64-bit machines, these MUST be aligned on 8 byte
// boundaries! Pointers on IA64 must be aligned! And we'll
// run faster on X86 machines too.
bw.Flush();
int alignBytes = ((int)bw.BaseStream.Position) & 7;
if (alignBytes > 0)
{
for (int i = 0; i < 8 - alignBytes; i++)
bw.Write("PAD"[i % 3]);
}
// Write out sorted name hashes.
// Align to 8 bytes.
Debug.Assert((bw.BaseStream.Position & 7) == 0, "ResourceWriter: Name hashes array won't be 8 byte aligned! Ack!");
foreach (int hash in nameHashes)
{
bw.Write(hash);
}
// Write relative positions of all the names in the file.
// Note: this data is 4 byte aligned, occurring immediately
// after the 8 byte aligned name hashes (whose length may
// potentially be odd).
Debug.Assert((bw.BaseStream.Position & 3) == 0, "ResourceWriter: Name positions array won't be 4 byte aligned! Ack!");
foreach (int pos in namePositions)
{
bw.Write(pos);
}
// Flush all BinaryWriters to their underlying streams.
bw.Flush();
names.Flush();
data.Flush();
// Write offset to data section
int startOfDataSection = (int)(bw.Seek(0, SeekOrigin.Current) + nameSection.Length);
startOfDataSection += 4; // We're writing an int to store this data, adding more bytes to the header
bw.Write(startOfDataSection);
// Write name section.
nameSection.Seek(0, SeekOrigin.Begin);
nameSection.CopyTo(bw.BaseStream, (int)nameSection.Length);
names.Dispose();
// Write data section.
Debug.Assert(startOfDataSection == bw.Seek(0, SeekOrigin.Current), "ResourceWriter::Generate - start of data section is wrong!");
dataSection.Position = 0;
dataSection.CopyTo(bw.BaseStream);
data.Dispose();
} // using(dataSection) <--- Closes dataSection, which was opened w/ FileOptions.DeleteOnClose
bw.Flush();
// Indicate we've called Generate
_resourceList = null;
}