The BlackBeltVB.com Newsletter

Volume 1 Issue 2
December 2000
DLL Resources
How to load resources from a DLL
Code listing for this Issue

DLL Resources

by Jerry Beers

Every project has resources: icons for the forms, text for the controls and form captions, maybe images for the background or just to improve the look, possibly even sound files or custom data. The simplest applications can define these resources directly. You can define a form's icon and caption in design mode, other controls have properties that can also be set in design mode, and data files or a database can store the sound or other data.

There is another option. Resource files can be used to store these resources in a central location. This method provides several advantages: it allows for easy maintenance of all your resources in one place, if you are developing for multiple languages it greatly simplifies this development, it allows for changes to the resources without the need to recompile a project, and it is more efficient since the resources are loaded as needed and not automatically with the form. It also provides a convenient package for all your binary or custom data. It is much harder for a user to accidentally remove resources from your application than to accidentally delete a data file.

There are some disadvantages to attaching all your resources to your application exe. First, if you are trying to use the multi-language advantages, you will have to create a new exe for each language. Second, the size of the exe will increase for each additional piece of data included. If you plan to run the complete application from an isolated system, this may not concern you. After all, you have to store the resource data somewhere. The fact that it is attached to the exe makes no difference. However, if you want to run the exe from the local system and keep the resource data on a CD or network, having all the data with the exe presents a problem. This article will show you how to keep the advantages of using a resource file while disconnecting it from the exe.

The key to this trick is two-fold. First, a dll can have a resource file attached to it just like an exe file can. Second, a dll can be loaded just for it's resources and not for it's code. So we will create a dll with nothing in it but the resource file. This dll can then be left on a CD or network drive while the application is installed locally. Also, an application can programmatically determine which of several dll files to load to facilitate switching between languages.

The first step then is to create the dll. Create a new project and pick ActiveX dll for the project type. This will start the project with a class with no code in it. We need that class in order to compile the dll, but it is fine with no code in it. Next, add the resource file to the project. For this sample, the resource file has a custom resource called "LISTDATA." The custom type is called "COMBODATA." We also have a custom resource called "MYSOUND" of type "WAVDATA." Finally, we have a bitmap resource called "SOMEIMG." Compile this project and you now have a loadable dll with resources attached.

The rest of the work will be done back in the main application project. First we need a variable to keep track of the hInstance for the loaded library. Note for the ambitious: you can have more than one library at a time loaded for it's resources and the hInstance variable is the key to each library. For this article, we will assume only one at a time is being loaded. We also keep track of the full path and name of the dll loaded.

    Dim hResLib As Long
    Dim aCurrentPath As String

Then we need a routine to load the dll. The only parameter is the full path of the dll to load. We will make it a function that returns back a true if it is successful.

    ' Function to load a resource dll - i.e. a dll not used for any code 
    ' or functions, but just for its resources
    Public Function LoadResDll(aDllPath As String) As Boolean
        Dim lErr As Long
    
        LoadResDll = False
    
        ' if no dll was specified, return a false
        If aDllPath = "" Then LoadResDll = False: Exit Function
    
        ' if the library requested is the one already loaded, 
        ' just return a true
        If aDllPath = aCurrentPath Then LoadResDll = True: Exit Function
    
        aCurrentPath = ""
    
        ' free the old library before opening a new one
        If hResLib <> 0 Then
            If FreeLibrary(hResLib) = 0 Then
                lErr = GetLastError
                MsgBox "Error unloading resources.  Error: " & CStr(lErr)
                Exit Function
            End If
        End If
    
        ' load the dll with the resources
        ' the third parameter tells VB that we are only using resources 
        ' and not code from this dll
        ' The return value from this is the hInstance that we use
        ' as the key to this library
        hResLib = LoadLibraryEx(aDllPath, 0, LOAD_LIBRARY_AS_DATAFILE)
        If hResLib = 0 Then
            lErr = GetLastError
            MsgBox "Could not load resource dll.", _
                   vbCritical + vbOKOnly, _
                   "Resource not found.  Error: " & CStr(lErr)
            Exit Function
        End If
    
        ' save the path of the library we have loaded
        aCurrentPath = aDllPath
    
        LoadResDll = True
    End Function

