Reading Data from Local or External Library Resources

Techniques for managing static data associated with your application

Somehow there's never enough disk space on my system.

Often your application will have associated data, such as pictures, sounds, static data and so forth you need to ship with it. If you are localizing your application then you also need to be able to provide alternative text strings for menus, messages, labels on so forth. Resources are a great way to package up all of this extra data into a single file and read it efficiently at run time. Not just that, but by putting the data into a resource file you significantly reduce the chances of someone tampering with your files and replacing them with their own "humourous" versions.

VB has reasonable resource support with the LoadResData function, however, it lacks some features:

  1. You can only load data from the local module, i.e. you cannot put your resources in an external DLL and load them from there.
  2. You cannot determine the resource contents of a file.
  3. It is not obvious how you can use the function to load an arbitrary resource type, such as JPEG file.

This article covers the code and tools you can use to overcome these limitations in a Visual Basic application.

Step Up to Resources

There are two ways of getting resources into a VB application.

  • The Resource Compiler
    The traditional method is to create a Resource Compiler (.RC) file and compile it into a Resource .RES file using the Microsoft Resource Compiler (RC.EXE).
  • IDE Support for Resource Files
    VB 6 has a visual resource compiler built into the IDE. For VB5 you can download a resource compiler add-in from the Visual Basic area at MSDN. Note: if you don't have RC.EXE then this is also included in the VB5 resource compiler add-in download.

The second method is a lot easier to manage, as there is an IDE to add and remove the files. Unfortunately, in my experience, it does not always seem to create resources which can be read by other tools, or even straight API calls. I don't know if this is always true, but if you're trying to add a document icon (say to support Document File Associations) which can be read by Windows then I always needed to use RC.EXE.

The important thing to note is it is really simple to use RC.EXE. Simply place RC.EXE and RCDLL.DLL into your system's path, or set the path to point to the directory these files live in, write your .RC file and then compile it with this command line:

   RC /r /fo [Output File] [Input File]

For more details, read the article "Using RC.EXE".

Loading Resources From an External Module

Unlike Visual Basic, The Windows API does not differentiate between loading resources from the local application or from an external module. The only thing it requires is a handle to the module where it will find the resource. For the local module, this is the value returned from App.hInstance. For an external binary, you need to load the file using the API LoadLibrary function in order to get a handle to the data. Paste the code below into a class module, then you have a simple means of loading an external EXE, OCX or DLL as a library, accessing the hModule handle and ensuring the resource is unloaded when you have finished with it:

Private Declare Function LoadLibraryEx Lib "kernel32" Alias "LoadLibraryExA" _
   (ByVal lpLibFileName As String, _
    ByVal hFile As Long, _
    ByVal dwFlags As Long) As Long
' Missing from VB API declarations:
Private Const DONT_RESOLVE_DLL_REFERENCES = &H1&
Private Const LOAD_LIBRARY_AS_DATAFILE = &H2&
Private Const LOAD_WITH_ALTERED_SEARCH_PATH = &H8&
Private Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" _
    (ByVal lpLibFileName As String) As Long

Private Declare Function FreeLibrary Lib "kernel32" _
     (ByVal hLibModule As Long) As Long

Private m_sFileName As String
Private m_hMod As Long

Public Property Get Filename() As String
   Filename = m_sFileName
End Property

Public Property Let Filename(ByVal sFileName As String)
   ClearUp
   m_sFileName = sFileName
   If m_sFileName <> "" Then
      m_hMod = LoadLibraryEx(m_sFileName, 0, 0)
      If (m_hMod = 0) Then
         Err.Raise vbObjectError + 1048 + 1, _
             App.EXEName & ".cLibrary", WinError(Err.LastDllError)
      End If
   End If
End Property

Public Property Get hModule() As Long
   hModule = m_hMod
End Property

Private Sub ClearUp()
   If Not (m_hMod = 0) Then
      FreeLibrary m_hMod
   End If
   m_hMod = 0
   m_sFileName = ""
End Sub


Private Sub Class_Terminate()
   ClearUp
End Sub

Putting your resources in a external module is then simple: simply create a VB ActiveX DLL project, add your .RES file to it, compile it and that's it! Ready to use.

Determing The Resource Contents Of a File

To do this you need to able to call the Win32 API functions EnumResourceTypes to get the different types of resources and EnumResourceNames to get the names of the resources. The code to do this is fundamentally simple but a bit of a pain to get working correctly (read: ensuring the ByVal and ByRefs are in the right place!). The finished product is demonstrated in the cResources class of the demonstration download. The biggest problem with the code is that the API has a strange way of reusing String parameters as Long values and vice-versa, which certainly doesn't look nice to anyone who has had the luxury of the variant before. I'm sure it saves, ooh, at least twenty-five bytes on disk for the typical project though.

WinHelp Resource ID 119

Do It Yourself: I was hoping for an animated cursor, but this is how they did it: Bitmap Resources 119 and 120 from WinHlp32.EXE...

The source code also demonstrates helper functions to read bitmaps, icons, cursors and binary data as well as to save them to disk in the mResource module.

Loading Arbitary Resources

Whilst VB provides you with a method to read a bitmap from a resource file, that can (literally) add Megabytes to the size of your application. What you want to do is to store the picture in a smaller format, such as JPG (or GIF, if you really have to). The problem is that VB doesn't allow you to read in these formats with the LoadResPicture function.

There are two solutions to this problem: one is simple, and the other is much harder. For both you add the picture data in JPG or GIF format as a binary resource format. In the simple method, you read the binary data using either LoadResPicture for local data or the techniques in the mResource module of the demonstration project for external data, and then you write it to a temporary file. It is simple then to use the LoadPicture function on the temporary file and clear up the file. The TempRes demonstration project demonstrates this technique.

The harder technique would be to implement the IPersistStream function to allow a StdPicture object to deserialise itself directly from a byte array. I haven't figured out how to do it yet, but if anyone has any tips on doing this, I'd love to hear!