Click here to Skip to main content
15,886,199 members
Articles / Programming Languages / C#
Article

Runtime Embedded Resource Manager

Rate me:
Please Sign up or sign in to vote.
4.50/5 (2 votes)
1 Jan 20065 min read 70K   653   44   10
A library to help use your project's embedded resources at run-time.

Introduction

When you're organizing your development solution, you can go one of two routes with your non-source files: you can leave your resources external to your executable "loose" on the local file system, or you can compile them into the assembly. The first route allows you to modify the resources easily without having to redistribute code changes, but the second offers a small amount of protection against the modification of those resources, and leads to a tidier distribution.

This article isn't meant to convince you to embed if you don't have the need, but to offer some small help if you do. Reading embedded resources involves tedious and repetitive code, lending itself well to encapsulation in a library. I'll show you how and why I wrote a run-time embedded resource manager to give simplified access to images and sounds in my applications.

Background

Basically, I did this because I grew tired of writing this type of code over and over:

C#
pictureBox1.Image = 
   new Icon(Assembly.GetExecutingAssembly().GetManifestResourceStream(
   "RuntimeResManTest.Icons.CDROM01.ICO")).ToBitmap();

I would inevitably get the case of a character in the name of the resource wrong and waste time troubleshooting the namespace name, class name, folder name, and resource name. To further cut down on the code to read the resource, I'd inevitably cache the current Assembly, and reference it on subsequent resource reads.

Eventually, I realized that what I wanted was a single point of contact for getting a handle to embedded resources, which in my case were always images (BMP, GIF, PNG and JPG), icons (ICO), and sound (WAV) files.

The Code

I wrote a simple class called Resources to act as that single point of contact for instantiation and resource reading. You have to pass the assembly with the embedded resources into the lone constructor, so it can enumerate and categorize them, but that's the only configuration it needs. That parameter will ordinarily be the assembly of the executable that references the library.

C#
private RuntimeResMan.Resources _r = 
   new Resources(Assembly.GetExecutingAssembly());

The constructor of the Resources class passes the supplied Assembly into the internal constructors of the objects that hold the actual resource data:

  • Bitmaps
  • Icons
  • Sounds

These resource collection classes are structured similarly. They contain an ArrayList object that stores BitmapEx, IconEx, and Sound objects, respectively. I created BitmapEx, IconEx, and Sound objects to attach a string Name property to the resource data, for accessing the resource at run-time.

When constructed and passed an Assembly, the Bitmaps object reads the list of embedded resources:

C#
internal Bitmaps(System.Reflection.Assembly target) {
   foreach(string resource in target.GetManifestResourceNames()) {
      string ext = Path.GetExtension(resource).ToLower();
   
      if (ext == ".bmp" || 
            ext == ".gif" ||
            ext == ".jpg" ||
            ext == ".jpeg") 
         _bitmaps.Add(new BitmapEx(resource, 
            (Bitmap)Bitmap.FromStream(
            target.GetManifestResourceStream(resource))));
   }
}

The BitmapEx constructor takes a string with the fully qualified name of the discovered resource, and the Bitmap object that it defines.

C#
public BitmapEx(string name, Bitmap bitmap) {
   string[] tokens = name.Split('.');
   
   _name = tokens[tokens.Length - 2].ToLower();
   _bitmap = bitmap;
}

The unqualified name of the resource is calculated and forced to lowercase, so you won't be tripped up by case when trying to use the Bitmap later at runtime. The extension is chopped too, since it isn't necessary. The framework knows which "flavour" the Bitmap is (GIF, JPG, etc.), so we can drop the extra characters for brevity and simplicity. This assumption would cause a problem if you have multiple resources with the same name that differ by extension (balloon.bmp and balloon.gif in the same assembly), so feel free to append the extension to the name if it suits your purpose.

My Bitmaps class exposes the actual Bitmap object to client code via a string accessed indexer property (the simple name of the embedded resource), so the BitmapEx class is never actually required to be exposed. It stays private in the assembly.

The Icons class works the same way, except that it searches for embedded resources with the extension ".ico", of course. In all other ways, though, it is structured and behaves the same way as the Bitmaps class.

