Reading Data from Local or External Library Resources
Techniques for managing static data associated with your application
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:
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 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.
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!