// Implements IHashCodeProvider too, due to Hashtable requirements. public int GetHashCode(Object key) { String s = (String)key; return(FastResourceComparer.GetHashCode(s)); }
// After calling AddResource, Generate() writes out all resources to the // output stream in the system default format. // If an exception occurs during object serialization or during IO, // the .resources file is closed and deleted, since it is most likely // invalid. /// <include file='doc\ResourceWriter.uex' path='docs/doc[@for="ResourceWriter.Generate"]/*' /> public void Generate() { if (_resourceList == null) { throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ResourceWriterSaved")); } try { BinaryWriter bw = new BinaryWriter(_output, Encoding.UTF8); Type[] types = new Type[20]; int numTypes = 0; // 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. Don't include the version number of mscorlib, // since we'd have to ignore it when reading it in. resMgrHeaderPart.Write(ResourceManager.ResReaderTypeNameInternal); // Write out class name of the ResourceSet class best suited to // handling this file. resMgrHeaderPart.Write(ResourceManager.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 bw.Write(resMgrHeaderBlob.GetBuffer(), 0, (int)resMgrHeaderBlob.Length); // End ResourceManager header // Write out the RuntimeResourceSet header // Version number bw.Write(RuntimeResourceSet.Version); // number of resources bw.Write(_resourceList.Count); // Store values in temporary streams to write at end of file. int[] nameHashes = new int[_resourceList.Count]; int[] namePositions = new int[_resourceList.Count]; int curNameNumber = 0; MemoryStream nameSection = new MemoryStream(_resourceList.Count * AverageNameSize); BinaryWriter names = new BinaryWriter(nameSection, Encoding.Unicode); MemoryStream dataSection = new MemoryStream(_resourceList.Count * AverageValueSize); BinaryWriter data = new BinaryWriter(dataSection, Encoding.UTF8); IFormatter objFormatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File | StreamingContextStates.Persistence)); #if RESOURCE_FILE_FORMAT_DEBUG // Write NAMES right before the names section. names.Write(new byte[] { (byte)'N', (byte)'A', (byte)'M', (byte)'E', (byte)'S', (byte)'-', (byte)'-', (byte)'>' }); // Write DATA at the end of the name table section. data.Write(new byte[] { (byte)'D', (byte)'A', (byte)'T', (byte)'A', (byte)'-', (byte)'-', (byte)'-', (byte)'>' }); #endif IDictionaryEnumerator items = _resourceList.GetEnumerator(); // Write resource name and position to the file, and the value // to our temporary buffer. Save Type as well. while (items.MoveNext()) { nameHashes[curNameNumber] = FastResourceComparer.GetHashCode((String)items.Key); namePositions[curNameNumber++] = (int)names.Seek(0, SeekOrigin.Current); names.Write((String)items.Key); // key names.Write((int)data.Seek(0, SeekOrigin.Current)); // virtual offset of value. #if RESOURCE_FILE_FORMAT_DEBUG names.Write((byte)'*'); #endif Object value = items.Value; // Find type table index. If not there, add new element. // We will use -1 as the type for null objects Type typeOfValue = (value == null) ? null : value.GetType(); if (value != null) { int i = 0; for (; i < numTypes; i++) { if (types[i] == typeOfValue) { Write7BitEncodedInt(data, i); break; } } if (i == numTypes) { if (numTypes == types.Length) { Type[] newTypes = new Type[numTypes * 2]; Array.Copy(types, newTypes, numTypes); types = newTypes; } types[numTypes] = typeOfValue; Write7BitEncodedInt(data, numTypes++); } } else { // -1 is the type for null. Write7BitEncodedInt(data, -1); } // Write out value WriteValue(typeOfValue, value, data, objFormatter); #if RESOURCE_FILE_FORMAT_DEBUG data.Write(new byte[] { (byte)'S', (byte)'T', (byte)'O', (byte)'P' }); #endif } // At this point, the ResourceManager header has been written. // Finish RuntimeResourceSet header // Write size & contents of class table bw.Write(numTypes); for (int i = 0; i < numTypes; i++) { bw.Write(types[i].AssemblyQualifiedName); } // 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 4 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. BCLDebug.Assert((bw.BaseStream.Position & 7) == 0, "ResourceWriter: Name hashes array won't be 8 byte aligned! Ack!"); #if RESOURCE_FILE_FORMAT_DEBUG bw.Write(new byte[] { (byte)'H', (byte)'A', (byte)'S', (byte)'H', (byte)'E', (byte)'S', (byte)'-', (byte)'>' }); #endif foreach (int hash in nameHashes) { bw.Write(hash); } #if RESOURCE_FILE_FORMAT_DEBUG Console.Write("Name hashes: "); foreach (int hash in nameHashes) { Console.Write(hash.ToString("x") + " "); } Console.WriteLine(); #endif // Write relative positions of all the names in the file. // Note: this data is 4 byte aligned, occuring immediately // after the 8 byte aligned name hashes (whose length may // potentially be odd). BCLDebug.Assert((bw.BaseStream.Position & 3) == 0, "ResourceWriter: Name positions array won't be 4 byte aligned! Ack!"); #if RESOURCE_FILE_FORMAT_DEBUG bw.Write(new byte[] { (byte)'P', (byte)'O', (byte)'S', (byte)'-', (byte)'-', (byte)'-', (byte)'-', (byte)'>' }); #endif foreach (int pos in namePositions) { bw.Write(pos); } #if RESOURCE_FILE_FORMAT_DEBUG Console.Write("Name positions: "); foreach (int pos in namePositions) { Console.Write(pos.ToString("x") + " "); } Console.WriteLine(); #endif // Flush all BinaryWriters to MemoryStreams. 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 BCLDebug.Log("RESMGRFILEFORMAT", "Generate: start of DataSection: 0x" + startOfDataSection.ToString("x") + " nameSection length: " + nameSection.Length); bw.Write(startOfDataSection); // Write name section. bw.Write(nameSection.GetBuffer(), 0, (int)nameSection.Length); names.Close(); // Write data section. BCLDebug.Assert(startOfDataSection == bw.Seek(0, SeekOrigin.Current), "ResourceWriter::Generate - start of data section is wrong!"); bw.Write(dataSection.GetBuffer(), 0, (int)dataSection.Length); data.Close(); bw.Flush(); #if DEBUG if (ResourceManager.DEBUG >= 1) { Console.WriteLine("Wrote out " + (resources.Length) + " resources."); } #endif } catch (Exception) { _output.Close(); throw; } // Indicate we've called Generate _resourceList = null; }
// From a name, finds the associated virtual offset for the data. // This does a binary search through the names. internal int FindPosForResource(String name) { BCLDebug.Assert(_store != null, "ResourceReader is closed!"); int hash = FastResourceComparer.GetHashCode(name); BCLDebug.Log("RESMGRFILEFORMAT", "FindPosForResource for " + name + " hash: " + hash.ToString("x")); // Binary search over the hashes. Use the _namePositions array to // determine where they exist in the underlying stream. int lo = 0; int hi = _numResources - 1; int index = -1; bool success = false; while (lo <= hi) { index = (lo + hi) >> 1; // Do NOT use subtraction here, since it will wrap for large // negative numbers. //int c = _nameHashes[index].CompareTo(hash); int currentHash = GetNameHash(index); int c; if (currentHash == hash) { c = 0; } else if (currentHash < hash) { c = -1; } else { c = 1; } //BCLDebug.Log("RESMGRFILEFORMAT", " Probing index "+index+" lo: "+lo+" hi: "+hi+" c: "+c); if (c == 0) { success = true; break; } if (c < 0) { lo = index + 1; } else { hi = index - 1; } } if (!success) { #if RESOURCE_FILE_FORMAT_DEBUG lock (this) { _store.BaseStream.Seek(_nameSectionOffset + GetNamePosition(index), SeekOrigin.Begin); String lastReadString = _store.ReadString(); } BCLDebug.Log("RESMGRFILEFORMAT", "FindPosForResource for " + name + " failed. i: " + index + " lo: " + lo + " hi: " + hi + " last read string: \"" + lastReadString + "\""); #endif return(-1); } // index is the location in our hash array that corresponds with a // value in the namePositions array. // There could be collisions in our hash function. Check on both sides // of index to find the range of hash values that are equal to the // target hash value. if (lo != index) { lo = index; while (lo > 0 && GetNameHash(lo - 1) == hash) { lo--; } } if (hi != index) { hi = index; while (hi < _numResources && GetNameHash(hi + 1) == hash) { hi++; } } lock (this) { for (int i = lo; i <= hi; i++) { _store.BaseStream.Seek(_nameSectionOffset + GetNamePosition(i), SeekOrigin.Begin); if (CompareStringEqualsName(name)) { int dataPos = _store.ReadInt32(); BCLDebug.Assert(dataPos >= 0 || dataPos < _store.BaseStream.Length - _dataSectionOffset, "Data section relative offset is out of the bounds of the data section! dataPos: " + dataPos); return(dataPos); } } } BCLDebug.Log("RESMGRFILEFORMAT", "FindPosForResource for " + name + ": Found a hash collision, HOWEVER, neither of these collided values equaled the given string."); return(-1); }