Windows resources and libraries

Download the res2cpp tool (110 kb, source included)

Altough the tool I wrote might by usefull with other compilers/resource editors, this tutorial is written for Microsoft Visual C++ .NET

As you might already know, windows resources and static libraries don't go together very well. Altough the .res file (a compiled .rc file) can be included in a library, it doesn't have any symbols, so you won't be able to access the resources once they are in the library (and so it's only useless bloat to your library). The solution I first used is that I just kept the .res file as a seperate file, and I included that file along with the .lib in the final project. This does work, but it has some drawbacks. First of all, it's not nice at all that you have two files. Especially if you're going to distribute the library to third parties. Also, to prevent the warning that the .res file has no symbols in it, I had to excluded it from the build process, and I had to compile it explicitely each time I changed a resource. And a problem I didn't have yet, but that will happen for sure is that if you use multiple resources, there might be different resources with the same ID.

The tool

It's obvious that we need a better way. So I wrote a tool that converts a .res file to a C++ source and header file, which contain the resource data, and the functions to use those resources. The functions are similar to the standard windows functions, only, they are placed in a seperated namespace, to seperate them from the standard windows functions. (And if you want to use multiple resource files, you just use a different namespace for each resource file, and you won't have the problem of id collisions any more).

So to create a dialog it's just:

OurNameSpace::DialogBoxParam(hInstance,MAKEINTRESOURCE(IDD_DIALOG),0,(DLGPROC)DlgProc,0);
Note that DialogBox is just a macro which is replaced with DialogBoxParam, so those "functions" will work too, altough they are not included in the header file.

To create those source files, you need the the res2cpp tool, which you can download from here. This tool can be used to create two files. The header files, which contains the prototypes of the functions to access your resources, and the source file, which contains the bodies of those functions, as well as the actual resource data.

To create the header file:

res2cpp -hheader.h -nOurNameSpace

This line creates the file header.h, with the namespace OurNameSpace. You don't need to know anything about the resources at all, the only thing that's important is that you'll use the same namespace for your .cpp file.

To create the source file:

res2cpp -rcompiledresource.res -sresourcedata.cpp -nOurNameSpace

This needs the compiled resource file (the .res file, not the .rc file), and will create a C++ source file, which you can include in your project instead of the .res file. Because this file includes the actual resource data, you have to recreate this file everytime your resources change.

Use it with the Visual C++ .NET IDE

It's nice that we can include resources in a library now, but it's very annoying that we would have to execute those commands each time we rebuild our project, or at least each time a resource has changed. Luckily we can do better. The best thing would be if we could automatically execute the required commands to build our resources. Well, the good thing is that there is an option in Visual C++ that exactly does that, Custom Build. The bad thing is that I couldn't manage to get that working with a file type that already has it's own compiler.
I don't know if it's a bug in VC++, or if there's another reason for this, but if I select Custom Build in the properties dialog of the .res file, it will jump back to resource compiler once I open the properties dialog again. I have this problem with all the types that have their own compiler, like .rc and .cpp, while Custom build does work with unknown types, like .asm *
Wether it's a bug, or if there's a reason for it, there is a solution, with a dummy file of an unknown type, on which we will perform the Custom Build.

But first, the header file. We only have to create the header file once, so it shouldn't be too much to just do that on the command line. Once we have that file, we just add it to the project, and we don't have to look back at it any more.

Before we will create the source file, we will eliminate that annoying warning that the resource file doesn't have any symbols, we get each time we build the project. We can do that in the properties dialog of the .rc file, by setting the Exclude From Build field to Yes. Of course we still have to compile the .rc file, but we will do that in the Custom Build step of the dummy file we are going to create now.
That dummy file is just a file with no content at all, and in it's Custom Build, we will just execute some commands, not to build that file, but to build the .rc file. So add a new file to your project, a text file is fine, and go to the properties page of that file. The Tool field should already be at Custom Build. Now go to the General page, to fill in the fields. The first field is the command line which will be executed to build the file. Click on the ... button next to that field to open a multiline window. In that window, type:

rc test.rc c:\res2cpp\res2cpp.exe -rtest.res -sresources.cpp -nOurNameSpace cl resources.cpp /c /MLd /EHsc
The first line will compile test.rc to test.res. (Of course you should use the name of your own .rc file here)
The second line executes the res2cpp tool, to convert the .res file we just created to a c++ source file.
The last line compiles that .cpp file to an object file (resources.obj in this case). The /c switch tells the compiler to only compile the file, and don't link it. the /MLd switch tells the compiler which runtime library you want to use. In this case it's Single-threaded Debug. The easiest way to get this right is just look at the command line page of the properties dialog of some other source file of your project. The /EHsc enables C++ exception handling.
The next field, the Outputs field, is to let the linker or librarian know which file the Custom Build tool created. In this case, resources.obj (the compiled resources.cpp).
The next field, Additional Dependencies, is to specify any files that this file depends on. Normally, a file is only rebuild if it has changed, but if you specify any files here, the file is also rebuild if any of those files has changed. By example, with a normal .cpp file, the file would need to be rebuild if one of it's header files has changed. In this case, the file doesn't really depend on itself at all, it only depends on the .rc file, so we specify that file here.

Everything is now properly set up, so let's see how we should use our resources now.
Like normally, we include the "resource.h" file, to define the id values you use (You don't want to use the nummerical id values, instead of IDD_DIALOG1, do you?). You also have to include the header file the res2cpp tool created, to use the special resource functions for this resource. Now you can use the resources like you would do normally, with the only difference that the functions you now use are the ones in the namespace you specified in the res2cpp tool. An example of some code that shows up a dialog:

#include "resource.h" #include "ourresources.h" LRESULT CALLBACK DlgProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam) { switch(msg) { case WM_CLOSE: EndDialog(hWnd,0); break; } return 0; } int main() { OurNameSpace::DialogBox(GetModuleHandle(0),MAKEINTRESOURCE(IDD_DIALOG1),0,(DLGPROC)DlgProc); return 0; }

That's it. It's a bit of a hassle to set it all up, but once it's set up, it works like a charm.

Lieven van der Heide

If you have any questions or comments, or if you just want to let me know that you like this tutorial, feel free to mail me at lieven@quasar3d.nl


* Now that I think of it, the reason for this could be that I have the standard edition of Visual C++. The command line compiler of the proffessional edition is freely available, while the proffessional edition itself costs much more than the standard edition. With Custom Build available for .cpp files, I would be able to use the compiler of the proffessional edition from the IDE of the standard edition, which is of course not what they want.