// Nick Drochak <ndrochak@gol.com>
// Dick Porter <dick@ximian.com>
// Marek Safar <marek.safar@seznam.cz>
+// Atsushi Enomoto <atsushi@ximian.com>
+// Larry Ewing <lewing@novell.com>
//
// (C) 2001, 2002 Ximian Inc, http://www.ximian.com
-// Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
+// Copyright (C) 2004-2005,2007-2008 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
using System.Resources;
using System.IO;
using System.Text;
+using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Permissions;
+using System.Collections.Generic;
namespace System.Resources
{
FistCustom = 64
}
+ [System.Runtime.InteropServices.ComVisible (true)]
public sealed class ResourceReader : IResourceReader, IEnumerable, IDisposable
{
+ struct ResourceInfo
+ {
+ public readonly long ValuePosition;
+ public readonly string ResourceName;
+ public readonly int TypeIndex;
+
+ public ResourceInfo (string resourceName, long valuePosition, int type_index)
+ {
+ ValuePosition = valuePosition;
+ ResourceName = resourceName;
+ TypeIndex = type_index;
+ }
+ }
+
+ struct ResourceCacheItem
+ {
+ public readonly string ResourceName;
+ public readonly object ResourceValue;
+
+ public ResourceCacheItem (string name, object value)
+ {
+ ResourceName = name;
+ ResourceValue = value;
+ }
+ }
+
BinaryReader reader;
+ object readerLock = new object ();
IFormatter formatter;
internal int resourceCount = 0;
int typeCount = 0;
- Type[] types;
+ string[] typeNames;
int[] hashes;
- long[] positions;
+ ResourceInfo[] infos;
int dataSectionOffset;
long nameSectionOffset;
int resource_ver;
+ ResourceCacheItem[] cache;
+ object cache_lock = new object ();
// Constructors
[SecurityPermission (SecurityAction.LinkDemand, SerializationFormatter = true)]
public ResourceReader (Stream stream)
{
if (stream == null)
- throw new ArgumentNullException ("Value cannot be null.");
+ throw new ArgumentNullException ("stream");
if (!stream.CanRead)
throw new ArgumentException ("Stream was not readable.");
public ResourceReader (string fileName)
{
- if (fileName == null)
- throw new ArgumentNullException ("Path cannot be null.");
-
- if (!System.IO.File.Exists (fileName))
- throw new FileNotFoundException ("Could not find file " + Path.GetFullPath(fileName));
-
reader = new BinaryReader (new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read));
formatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File|StreamingContextStates.Persistence));
try {
int manager_magic = reader.ReadInt32();
- if(manager_magic != ResourceManager.MagicNumber) {
- throw new ArgumentException("Stream is not a valid .resources file!");
- }
+ if(manager_magic != ResourceManager.MagicNumber)
+ throw new ArgumentException(String.Format ("Stream is not a valid .resources file, magic=0x{0:x}", manager_magic));
int manager_ver = reader.ReadInt32();
int manager_len = reader.ReadInt32();
/* Now read the ResourceReader header */
resource_ver = reader.ReadInt32();
- if(resource_ver != 1
-#if NET_2_0
- && resource_ver != 2
-#endif
- ) {
+ if(resource_ver != 1 && resource_ver != 2) {
throw new NotSupportedException("This .resources file requires unsupported set class version: " + resource_ver.ToString());
}
resourceCount = reader.ReadInt32();
typeCount = reader.ReadInt32();
- types=new Type[typeCount];
+ typeNames=new string[typeCount];
for(int i=0; i<typeCount; i++) {
- types[i]=Type.GetType(reader.ReadString(), true);
+ typeNames[i]=reader.ReadString();
}
/* There are between 0 and 7 bytes of
throw new ArgumentException("Malformed .resources file (padding values incorrect)");
}
}
-
/* Read in the hash values for each
* resource name. These can be used
* by ResourceSet (calling internal
/* Read in the virtual offsets for
* each resource name
*/
- positions=new long[resourceCount];
- for(int i=0; i<resourceCount; i++) {
- positions[i]=reader.ReadInt32();
- }
+ long[] positions = new long [resourceCount];
+ for(int i = 0; i < resourceCount; i++)
+ positions [i] = reader.ReadInt32();
dataSectionOffset = reader.ReadInt32();
nameSectionOffset = reader.BaseStream.Position;
+
+ long origPosition = reader.BaseStream.Position;
+ infos = new ResourceInfo [resourceCount];
+ for (int i = 0; i < resourceCount; i++)
+ CreateResourceInfo (positions [i], ref infos [i]);
+ reader.BaseStream.Seek (origPosition, SeekOrigin.Begin);
+
+ positions = null;
} catch(EndOfStreamException e) {
- throw new ArgumentException("Stream is not a valied .resources file! It was possibly truncated.", e);
+ throw new ArgumentException("Stream is not a valid .resources file! It was possibly truncated.", e);
}
}
+ void CreateResourceInfo (long position, ref ResourceInfo info)
+ {
+ long pos = position + nameSectionOffset;
+
+ reader.BaseStream.Seek (pos, SeekOrigin.Begin);
+
+ // Resource name
+ int len = Read7BitEncodedInt ();
+ byte[] str = new byte [len];
+
+ reader.Read (str, 0, len);
+ string resourceName = Encoding.Unicode.GetString (str);
+
+ long data_offset = reader.ReadInt32 () + dataSectionOffset;
+ reader.BaseStream.Seek (data_offset, SeekOrigin.Begin);
+ int type_index = Read7BitEncodedInt ();
+
+ info = new ResourceInfo (resourceName, reader.BaseStream.Position, type_index);
+ }
+
/* Cut and pasted from BinaryReader, because it's
* 'protected' there
*/
return ret;
}
- private string ResourceName(int index)
- {
- lock(this)
- {
- long pos=positions[index]+nameSectionOffset;
- reader.BaseStream.Seek(pos, SeekOrigin.Begin);
-
- /* Read a 7-bit encoded byte length field */
- int len=Read7BitEncodedInt();
- byte[] str=new byte[len];
-
- reader.Read(str, 0, len);
- return Encoding.Unicode.GetString(str);
- }
- }
-
- // TODO: Read complex types
object ReadValueVer2 (int type_index)
{
switch ((PredefinedResourceType)type_index)
return new TimeSpan(reader.ReadInt64());
case PredefinedResourceType.ByteArray:
- throw new NotImplementedException (PredefinedResourceType.ByteArray.ToString ());
+ return reader.ReadBytes (reader.ReadInt32 ());
case PredefinedResourceType.Stream:
- throw new NotImplementedException (PredefinedResourceType.Stream.ToString ());
+ // FIXME: create pinned UnmanagedMemoryStream for efficiency.
+ byte [] bytes = new byte [reader.ReadUInt32 ()];
+ reader.Read (bytes, 0, bytes.Length);
+ return new MemoryStream (bytes);
}
type_index -= (int)PredefinedResourceType.FistCustom;
- return ReadNonPredefinedValue (types[type_index]);
+ return ReadNonPredefinedValue (Type.GetType (typeNames[type_index], true));
}
object ReadValueVer1 (Type type)
throw new InvalidOperationException("Deserialized object is wrong type");
}
return obj;
- }
+ }
- private object ResourceValue(int index)
+ void LoadResourceValues (ResourceCacheItem[] store)
{
- lock(this)
- {
- long pos=positions[index]+nameSectionOffset;
- reader.BaseStream.Seek(pos, SeekOrigin.Begin);
-
- /* Read a 7-bit encoded byte length field */
- long len=Read7BitEncodedInt();
- /* ... and skip that data to the info
- * we want, the offset into the data
- * section
- */
- reader.BaseStream.Seek(len, SeekOrigin.Current);
-
- long data_offset=reader.ReadInt32();
- reader.BaseStream.Seek(data_offset+dataSectionOffset, SeekOrigin.Begin);
- int type_index=Read7BitEncodedInt();
+ ResourceInfo ri;
+ object value;
+
+ lock (readerLock) {
+ for (int i = 0; i < resourceCount; i++) {
+ ri = infos [i];
+ if (ri.TypeIndex == -1) {
+ store [i] = new ResourceCacheItem (ri.ResourceName, null);
+ continue;
+ }
-#if NET_2_0
- if (resource_ver == 2)
- return ReadValueVer2 (type_index);
-#endif
- if (type_index == -1)
- return null;
+ reader.BaseStream.Seek (ri.ValuePosition, SeekOrigin.Begin);
+ if (resource_ver == 2)
+ value = ReadValueVer2 (ri.TypeIndex);
+ else
+ value = ReadValueVer1 (Type.GetType (typeNames [ri.TypeIndex], true));
- return ReadValueVer1 (types[type_index]);
+ store [i] = new ResourceCacheItem (ri.ResourceName, value);
+ }
+ }
+ }
+
+ internal UnmanagedMemoryStream ResourceValueAsStream (string name, int index)
+ {
+ ResourceInfo ri = infos [index];
+ if ((PredefinedResourceType)ri.TypeIndex != PredefinedResourceType.Stream)
+ throw new InvalidOperationException (String.Format ("Resource '{0}' was not a Stream. Use GetObject() instead.", name));
+
+ lock (readerLock) {
+ reader.BaseStream.Seek (ri.ValuePosition, SeekOrigin.Begin);
+
+ // here we return a Stream from exactly
+ // current position so that the returned
+ // Stream represents a single object stream.
+ long slen = reader.ReadInt32();
+ UnmanagedMemoryStream basePtrStream = reader.BaseStream as UnmanagedMemoryStream;
+ unsafe {
+ if (basePtrStream != null) {
+ return new UnmanagedMemoryStream (basePtrStream.PositionPointer, slen);
+ } else {
+ IntPtr ptr = Marshal.AllocHGlobal ((int) slen);
+ byte* addr = (byte*) ptr.ToPointer ();
+ UnmanagedMemoryStream ms = new UnmanagedMemoryStream (addr, slen, slen, FileAccess.ReadWrite);
+ // The memory resource must be freed
+ // when the stream is disposed.
+ ms.Closed += delegate (object o, EventArgs e) {
+ Marshal.FreeHGlobal (ptr);
+ };
+
+ byte [] bytes = new byte [slen < 1024 ? slen : 1024];
+ while (slen > 0 ) {
+ int x = reader.Read (bytes, 0, (int)Math.Min (bytes.Length, slen));
+
+ if (x == 0)
+ throw new FormatException ("The resource data is corrupt. Resource stream ended");
+
+ ms.Write (bytes, 0, x);
+ slen -= x;
+ }
+ ms.Seek (0, SeekOrigin.Begin);
+ return ms;
+ }
+ }
}
}
{
Dispose(true);
}
+
+#if NET_4_0
+ public void Dispose ()
+#else
+ void IDisposable.Dispose ()
+#endif
+ {
+ Dispose(true);
+ }
public IDictionaryEnumerator GetEnumerator () {
if (reader == null){
{
return ((IResourceReader) this).GetEnumerator();
}
-
- void IDisposable.Dispose ()
+
+ public void GetResourceData (string resourceName, out string resourceType, out byte [] resourceData)
{
- Dispose(true);
+ if (resourceName == null)
+ throw new ArgumentNullException ("resourceName");
+ ResourceEnumerator en = new ResourceEnumerator (this);
+ while (en.MoveNext ())
+ if ((string) en.Key == resourceName) {
+ GetResourceDataAt (en.Index, out resourceType, out resourceData);
+ return;
+ }
+ throw new ArgumentException (String.Format ("Specified resource not found: {0}", resourceName));
+ }
+
+ private void GetResourceDataAt (int index, out string resourceType, out byte [] data)
+ {
+ ResourceInfo ri = infos [index];
+ int type_index = ri.TypeIndex;
+ if (type_index == -1)
+ throw new FormatException ("The resource data is corrupt");
+
+ lock (readerLock) {
+ reader.BaseStream.Seek (ri.ValuePosition, SeekOrigin.Begin);
+ long pos2 = reader.BaseStream.Position;
+
+ // Simply read data, and seek back to the original position
+ if (resource_ver == 2) {
+ if (type_index >= (int) PredefinedResourceType.FistCustom) {
+ int typenameidx = type_index - (int)PredefinedResourceType.FistCustom;
+ if (typenameidx >= typeNames.Length)
+ throw new FormatException ("The resource data is corrupt. Invalid index to types");
+ resourceType = typeNames[typenameidx];
+ }
+ else
+ resourceType = "ResourceTypeCode." + (PredefinedResourceType) type_index;
+ ReadValueVer2 (type_index);
+ } else {
+ // resource ver 1 == untyped
+ resourceType = "ResourceTypeCode.Null";
+ ReadValueVer1 (Type.GetType (typeNames [type_index], true));
+ }
+
+ // FIXME: the data size is wrong.
+ int datalen = (int) (reader.BaseStream.Position - pos2);
+ reader.BaseStream.Seek (-datalen, SeekOrigin.Current);
+ data = new byte [datalen];
+ reader.BaseStream.Read (data, 0, datalen);
+ }
}
private void Dispose (bool disposing)
reader.Close();
}
}
-
+
reader=null;
hashes=null;
- positions=null;
- types=null;
+ infos=null;
+ typeNames=null;
+ cache = null;
}
- internal class ResourceEnumerator : IDictionaryEnumerator
+ internal sealed class ResourceEnumerator : IDictionaryEnumerator
{
private ResourceReader reader;
private int index = -1;
- private bool finished = false;
+ private bool finished;
- internal ResourceEnumerator(ResourceReader readerToEnumerate){
+ internal ResourceEnumerator(ResourceReader readerToEnumerate)
+ {
reader = readerToEnumerate;
+ FillCache ();
}
- public virtual DictionaryEntry Entry
+ public int Index
{
+ get { return index; }
+ }
+
+ public DictionaryEntry Entry {
get {
if (reader.reader == null)
throw new InvalidOperationException("ResourceReader is closed.");
if (index < 0)
throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
- DictionaryEntry entry = new DictionaryEntry();
- entry.Key = Key;
- entry.Value = Value;
- return entry;
+ return new DictionaryEntry(Key, Value);
}
}
- public virtual object Key
+ public object Key
{
get {
if (reader.reader == null)
throw new InvalidOperationException("ResourceReader is closed.");
if (index < 0)
throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
- return (reader.ResourceName(index));
+
+ return reader.cache [index].ResourceName;
}
}
- public virtual object Value
+ public object Value
{
get {
if (reader.reader == null)
throw new InvalidOperationException("ResourceReader is closed.");
if (index < 0)
throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
- return(reader.ResourceValue(index));
+ return reader.cache [index].ResourceValue;
+ }
+ }
+
+ public UnmanagedMemoryStream ValueAsStream
+ {
+ get {
+ if (reader.reader == null)
+ throw new InvalidOperationException("ResourceReader is closed.");
+ if (index < 0)
+ throw new InvalidOperationException("Enumeration has not started. Call MoveNext.");
+ return(reader.ResourceValueAsStream((string) Key, index));
}
}
- public virtual object Current
+ public object Current
{
get {
/* Entry does the checking, no
}
}
- public virtual bool MoveNext ()
+ public bool MoveNext ()
{
if (reader.reader == null)
throw new InvalidOperationException("ResourceReader is closed.");
if (++index < reader.resourceCount){
return true;
}
- else {
- finished=true;
- return false;
- }
+
+ finished=true;
+ return false;
}
public void Reset () {
index = -1;
finished = false;
}
+
+ void FillCache ()
+ {
+ if (reader.cache != null)
+ return;
+
+ lock (reader.cache_lock) {
+ if (reader.cache != null)
+ return;
+
+ ResourceCacheItem[] resources = new ResourceCacheItem [reader.resourceCount];
+ reader.LoadResourceValues (resources);
+ reader.cache = resources;
+ }
+ }
} // internal class ResourceEnumerator
} // public sealed class ResourceReader
} // namespace System.Resources