Of course that is only half of the equation. Since we want to get resources from the loaded dll and not from a resource file attached to the application exe, loading the resources is a bit more complicated than the LoadResPicture, LoadResString, and LoadResData functions that are normally used. To make it simpler, we will write a function that takes the name of resource and the name of its custom type and returns a string containing the resource data. As a side note, this technique was developed relying heavily on topics discussed in Hardcore Visual Basic by Bruce McKinney. I would recommend this book to any serious VB developer. It is available online at www.mvps.org/vb/hardcore, but I bought the book and would recommend that you do too. This function is specific to loading string data, but the function can be modified to return other types of data if necessary.

    ' Function to load a resource of a custom type from the resource dll
    Public Function LoadCustomString(aResName As String, _
                                     aResType As String) As String
        Dim hRes As Long, hmemRes As Long, cRes As Long
        Dim pRes As Long, aDat As String
    
        LoadCustomString = ""
    
        If hResLib = 0 Then
            ' resource dll has not been loaded yet
            Debug.Assert False  ' this really should never happen
            Exit Function
        End If
    
        ' FindResourceStrStr is an alias for FindResource
        hRes = FindResourceStrStr(hResLib, aResName, aResType)
        If hRes = 0 Then Exit Function

        ' Allocate memory block and get its size
        hmemRes = LoadResource(hResLib, hRes)
        cRes = SizeofResource(hResLib, hRes)

        ' Lock it to get pointer
        pRes = LockResource(hmemRes)

        ' Copy memory block to string
        aDat = Space$(cRes + 1)
        CopyMemory ByVal aDat, ByVal pRes, cRes

        ' Free resource (no need to unlock)
        Call FreeResource(hmemRes)
    
        ' Return string
        LoadCustomString = aDat
    End Function

Finally we have the tools we need to load the library and lookup the resources. In the Form_Load event handler, we call the routine to load the dll.

    Private Sub Form_Load()
        Dim aResDll As String
        aResDll = App.Path & "dllres.dll"
        '-- Load the resource dll
        If Not LoadResDll(aResDll) Then
            Unload Me
            Exit Sub
        End If
    End Sub

Now we can use the dll resources to load a combo box list. Add a combo box and command button to your form. Name the combo box "cbxTest" and the command button "cmdFill." Add the following code in the cmdFill_Click event handler:

    Private Sub cmdFill_Click()
        Dim aDat As String
    
        cbxTest.Clear
    
        '-- Load the combo data from the resource file
        aDat = LoadCustomRes("LISTDATA", "COMBODATA")
        If aDat = "" Then
            MsgBox "Error accessing combo data.", _
                   vbCritical, "Resource Error"
        Else
            Dim iLoop As Integer
            Dim aLines() As String
            aLines() = Split(aDat, vbCrLf)
            For iLoop = LBound(aLines, 1) To UBound(aLines, 1)
                cbxTest.AddItem aLines(iLoop)
            Next
        End If
    End Sub

This event handler is loading the string data from the resource dll, splitting it into lines, and adding those lines to the combo box list.

Of course, when you load a library like that, you should always free it when you are done:

    Private Sub Form_Unload(Cancel As Integer)
        '-- Unload the resource dll
        If hResLib <> 0 Then FreeLibrary hResLib
    End Sub

This sample project shows how you can load string data from a resource library, but the technique is the same for binary data like a bitmap or wave data. A sample of this is included in the code for this article, but since the principles are the same, it is not discussed.

We now have a method that allows us to separate the resource data from the application executable. Just as changing the resource dll can easily change the language of an application, the interface of an application could just as easily be changed. This method could be used to create a "skinnable" interface to an application.

Finally, as a footnote, I don't think VB makes working with resources very easy. If you are going to be doing much editing of the resource file after you first create it, I would highly recommend that you create a resource script (.rc file) and compile it into a resource file (.res) for VB. The sample includes a resource script for the sample resource file. And although it is distributed with VB, I have also included the utility for compiling resource scripts. More information on resource scripts and the types recognized by the compiler can be found on the MSDN website.

Article Code


Back to Newsletters
Copyright © 2000 by Jerry Beers, All Rights Reserved Worldwide.
Nothing on this web site may be reproduced, in any form, without express written consent.