Using Windows Resource Files with D2

From D Wiki
Jump to: navigation, search

Prerequisites

Name Info
Windows SDK Windows header files and C/C++ example code.
WindowsAPI bindings D header files with prototypes of most of the Windows API functions.
RCC Resource Compiler Download the Basic Utilities package (5th package from above).
ResEdit GUI Resource Editor - optional, but highly recommended. You will have to add search paths to the include directories of Windows header files for this application to work.]

Creating Resource Files

You can use a resource editor such as ResEdit to create the .rc resource file, or write the resource file manually by following the syntax rules defined on these MSDN pages: Resource File Syntax and Resource Types.

A separate C header file (usually resource.h) should be defined which lists the unique IDs of all the resources in the .rc file. This is either generated automatically by a resource editor, or you'll have to write the header file yourself.

Compiling Resource Files

Use rcc to compile the resource file, for example:

rcc -32 -D_WIN32 -I"C:\Program Files\Microsoft Visual Studio 10.0\VC\atlmfc\include" resource.rc

-D_WIN32 defines the _WIN32 macro, which might be needed in existing RC files generated by Visual Studio, otherwise RCC could attempt to include the win16 header files.

-I adds a directory search path for Windows header files. Double quotes are needed for paths with spaces. These include paths might be:

    -I"C:\Program Files\Microsoft Visual Studio 10.0\VC\atlmfc\include"
    -I"C:\Program Files\Microsoft SDKs\Windows\v7.1\Include"

Set these include paths accordingly to where you've installed the Windows SDK. The Visual Studio directory might be needed if the resource file was generated by VS. You could try compiling without the VS headers and see if that works for you.

The resource file will typically #include a resource.h header file, and RCC must be able to find it when you invoke it. After compiling the resource file with RCC, use HTOD to convert the header file to a .d file:

    htod resource.h

Import the resulting header.d file into your project's source module. This will give your project access to the unique resource IDs.

Using RC Instead Of RCC

Sometimes RCC will fail to compile a resource file. In this case you might want to try using Microsoft's Resource Compiler, which should be distributed with the Windows SDK. RC is typically installed in the C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin directory. An example command to compile a resource file is:

rc /i"C:\Program Files\Microsoft SDKs\Windows\v7.1\Include" /i"C:\Program Files\Microsoft Visual Studio 10.0\VC\include" /i"C:\Program Files\Microsoft Visual Studio 10.0\VC\atlmfc\include" resource.rc

You will have to experiment with the include paths, since some needed headers might be located in different directories. Don't forget to put double quotes for paths that have spaces.

Writing Code That Uses Resource Files

Make sure you specify the correct name of a resource when using it in your code. Names are case-insensitive.

Download the example project here.

Walktrough:

We have a header file that lists the unique IDs for every resource:

#define IDC_STATIC                      -1
#define IDM_APP_ABOUT                   40000
#define ID_MENUITEM40020                40020

Our menu resource is called AboutWindow. We use it in a windows class:

WNDCLASS wndclass;
wndclass.lpszMenuName  = "AboutWindow";
RegisterClass(&wndclass);

The resource.rc file has this definition:

ABOUTWINDOW MENU DISCARDABLE 
BEGIN
    POPUP "&Help"
    BEGIN
        MENUITEM "&About About1...",            IDM_APP_ABOUT
    END
END    

Inside the window procedure we check if the menu is invoked, and if so we invoke a dialog box:

extern(Windows)
LRESULT WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        case WM_COMMAND:
        {
            switch (LOWORD(wParam))
            {
                case IDM_APP_ABOUT:    
                    DialogBox (ghInstance, "AboutBox", hwnd, &AboutDlgProc);  
                    break;
                default:
            }
        }
        default:
    }    
    
    return DefWindowProc(hwnd, message, wParam, lParam);
}

We need another resource for the dialog box, named AboutBox. In the resource.rc file, the dialog box is defined as:

ABOUTBOX DIALOG DISCARDABLE  32, 32, 180, 102
STYLE DS_MODALFRAME | WS_POPUP
FONT 8, "MS Sans Serif"
BEGIN
    DEFPUSHBUTTON   "OK",IDOK,66,81,50,14
    CTEXT           "About1",IDC_STATIC,40,12,100,8
    CTEXT           "About My Program ",IDC_STATIC,7,40,166,8
    CTEXT           "Made by ..",IDC_STATIC,7,52,166,8
END    

We also need a dialog box procedure;

extern (Windows)
BOOL AboutDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        case WM_INITDIALOG:
            return TRUE;

        case WM_COMMAND:
        {
            switch (LOWORD (wParam))
            {
                case IDOK:
                case IDCANCEL:
                    EndDialog (hDlg, 0);
                    return TRUE;
                default:
            }
            break;
        }
        default:
    }
    
    return FALSE;
}

If you're using ResEdit, you can invoke a code-generating feature from the menu File->Code Preview->c/c++, which creates sample C code that uses your resources. You can easily translate this code to D. Note that resource files generated with ResEdit might need to be compiled with Microsoft's resource compiler.

Important Notes

Unicode

For Windows unicode functions which expect string arguments, Windows expects pointers to zero-terminated UTF16 strings. To convert any D string into a zero-terminated UTF16 string you can use the std.utf.toUTF16z function, e.g.:

someWindowsFunction("widestring".toUTF16z);.

For non-Unicode WinAPI calls you should use the std.string.toStringz function. Note that you don't have to call this function for string literals, since they already have a null terminator appended to them:

string str = "normal string";
someWindowsFunction(str.toStringz);
someWindowsFunction("normal string");

Version Resources

An example version resource:

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif /* Prevents winsock.h from being included in windows.h */
#include <windows.h> /* Needed for a few definitions, can be excluded if you use values instead. */

VS_VERSION_INFO VERSIONINFO
FILEVERSION    	1,0,0,0
/* PRODUCTVERSION 	1,0,0,0 */
FILEOS         	VOS__WINDOWS32
FILETYPE       	VFT_APP
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
        BLOCK "040904E4"
        BEGIN
            VALUE "CompanyName",      "My Company."
            VALUE "FileDescription",  "A Win32 program."
            VALUE "FileVersion",      "1.0.0.0"
            VALUE "ProductName",      "The product name."
            VALUE "ProductVersion",   "1.0"
            VALUE "LegalCopyright",   "My Company."
        END
    END

    BLOCK "VarFileInfo"
    BEGIN
        VALUE "Translation", 0x409, 1252
    END
END


Compiling Code That Uses Resource Files

If you want to use the WindowsAPI bindings (highly recommended over the slightly outdated std.c.windows.windows module), you will have to define some version identifiers when both building the bindings for the first time, and every subsequent use of the bindings, e.g:

-version=WindowsNTonly -version=Windows2000 -version=WindowsXP -version=Unicode 

Using the -version=Unicode flag means that a function such as CreateWindow will be aliased to the Unicode function CreateWindowW. Otherwise the function would be aliased to the CreateWindowA function. You can still invoke Unicode and non-Unicode functions explicitly if you want to.

You can pass the -L-Subsystem:Windows switch when invoking DMD to remove the console window from your GUI apps. However using the console window and D's various writeln() functions can be useful for debugging purposes.

To compile your project you will have to invoke DMD with your project file, several version identifiers which the WindowsAPI bindings expect, the translated resource header file (translated by htod), and the compiled resource file. If you are using the WindowsAPI bindings, you need to add an include switch with the path of the bindings' win32 directory.

Here's an example of compiling a project that uses resources:

dmd -I. win32.lib -version=WindowsNTonly -version=Windows2000 -version=WindowsXP project.d RESOURCE.d resource.res