Anton Shepelev
Anton Shepelev

Reputation: 1011

How to read a resource from a .Net assembly without Reflection?

I want to access an embedded resource in a .Net assembly by reading the binary data of the file but without the overhead of loading the assembly via reflection. Is it possible?

Upvotes: 0

Views: 376

Answers (1)

Anton Shepelev
Anton Shepelev

Reputation: 1011

Here is a working solution, naive and inefficient though it may be:

using System;
using System.IO;
using System.Text;
using System.Resources;

namespace ResUtil {

class Substream: Stream
{  Stream Base;
   long   Orig;
   long   Len;

   public override bool CanSeek
   {  get
      {  return Base.CanSeek;  }
   }

   public override bool CanRead
   {  get
      {  return Base.CanRead && Position < Len;  }
   }

   public override bool CanTimeout
   {
      get { return Base.CanTimeout;  }
   }

   public override Boolean CanWrite {
      get {
         return Base.CanWrite;
      }
   }

   public override long Position
   {  get
      {  return Base.Position - Orig;  }
      set
      {  Base.Position = value + Orig;  }
   }

   public override Int64 Length {
      get { return Len; }
   }

   public override void SetLength(Int64 value)
   {  throw new NotSupportedException();  }

   // This is unnecessary, but very desirable, in order to override the default
   // inefficient implementation via Read( Byte[],...):
   // see: https://msdn.microsoft.com/en-us/library/system.io.stream.readbyte%28v=vs.110%29.aspx
   public override Int32 ReadByte()
   {  return Base.ReadByte();  }

   public override Int32 Read(Byte[] buffer,Int32 offset,Int32 count)
   {  return Base.Read( buffer, offset, count );  }

   public override void Write(Byte[] buffer,Int32 offset,Int32 count)
   {  throw new NotSupportedException();  }

   public Substream( Stream s, long l )
   {  Base   = s;
      Orig   = s.Position;
      Len    = l;
   }

   public override long Seek( long ofs, SeekOrigin orig )
   {  if( orig == SeekOrigin.Begin )
      {  ofs = ofs + Orig;  }
      return Base.Seek( ofs, orig );
   }

   public override void Flush()
   {  Base.Flush();  }
}

public static class BinResReader { // TODO: a more generic overload accepting a stream instead of the file path

public static void Get
( string file, string resName, out string type, out byte[] data )
{  FileStream s;
   ResourceReader rr;
   bool found;
   long len;

   type = null;
   data = null;
   s = new FileStream( file, FileMode.Open );
   while( true )
   {  if( !FindResHeader( s, out len ) )
      {  break;  }
      try
      {  Substream ss = new Substream( s, len );
         rr = new ResourceReader( ss );
      }
      catch( BadImageFormatException ) // no resource here
      {  continue;  }

      found = true;
      try
      {  rr.GetResourceData( resName, out type, out data );  }
      catch // resource may not be present
      {  found = false;  } 
      ( ( IDisposable )rr ).Dispose(); // rr.Dispose() won't compile in .NET 3.5
      if( found )
      {  break;  }
      if( !s.CanRead )
      {  break;  }
   }
   s.Dispose();
}

// ---------------- Shift-based eight-byte queue in a UInt64 ----------------

static void Q_LR_In( ref UInt64 data, byte b )
{  data = data << 8;
   data = data | b;
}

static void Q_RL_In( ref UInt64 data, byte b )
{  UInt64 bm;

   data = data >> 8;
   bm   = (ulong)b << 7 * 8;
   data = data | bm;
}

static uint Q_High( ulong data )
{  return (uint)( data );  }

static uint Q_Low ( ulong data )
{  return (uint)( (data >> 32) );  }

// ------------- Find the next location of suspected resource ---------------

// locate next resource in stream s, return its length in bytes
static bool FindResHeader( Stream s, out long len )
{  bool   found = false;

   ulong data;

   System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();

   len     = 0;
   data    = 0xFFFFFFFFFFFFFFFF; // to decrease false alarms

   if( s.Length < 9 )
   {  goto NotFound;  }

   sw.Start();

   /* Buffered reading: */
   const int BufSize = 1024;
   int i;
   byte[] buffer = new byte[BufSize];
   int read;

   while( !found )
   {  read = s.Read( buffer, 0, BufSize );
      if( read == 0 )
      {  break;  }
      for( i = 0; i < read; i++ )
      {  Q_RL_In( ref data, buffer[i] );
         if( Q_Low( data ) != MagicNumber )
         {  continue;  }
         len = Q_High( data );
         if( len > s.Length - s.Position - 4 )
         {  continue;  }
         s.Seek( -4 - read + i + 1, SeekOrigin.Current );
         found = true;
         break;
      }
   }
   sw.Stop();

NotFound:
   return found;
}

// --------- Parsing of the various formats of string resource -----------

public static string GetStr( string file, string resName )
{  return GetStr( file, resName, Encoding.ASCII );  }

public static string GetStr( string file, string resName, Encoding enc )
{  byte[] data;
   string text;
   string type;
   int count;
   int index;
   text = null;
   Get( file, resName, out type, out data );
   if( data != null )
   {  switch( type )
      {  case "ResourceTypeCode.ByteArray":
            count = GetIntBE( data );
            index = 4;
         break;
         case "ResourceTypeCode.String":
            count = Read7BitEncodedInt( data, out index );
         break;
         case "ResourceTypeCode.Stream":
            goto case "ResourceTypeCode.ByteArray";
         default: throw new Exception( "Cannot convert resource type " +
            type + " to string.");
      }
      text = enc.GetString( data, index, count );
   }
   return text;
}

// This is copied from the reference source:
static int Read7BitEncodedInt( byte[] data, out int offset )
{
   int count = 0;
   int shift = 0;
   offset = 0;
   byte b;
   do
   {  b = data[offset];
      offset++;
      count |= (b & 0x7F) << shift;
      shift += 7;
   }
   while ((b & 0x80) != 0);
   return count;
}

// -------------------------- Utility routines ---------------------------

static int GetIntBE( byte[] bytes )
{  return bytes[0] | bytes[1]<<8 | bytes[2]<<16 | bytes[3]<<24;  }

static int GetIntLE( byte[] bytes )
{  return bytes[3] | bytes[2]<<8 | bytes[1]<<16 | bytes[0]<<24;  }

// -------------------------- Initialization -----------------------------
static long  MagicNumber;
static BinResReader()
{  MagicNumber = (uint)ResourceManager.MagicNumber;  }

}}

It wants at least these improvements:

a. read several or all resources in one pass,

b. extract and use the names of embedded resource files during search instead of scanning all.

Another option is to use Mono.Cecilthanks to Arne Vajhøj from microsoft.public.dotnet.languages.csharp.

Upvotes: 2

Related Questions