|
Introduction
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:
- 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.
- You cannot determine the resource contents of a file.
- 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 (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 demonstrations. 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.
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 ExtRes demonstration project for external data, and then you write it to
a temporary file. It is simple to use the LoadPicture function on the temporary file from there
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!
Back to top
Back to Source Code
  |