The Sounds class works slightly differently. First, since there is no Sound class in v1.1 of the framework, there's no need to use a contrived name like SoundEx to describe an object that contains both the sound data and the name of the sound. Secondly, since this class exposes a method, Play, to start playing the sound described, the class has to be public. The sound data contained in the .wav file isn't really meaningful outside of the context of the parameter for P/Invoke, so the raw data isn't exposed in the same way as the BitmapEx class exposes a Bitmap object, or the IconEx class exposes an Icon.

Knowing all this, you can now use embedded resources in your projects easily, without having to muck around with reading stream objects. You can see the simple code in the sample project.

C#
private void cmdShowIcon_Click(object sender, System.EventArgs e) {
   pictureBox1.Image = _r.Icons["cdrom01"].ToBitmap();
}
   
private void cmdShowBitmap_Click(object sender, System.EventArgs e) {
   pictureBox1.Image = _r.Bitmaps["beany"];
}

To play a sound, add a .wav file to your project as an embedded resource and play it like this:

C#
Sound s = _r.Sounds["burp"];
   
if (s != null)
   s.Play();

Or, if you fear no NullReferenceException, you can play it even more simply:

C#
_r.Sounds["burp"].Play();

No muss, no fuss.

Caveat

This method is not very memory efficient if you have an awful lot of embedded resources and they're used infrequently in your solution. You wouldn't want to load them all aggressively at project startup, only to have them soaking up memory unnecessarily. You could modify this solution easily to load them only the first time they're required, thus spreading out the memory hit (and the overhead of the discovery process) over the lifetime of your application, rather than incurring the performance penalty up front, but I wanted to keep this example simple.

Obviously, since you're fetching and reading the same copy of the Bitmaps and Icons that were read at instantiation, if you fiddle with them programmatically, all subsequent uses of the embedded resources will get the modified, in-memory versions.

Conclusion

This article wasn't meant as a tutorial on the PlaySound API call, as much fun as it is to play with that particular function. There are a lot of other things you can do with it, like play synchronously, or stop the sound, but you can read more about those capabilities here, and add them to your solution if you're so inclined.

I really like the fact that managing resources this way insulates me from the annoying idiosyncrasies of the framework, like the fact that creating a Bitmap from a stream requires me to call a static method of the Bitmap class, while creating an Icon from a stream more naturally requires me to put the stream into the Icon constructor. Why the difference?

History

  • Jan 1 2006 - Initial revision (Happy New Year).

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Technical Lead
Canada Canada
I'm a graduate of the University of Toronto with a degree in zoology. I'm currently a software development manager with a large Canadian financial institution, and a passionate squash player.

I am a proud daddy to Alex and Sarah.

Comments and Discussions

 
GeneralThis rocks! Pin
jfamme22-Mar-07 13:33
jfamme22-Mar-07 13:33 
GeneralRe: This rocks! Pin
Robb Sadler1-Sep-07 7:58
Robb Sadler1-Sep-07 7:58 
Yes - double rocks! I have been thinking about this and the pain level was just getting high enough that I was going to have to do something.

Many thanks!


GeneralYou wrote how to retrieve Resource... but how do i add them the the assembly Pin
Assaf Koren5-Feb-06 14:18
Assaf Koren5-Feb-06 14:18 
GeneralRe: You wrote how to retrieve Resource... but how do i add them the the assembly Pin
Sean Michael Murphy6-Feb-06 2:46
Sean Michael Murphy6-Feb-06 2:46 
GeneralRe: You wrote how to retrieve Resource... but how do i add them the the assembly Pin
Assaf Koren6-Feb-06 11:11
Assaf Koren6-Feb-06 11:11 
GeneralRe: You wrote how to retrieve Resource... but how do i add them the the assembly Pin
Sean Michael Murphy8-Feb-06 16:42
Sean Michael Murphy8-Feb-06 16:42 
GeneralRe: You wrote how to retrieve Resource... but how do i add them the the assembly Pin
Assaf Koren11-Feb-06 19:24
Assaf Koren11-Feb-06 19:24 
GeneralInteresting but... Pin
Robert Kozak3-Jan-06 9:00
Robert Kozak3-Jan-06 9:00 
GeneralRe: Interesting but... Pin
Sean Michael Murphy3-Jan-06 10:08
Sean Michael Murphy3-Jan-06 10:08 
GeneralRe: Interesting but... Pin
i.tec1-Aug-06 19:02
i.tec1-Aug-06 19:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.