Click here to Skip to main content
15,881,139 members
Articles / Desktop Programming / Win32
Tip/Trick

The Inner Working of FindResource() and LoadString() Win32 Functions.

Rate me:
Please Sign up or sign in to vote.
4.96/5 (13 votes)
24 Mar 2014CPOL6 min read 48.5K   26   11
A custom implementation of the FindResource() and LoadString() functions with better error indication. Pointing the direction for those who want to learn the binary PE resource format.

Introduction

Who is this tip for?

Before you go on reading the tip, I would like to make it clear that the content of this tip has very limited use only for those who are digging low level in the binary resource tree of PE modules. This tip assumes that you are already familiar with the FindResource() WinAPI function, and that you are learning the PE resource format from another tutorial you found on the internet. I don't have the motivation to write the 1000th PE resource tutorial but I think this tip can still add something complementary to those resource tutorials. If you don't feel that this tip would serve you well, then please don't continue reading on and you spare some free time for yourself to have a coffee+donut combo. Smile | :)

So what's the thing this tip gives?

This tip gives you some source code that searches in the binary resource tree. In my opinion, the best example sources are very short and contain only the essence. This is why this source can come in handy for you. It's much easier to learn from this than taking a look at the sources of a resource editor program with a thousand source files. Another good reason to choose this source to analyze is that these sources emulate the work of the FindResource() WinAPI function so you might know what to expect as a result when you run it!

About the Code

Limitations/Advantages of this code compared to its WinAPI sibling

This code served me well in practice in not too ordinary situations but it is now here for learning purposes and not for use in production code - but who knows - maybe it comes handy for someone. Note that the code works only on Win32, but maybe with minor modifications it could operate in x64 environment as well. Maybe the only thing that needs to be changed is the check for IMAGE_NT_OPTIONAL_HDR64_MAGIC in the PE header, I don't know if there are any changes to the resource format since then because I'm not digging the binaries any more. This FindResource() and LoadString() has more detailed error detection (language) than the Win32 version, you can find more about this in the sources posted.

Usage of the code

I assume that you used the windows version of these functions so I will be brief: First call MyFindResource() and then with the return value you can use MyLoadResource() and MySizeOfResource() like you use these methods of the WinAPI. MyLoadResource() and MySizeOfResource() return the pointer to the resource and the size of the resource that you can use for example with CreateDialogIndirect() (you have to cast the pointer) in case of a dialog resource, or maybe you can put your binary right into the resource part of your module for your own purposes. I myself hate string resources and use text files to do localization work but many programs use string resources for that by copy pasting the tutorials from the internet. :P Since string resources have a special format I wrote a codepiece that handles those and posted it up if you are interested in it. The code was used with Visual C++ 6 but it should work with newer compilers at most with minor modifications. I added the C tag too to this article despite the fact that the sources are .cpp files because it could be ported to C easily without C++ knowledge. The sources will work with both Unicode and ANSI character set setting.

You can find some weird coding conventions and Hungarian notation in this antique piece of code that I don't anymore follow. Big Grin | :-D

Mini Start Guide to Learn about PE Resources

If you read along this tip and have no clue about the resource format inside PE files, but you are hell curious then I give you a small startup guide. Again, this isn't going to be the 1000th resource format tutorial, I just point you the direction!

What is this binary resource stuff?

If you already worked on a windowed win32 application in Visual Studio, then you must be familiar with the .rc files and maybe with the .res files that are generated from .rc files. The .rc file in your project is a text file that defines different kind of resources: cursors, icons, dialogs, etc... Visual Studio has very nice visual editors for each resource types (icon, dialog, stringtable, etc...) so might never edit .rc files manually. There are several resource types and the number of types is growing as time passes. Big Grin | :-D This textual .rc file can reference other files (cursors, icons, binary files), but it contains complete description in text about some resource formats (dialogs, versioninfo). A compiler called rc.exe compiles this .rc file into a binary file that is the .res file. This res file contains everything, even external files referenced by the .rc (cursors, icons) are already compiled into it. This binary .res file is interesting because it is basically attached (usually to the end) to your .exe or .dll file by placing each resource in a search tree (by the linker), and the header of your .exe or .dll has a pointer to the beginning of this data.

How is this binary resource accessed by windows functions?

When your .exe or .dll is loaded, this attached binary resource data is also loaded into memory and some windows functions (like CreateDialog, LoadCursor, LoadIcon) expect you to specify a HMODULE/HINSTANCE parameter because the HMODULE/HINSTANCE handle values are really pointers to the loaded header of your .exe/.dll files in memory so from this pointer it can look up the address of the resource binary by accessing the headers of your .exe/.dll. In my example program, you can check out the ResDirVAFromHMODULE() function that converts a HMODULE handle into a pointer that points to the binary resource in the loaded module.

OK, I have a pointer to this binary resource so what to do?

At the beginning of that loaded binary resource, you have a tree that has 3 levels. (My source code contains a brief tutorial about the structure of this.) On these levels, you search for your resource by type (level 1), name/id (level 2), and language (level 3). (On each level, the sibling nodes are sorted so you can perform binary search.) You must be familiar with these parameters (type, name, language) if you ever looked into .rc files. After completing your search for your resource on all three levels, you get a pointer to the actual resource data and you get the size of the data. It depends on the type of the resource how to interpret that data, it's different for every resource type (cursor, icon, dialog, versioninfo, ...). In my sources, you have the code that searches the 3 levels of the resource tree (FindResource), and you have an example to handle one specific type of resource (LoadString: string resources).

Source Files

ResCommon.h:

C++
#ifndef ResCommon_h
#define ResCommon_h

    /*
    // Ensure that both UNICODE and _UNICODE symbols are
    // defined or none of them.
    // You must define only UNICODE. _UNICODE will be
    // automatically defined or undefined.
    */

#if defined(UNICODE) && !defined(_UNICODE)
#  define _UNICODE
#endif

#if !defined(UNICODE) && defined(_UNICODE)
#  undef _UNICODE
#endif

#include <windows.h>
#include <winnt.h>
#include <tchar.h>

#define IS_RES_ID(lpName) (!((DWORD)(lpName) & 0xFFFF0000))

#define RAISE_EXCEPTION_RESULT(result) 
(RaiseException (0xEDCB0000 | ((result) & 0xFFFF), 0, 0, NULL))
#define RAISE_EXCEPTION() (RaiseException (0xE0000000, 0, 0, NULL))

#define EXCEPT_EVAL ( error_code = 
((GetExceptionCode () & 0xFFFF0000) == 0xEDCB0000) \
                      ? (WORD)(GetExceptionCode () & 
                      0xFFFF) : error_code, EXCEPTION_EXECUTE_HANDLER)

    /*
    // ResDirVAFromHMODULE:
    // Speaks for itself... :)
    // The function returns zero on error.
    */

DWORD ResDirVAFromHMODULE (HMODULE ImageBase, WORD *lpwErrorCode = NULL);

    /*
    // Resource function return codes:
    // All codes can be at most 16 bits wide.
    //
    // * RES_OK:
    // Success.
    // * RES_MEMORY_ERR:
    // Out of memory.
    // * RES_INVALID_RESNAME:
    // The lpName or lpType parameter of FindResourceEx() is a string
    // and begins with a '#' character but the following numbers
    // do not contain valid decimal digits or the number can not be
    // stored using 16 bits.
    // * RES_TYPE_NOT_FOUND:
    // The specified resource type does not exist in this module.
    // * RES_NOT_FOUND:
    // The module does not contain the specified type of resource
    // with the given name or id.
    // * RES_LANG_NOT_FOUND:
    // The resource specified by type and name or id does not have
    // a copy with the specified language id.
    // * RES_NO_RESOURCE:
    // The specified module does not contain resources. It will return
    // only by functions that require a HMODULE.
    //
    // * RES_INVALID_IMAGEBASE:
    // The dwImageBase parameter is invalid.
    // * RES_UNKNOWN_ERR:
    // Unknown error. Most likely an unwanted exception. (GPF)
    */

#define RES_OK                      0x0000
#define RES_MEMORY_ERR              0x0001
#define RES_INVALID_RESNAME         0x0002
#define RES_TYPE_NOT_FOUND          0x0003
#define RES_NOT_FOUND               0x0004
#define RES_LANG_NOT_FOUND          0x0005
#define RES_NO_RESOURCE             0x0006
#define RES_INVALID_IMAGEBASE       0xFFFE
#define RES_UNKNOWN_ERR             0xFFFF


#endif /* ResCommon_h */

ResCommon.cpp:

C++
#include "ResCommon.h"


    /*
    // ResDirVAFromHMODULE:
    // Speaks for itself... :)
    // The function returns zero on error.
    */


DWORD ResDirVAFromHMODULE (HMODULE ImageBase, WORD *lpwErrorCode)
{
    DWORD   res;
    WORD    error_code = RES_INVALID_IMAGEBASE;

    __try
    {
        if ( ((IMAGE_DOS_HEADER*)ImageBase)->e_magic != IMAGE_DOS_SIGNATURE) 
            RAISE_EXCEPTION_RESULT (RES_INVALID_IMAGEBASE);

        IMAGE_NT_HEADERS *hdr = (IMAGE_NT_HEADERS*)((DWORD)ImageBase + ((IMAGE_DOS_HEADER*)ImageBase)->e_lfanew);

        if (hdr->Signature != IMAGE_NT_SIGNATURE ||
            hdr->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC)
            RAISE_EXCEPTION_RESULT (RES_INVALID_IMAGEBASE);

        if (hdr->OptionalHeader.NumberOfRvaAndSizes < (IMAGE_DIRECTORY_ENTRY_RESOURCE + 1) ||
            !hdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress ||
            hdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].Size < sizeof (IMAGE_RESOURCE_DIRECTORY))
            RAISE_EXCEPTION_RESULT (RES_NO_RESOURCE);

        res = (DWORD)ImageBase + hdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress;
        error_code = RES_OK;
    }
    __except (EXCEPT_EVAL)
    {
        res = 0;
    }

    __try
    {
        if (lpwErrorCode) *lpwErrorCode = error_code;
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {}

    return res;
} 

LoadString.h:

C++
#ifndef LoadString_h
#define LoadString_h

#include "ResCommon.h"
#include "FindResource.h"

    /*
    // LoadString functions:
    // Each function have 2 variants: One that requires not only a
    // HMODULE but another parameter dwResDirVA, does not require
    // the DOS/NT headers to be loaded.
    */

    /*
    // MyLoadString:
    // The fake of the original windows LoadString if you don't use
    // the default parameters except that lengths are DWORD instead
    // of int.
    // - If nBufferMax is zero then the return value is the length of
    //   the string not including the terminating zero.
    // - If nBufferMax is nozero but lpBuffer is NULL then the return
    //   value is a const pointer to an IMAGE_RESOURCE_DIR_STRING_U
    //   structure.
    // - The return value is zero on error.
    */


DWORD MyLoadString (
    HMODULE ImageBase,  // dwImageBase
    DWORD dwResDirVA,   // Pointer (VA) to the root of resource tree.
    UINT uID,           // string resource identifier
    LPTSTR lpBuffer,    // pointer to buffer to receive the string with terminating zero.
    DWORD nBufferMax,   // size of buffer
    WORD wLanguage = MAKELANGID (LANG_NEUTRAL, 
    SUBLANG_NEUTRAL),    // LANGID forged with MAKELANGID().
    WORD *lpwErrorCode = NULL,  // Pointer to a variable that 
        // receives error code on failure or zero on success.
    WORD *lpwLanguageFound = NULL   // Language of the resource found. 
        //It may differ from wLanguage if that language is absent.
    );


DWORD MyLoadString (
    HMODULE ImageBase,  // dwImageBase
    UINT uID,           // string resource identifier
    LPTSTR lpBuffer,    // pointer to buffer to receive the string with terminating zero.
    DWORD nBufferMax,   // size of buffer
    WORD wLanguage = MAKELANGID (LANG_NEUTRAL, 
        SUBLANG_NEUTRAL),    // LANGID forged with MAKELANGID().
    WORD *lpwErrorCode = NULL,  // Pointer to a variable that 
        // receives error code on failure or zero on success.
    WORD *lpwLanguageFound = NULL   // Language of the resource found. 
        // It may differ from wLanguage if that language is absent.
    );

    /*
    // InternalLoadString:
    // Retrieve a pointer to the resource string identified by uID or
    // if an error occurs. The preferred language wLanguage will be
    // searched. This can be MAKELANGID (LANG_NEUTRAL, SUBLANG_NEUTRAL)
    // to search for the trhead default language. If it is not found
    // then the first language found in the language directory will be
    // used.
    */


IMAGE_RESOURCE_DIR_STRING_U *InternalLoadString (
    HMODULE ImageBase,  // dwImageBase
    DWORD dwResDirVA,   // Pointer (VA) to the root of resource tree.
    UINT uID,           // string resource identifier
    WORD wLanguage = MAKELANGID (LANG_NEUTRAL, 
        SUBLANG_NEUTRAL),    // LANGID forged with MAKELANGID().
    WORD *lpwErrorCode = NULL,  // Pointer to a variable that 
        // receives error code on failure or zero on success.
    WORD *lpwLanguageFound = NULL   // Language of the resource found. 
        // It may differ from wLanguage if that language is absent.
    );

IMAGE_RESOURCE_DIR_STRING_U *InternalLoadString (
    HMODULE ImageBase,  // dwImageBase
    UINT uID,           // string resource identifier
    WORD wLanguage = MAKELANGID (LANG_NEUTRAL, 
        SUBLANG_NEUTRAL),    // LANGID forged with MAKELANGID().
    WORD *lpwErrorCode = NULL,  // Pointer to a variable that 
        // receives error code on failure or zero on success.
    WORD *lpwLanguageFound = NULL   // Language of the resource found. 
        // It may differ from wLanguage if that language is absent.
    );

#endif /* LoadString_h */

LoadString.cpp

C++
#include "FindResource.h"
#include "LoadString.h"

    /*
    // The strings are stored using IMAGE_RESOURCE_DIR_STRING_U
    // structures. Strings can be identified only by ID.
    // The whole string table is broken up to blocks and each block
    // contains 16 IMAGE_RESOURCE_DIR_STRING_U structures. These blocks
    // always contain all 16 strings, the non existent strings'
    // length is set to zero.
    // Strings with ID number
    // 0x00-0x0f are stored in block #1
    // 0x10-0x1f are stored in block #2
    // 0x20-0x2f are in block #3
    // ...and so on.
    //
    // If you do not use the string ID 17, then the second string in
    // block #2 will have a length of zero. If you don't need any string
    // IDs in the range 0x10-0x1f then the whole second block will be
    // absent, but #1 and #3 can be present.
    // Calculating the block number and the index inside the
    // block using the ID of string:
    // block_number = (ID >> 4) + 1
    // index_inside_block = ID & 0xf
    //
    // How do these blocks reside in the resource information?
    // At the FindResource tutorial we learned that you search for
    // a particular resource by RESOURCE_TYPE, NAME/ID, and LANGUAGE
    // on the first 3 levels of the resource tree.
    // When you search for a string then RESOURCE_TYPE is RT_STRING,
    // NAME/ID is the block number, and the LANGUAGE is your choice.
    // When you use the Win32 LoadString then the thread default
    // language is used.
    // After searching the block with FindResource/LoadResource you
    // have the pointer to an array of IMAGE_RESOURCE_DIR_STRING_U
    // whose elements are variable length, but its element count
    // is always 16.
    //
    // Info:
    // - String ID 0 is usually not used.
    // - If you use only the string IDs 0x11, 0x55 then these
    //   strings will occupy 2 full blocks (#2 and #6) each containing
    //   16 strings, and 15 of them have zero length. It is recommended
    //   to use a continuous range of string IDs.
    */

    /*
    // MyLoadString:
    // The fake of the original windows LoadString if you don't use
    // the default parameters except that lengths are DWORD instead
    // of int.
    // - If nBufferMax is zero then the return value is the length of
    //   the string not including the terminating zero.
    // - If nBufferMax is nozero but lpBuffer is NULL then the return
    //   value is a const pointer to an IMAGE_RESOURCE_DIR_STRING_U
    //   structure.
    // - If neither of lpBuffer/nBufferMax is zero then the return
    //   value is the number of characters copied to the buffer not
    //   including the terminating zero.
    // - The return value is zero on error.
    */


DWORD MyLoadString (
    HMODULE ImageBase,  // dwImageBase
    DWORD dwResDirVA,   // Pointer (VA) to the root of resource tree.
    UINT uID,           // string resource identifier
    LPTSTR lpBuffer,    // pointer to buffer to receive the 
                    // string with terminating zero.
    DWORD nBufferMax,   // size of buffer
    WORD wLanguage,     // LANGID forged with MAKELANGID().
    WORD *lpwErrorCode, // Pointer to a variable that receives 
            // error code on failure or zero on success.
    WORD *lpwLanguageFound  // Language of the resource found. 
            // It may differ from wLanguage if that language is absent.
    )
{
    IMAGE_RESOURCE_DIR_STRING_U *str;
    DWORD len;
    WORD error_code;

    __try
    {
        str = InternalLoadString (
            ImageBase,
            dwResDirVA,
            uID,
            wLanguage,
            &error_code,
            lpwLanguageFound
            );

        if (!str) RAISE_EXCEPTION_RESULT (RES_NOT_FOUND);
        error_code = RES_UNKNOWN_ERR;

        len = str->Length;

        if (nBufferMax && lpBuffer)
        {
            /*
            // return the string
            */

            /* number of characters to copy not including the zero */
            len = min (nBufferMax - 1, len);

#ifdef UNICODE

            /*
            // UNICODE: Simply copy one buffer to another.
            */

            memcpy (
                (void*)lpBuffer, 
                (void*)&str->NameString, 
                len * sizeof (WCHAR)
                );

#else /* UNICODE */

            /*
            // ANSI: UNICODE->ANSI conversion instead of memcpy
            */

            len = WideCharToMultiByte (
                CP_ACP,
                0,
                (LPCWSTR)&str->NameString,
                len,
                lpBuffer,
                len,
                NULL,
                NULL
                );


#endif /* UNICODE */

            lpBuffer[len] = 0;  /* terminating zero */

        }
        else if (nBufferMax && !lpBuffer)
        {
            /*
            // return a pointer to the IMAGE_RESOURCE_DIR_STRING_U.
            */

            len = (DWORD)str;
        } 
        /* else, nBufferMax==0 so return only the length without terminating zero */

        error_code = RES_OK;
    }
    __except (EXCEPT_EVAL) 
    {
        len = 0;
    }

    __try
    {
        if (lpwErrorCode) *lpwErrorCode = error_code;
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {}

    return len;
}

DWORD MyLoadString (
    HMODULE ImageBase,  // dwImageBase
    UINT uID,           // string resource identifier
    LPTSTR lpBuffer,    // pointer to buffer to receive the string with terminating zero.
    DWORD nBufferMax,   // size of buffer
    WORD wLanguage,     // LANGID forged with MAKELANGID().
    WORD *lpwErrorCode, // Pointer to a variable that receives 
            //error code on failure or zero on success.
    WORD *lpwLanguageFound  // Language of the resource found. 
        // It may differ from wLanguage if that language is absent.
    )
{
    DWORD dwResDirVA = ResDirVAFromHMODULE (ImageBase, lpwErrorCode);
    if (!dwResDirVA) return 0;

    return MyLoadString (
        ImageBase,
        dwResDirVA,
        uID,
        lpBuffer,
        nBufferMax,
        wLanguage,
        lpwErrorCode,
        lpwLanguageFound
        );
}

    /*
    // InternalLoadString:
    // Retrieve a pointer to the resource string identified by uID or
    // if an error occurs. The preferred language wLanguage will be
    // searched. This can be MAKELANGID (LANG_NEUTRAL, SUBLANG_NEUTRAL)
    // to search for the trhead default language. If it is not found
    // then the first language found in the language directory will be
    // used.
    */

IMAGE_RESOURCE_DIR_STRING_U *InternalLoadString (
    HMODULE ImageBase,  // dwImageBase
    DWORD dwResDirVA,   // Pointer (VA) to the root of resource tree.
    UINT uID,           // string resource identifier
    WORD wLanguage,     // LANGID forged with MAKELANGID().
    WORD *lpwErrorCode, // Pointer to a variable that receives 
        // error code on failure or zero on success.
    WORD *lpwLanguageFound  // Language of the resource found. 
        // It may differ from wLanguage if that language is absent.
    )
{
    IMAGE_RESOURCE_DIR_STRING_U *result;
    IMAGE_RESOURCE_DATA_ENTRY   *data;
    WORD                        error_code = RES_UNKNOWN_ERR;

    __try
    {
        /*
        // Find the block of strings we need.
        */

        data = InternalFindResourceEx (
            dwResDirVA,
            (LPCTSTR)((uID >> 4) + 1),
            RT_STRING,
            wLanguage,
            &error_code,
            lpwLanguageFound
            );

        if (!data) RAISE_EXCEPTION ();

        error_code = RES_UNKNOWN_ERR;

        uID &= 0xf; // String index in the block.
        result = (IMAGE_RESOURCE_DIR_STRING_U*)MyLoadResource (ImageBase, data);

        /*
        // MyLoadResource should not fail.
        */

        for (; uID; uID--)
            result = (IMAGE_RESOURCE_DIR_STRING_U*)((DWORD)result
                + (result->Length + 1) * sizeof (WCHAR));

        if (!result->Length) RAISE_EXCEPTION_RESULT (RES_NOT_FOUND);
        error_code = RES_OK;
    }
    __except (EXCEPT_EVAL) 
    {
        result = NULL;
    }

    __try
    {
        if (lpwErrorCode) *lpwErrorCode = error_code;
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {}

    return result;
}

IMAGE_RESOURCE_DIR_STRING_U *InternalLoadString (
    HMODULE ImageBase,  // dwImageBase
    UINT uID,           // string resource identifier
    WORD wLanguage,     // LANGID forged with MAKELANGID().
    WORD *lpwErrorCode, // Pointer to a variable that receives 
            // error code on failure or zero on success.
    WORD *lpwLanguageFound  // Language of the resource found. 
        // It may differ from wLanguage if that language is absent.
    )
{
    DWORD dwResDirVA = ResDirVAFromHMODULE (ImageBase, lpwErrorCode);
    if (!dwResDirVA) return NULL;

    return InternalLoadString (
        ImageBase,
        dwResDirVA,
        uID,
        wLanguage,
        lpwErrorCode,
        lpwLanguageFound
        );
}

FindResource.h

C++
#ifndef FindResource_h
#define FindResource_h

#include "ResCommon.h"

    /*
    // MyLoadResource:
    // Fake LoadResource. Fake LockResource isn't required since
    // that function does nothing. The headers at ImageBase (if any)
    // aren't used.
    */


void *MyLoadResource (
    HMODULE ImageBase,  // dwImageBase
    IMAGE_RESOURCE_DATA_ENTRY *lpDataEntry  // pointer returned by FindResource()
    );

DWORD MySizeOfResource (
    IMAGE_RESOURCE_DATA_ENTRY *lpDataEntry  // pointer returned by FindResource()
    );

    /*
    // FindResource functions have two variants. One that works
    // with the address of the resource directory, and another that
    // takes a HMODULE and retrieves the address of resource directory
    // itself. You must use the functions that require ResDirVA if
    // you haven't loaded the headers of the PE file.
    */


    /*
    // MyFindResource:
    // The fake of the original Win32 FindResource.
    // Calls InternalFindResourceEx with MAKELANGID (LANG_NEUTRAL, SUBLANG_NEUTRAL).
    // Returns a resource with the language of the current thread, or
    // with the first language in the language directory for the specified
    // resource if the language of the current thread was not found.
    */

IMAGE_RESOURCE_DATA_ENTRY *MyFindResource (
    DWORD dwResDirVA,   // Pointer (VA) to the root of resource tree.
    LPCTSTR lpName,     // Name or ID of the resource.
    LPCTSTR lpType,     // One of the RT_XXX constants 
            // (a MAKEINTRESOURCE() value).
    WORD *lpwErrorCode = NULL,  // Pointer to a variable that 
        // receives error code on failure or zero on success.
    WORD *lpwLanguageFound = NULL   // Language of the resource found. 
        // It may differ from wLanguage if that language is absent.
    );


IMAGE_RESOURCE_DATA_ENTRY *MyFindResource (
    HMODULE ImageBase,  // dwImageBase
    LPCTSTR lpName,     // Name of the resource or resource ID.
    LPCTSTR lpType,     // One of the RT_XXX constants 
            // (a MAKEINTRESOURCE() value).
    WORD *lpwErrorCode = NULL,  // Pointer to a variable that     
        // receives error code on failure or zero on success.
    WORD *lpwLanguageFound = NULL   // Language of the resource found. 
        // It may differ from wLanguage if that language is absent.
    );


    /*
    // MyFindResourceEx:
    // Works the same way as the original Win32 FindResourceEx.
    // Its similar to InternalFindResourceEx except that
    // this function will fail if the resource with the specified
    // language was not found. You can use InternalFindResourceEx
    // as well despite its name.
    */


IMAGE_RESOURCE_DATA_ENTRY *MyFindResourceEx (
    DWORD dwResDirVA,   // Pointer (VA) to the root of resource tree.
    LPCTSTR lpName,     // Name or ID of the resource.
    LPCTSTR lpType,     // One of the RT_XXX constants 
            // (a MAKEINTRESOURCE() value).
    WORD wLanguage = MAKELANGID (LANG_NEUTRAL, 
            SUBLANG_NEUTRAL),    // LANGID forged with MAKELANGID().
    WORD *lpwErrorCode = NULL   // Pointer to a variable that 
        // receives error code on failure or zero on success.
    );


IMAGE_RESOURCE_DATA_ENTRY *MyFindResourceEx (
    HMODULE ImageBase,  // dwImageBase
    LPCTSTR lpName,     // Name of the resource or resource ID.
    LPCTSTR lpType,     // One of the RT_XXX constants (a MAKEINTRESOURCE() value).
    WORD wLanguage = MAKELANGID (LANG_NEUTRAL, 
        SUBLANG_NEUTRAL),    // LANGID forged with MAKELANGID().
    WORD *lpwErrorCode = NULL   // Pointer to a variable that 
        // receives error code on failure or zero on success.
    );

    /*
    // InternalFindResourceEx:
    // Search for a specified resource with the given language.
    // If this language not found then return the first resource
    // of the language directory. You can specify
    // MAKELANGID (LANG_NEUTRAL, SUBLANG_NEUTRAL) == 0
    // as wLanguage if you want to search for a resource with the
    // LANGID of the current thread.
    */


IMAGE_RESOURCE_DATA_ENTRY *InternalFindResourceEx (
    DWORD dwResDirVA,   // Pointer (VA) to the root of resource tree.
    LPCTSTR lpName,     // Name or ID of the resource.
    LPCTSTR lpType,     // One of the RT_XXX constants 
        // (a MAKEINTRESOURCE() value).
    WORD wLanguage = MAKELANGID (LANG_NEUTRAL, 
        SUBLANG_NEUTRAL),    // LANGID forged with MAKELANGID().
    WORD *lpwErrorCode = NULL,      // Pointer to a variable that 
        // receives error code on failure or zero on success.
    WORD *lpwLanguageFound = NULL   // Language of the resource found. 
        // It may differ from wLanguage if that language is absent.
    );


IMAGE_RESOURCE_DATA_ENTRY *InternalFindResourceEx (
    HMODULE ImageBase,  // dwImageBase
    LPCTSTR lpName,     // Name of the resource or resource ID.
    LPCTSTR lpType,     // One of the RT_XXX constants (a MAKEINTRESOURCE() value).
    WORD wLanguage = MAKELANGID (LANG_NEUTRAL, 
        SUBLANG_NEUTRAL),    // LANGID forged with MAKELANGID().
    WORD *lpwErrorCode = NULL,     // Pointer to a variable that 
        // receives error code on failure or zero on success.
    WORD *lpwLanguageFound = NULL  // Language of the resource found. 
        // It may differ from wLanguage if that language is absent.
    );

#endif /* FindResource_h */

FindResource.cpp

C++
#include "FindResource.h"

    /*
    // The resource tree has 4 levels. Each node on the first 3 levels consist
    // of an IMAGE_RESOURCE_DIRECTORY that is followed by variable number
    // of IMAGE_RESOURCE_DIRECTORY_ENTRY structures. Only the 
    // IMAGE_RESOURCE_DIRECTORY_ENTRY structures contain important info
    // for us. They always point (OffsetToData) to one of the
    // IMAGE_RESOURCE_DIRECTORY structures of the next level and the meaning
    // of their other field (Name) is dependent on the current level (1-3).
    //
    // Meaning of the IMAGE_RESOURCE_DIRECTORY_ENTRY.Name field on each level:
    // Level 1: Resource type. It is usually one of the predefined constants
    //          (RT_CURSOR, RT_BITMAP, RT_ICON....) but if the MSB is set then
    //          the lower 31 bits contain offset to an IMAGE_RESOURCE_DIR_STRING_U
    //          struct that contains the type name of the resource.
    // Level 2: Collection of resources of the same type.
    //          If the most significant bit of the Name field is set then the
    //          lower 31 bits contain an OffsetToData that points to an
    //          IMAGE_RESOURCE_DIR_STRING_U that holds the resource name in
    //          unicode format.
    //          If the most significant bit of the Name field is cleared than
    //          the lower 16 bits contain the resource ID.
    // Level 3: This level can contain more than one entry for the same
    //          resource that is identified by its Name or ID allowing
    //          you to have for example the same dialog in multiple languages.
    //          The Name field here contains a LANGID value.
    //
    // You search for a specific resource by: - TYPE        (level 1)
    //                                        - NAME/ID     (level 2)
    //                                        - LANGUAGE    (level 3)
    //
    // When you ended the search and found the suitable IMAGE_RESOURCE_DIRECTORY_ENTRY
    // on level 3 then its OffsetToData field is a pointer to an
    // IMAGE_RESOURCE_DATA_ENTRY that has no more branches, it is only a bridge
    // between the level 3 IMAGE_RESOURCE_DIRECTORY_ENTRY you found, and the
    // actual resource data whose structure is dependant on the resource type.
    //
    // Important:
    // - On each level the number of actual IMAGE_RESOURCE_DIRECTORY_ENTRY
    //   structures is dependent on two fields of the IMAGE_RESOURCE_DIRECTORY
    //   structure. These fields are NumberOfNamedEntries, and NumberOfIdEntries.
    //   The sum of these fields give the number of entry structures.
    //   NumberOfNamedEntries is always zero and NumberOfIdEntries stores the
    //   number of entries except on the first and second levels. At these levels there
    //   can be either resource Name or ID in the Name field of the entry struct.
    //   NumberOfNameEntries contains the number of named entries and
    //   guess what is in the NumberOfIdEntries field... :)
    //   The IMAGE_RESOURCE_DIRECTORY_ENTRY struct is followed by the Named
    //   entry structs that are in ascending order by Name, and after it
    //   you find the ID entry structs in ascending order by IDs.
    //   Exploiting this you can speed up your search for resource Names and IDs.
    //   You can also make difference between a Named and ID entry structure
    //   by checking the MSB of the Name fields cause it is set for Named entries.
    // - All strings are stored in unicode format in the resource data.
    //   WideCharToMultiByte() and MultiByteToWideChar() should be used for conversion.
    // - The OffsetToData field is always an offset relative to the address
    //   of the first (root) IMAGE_RESOURCE_DIRECTORY whose RVA is also in the
    //   DataDirectory[] of the IMAGE_NT_OPTIONAL header of the PE file.
    //   The only exception is the IMAGE_RESOURCE_DATA_ENTRY structure whose
    //   OffsetToData field contains RVA.
    // - On the 1st and 2nd levels of the tree the most significant bit of
    //   OffsetToData member in the IMAGE_RESOURCE_DIRECTORY_ENTRY structures
    //   is set to 1. This indicates that they point to an IMAGE_RESOURCE_DIRECTORY
    //   struct (a new tree node that has branches) instead of a normal
    //   entry structure. On level 3 and 4 the OffsetToData members always
    //   have their MSB cleared.
    // - Functions that work with resource data always use the
    //   FindResource(), LoadResource(), and LockResource()
    //   functions of KERNEL32.
    //   FindResource() searches a resource for you and retrieves a
    //   pointer (VA) to the IMAGE_RESOURCE_DATA_ENTRY of the specified
    //   resource. LoadResource() does nothing more than calculates a
    //   pointer (VA) to the actual resource data by taking the OffsetToData
    //   member of the IMAGE_RESOURCE_DATA_ENTRY that was found by the
    //   previous call to FindResource(), and converts it to a pointer (VA)
    //   using the ImageBase (HMOUDLE) parameter. SizeOfResource() only
    //   one of the fields of the PIMAGE_RESOURCE_DATA_ENTRY you pass in.
    //   LockResource() is a legacy function from Win16, it does nothing,
    //   returns the pointer you give to it as a parameter.
    // - You can not rely on FindResource() when you pass the address of
    //   a "manually loaded" module. This affects all functions that use
    //   resource data. For example FindResource() does not work on Win9x
    //   because it tries to look up the given HMODULE in the internal
    //   module table, but it works on WinNT. Lot of User32 functions that
    //   use resource (for example CreateDialog()) can be emulated easily.
    //   You must use a "hand made" FindResource() and LoadResource()
    //   function and then call a variant of the wanted function that
    //   operates on raw resource data. For example CreateDialogIndirect()
    //   instead of CreateDialog().
    */ 

    /*
    // MyLoadResource, MySizeOfResource:
    */

void *MyLoadResource (
    HMODULE ImageBase,  // dwImageBase
    IMAGE_RESOURCE_DATA_ENTRY *lpDataEntry  // pointer returned by FindResource()
    )
{
    __try
    {
        if (!ImageBase || !lpDataEntry) return NULL;
        return (void*)((DWORD)ImageBase + (lpDataEntry->OffsetToData & 0x7FFFFFFF));
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        return NULL;
    }
}

DWORD MySizeOfResource (
    IMAGE_RESOURCE_DATA_ENTRY *lpDataEntry  // pointer returned by FindResource()
    )
{
    __try
    {
        return lpDataEntry->Size;
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        return 0;
    }
}

    /*
    // MyFindResource:
    // The fake of the original Win32 FindResource.
    // Calls InternalFindResourceEx with MAKELANGID (LANG_NEUTRAL, SUBLANG_NEUTRAL).
    // Returns a resource with the language of the current thread, or
    // with the first language in the language directory for the specified
    // resource if the language of the current thread was not found.
    */

IMAGE_RESOURCE_DATA_ENTRY *MyFindResource (
    DWORD dwResDirVA,   // Pointer (VA) to the root of resource tree.
    LPCTSTR lpName,     // Name or ID of the resource.
    LPCTSTR lpType,     // One of the RT_XXX constants 
        // (a MAKEINTRESOURCE() value).
    WORD *lpwErrorCode, // Pointer to a variable that 
        // receives error code on failure or zero on success.
    WORD *lpwLanguageFound  // Language of the resource found. 
        // It may differ from wLanguage if that language is absent.
    )
{
    return InternalFindResourceEx (
        dwResDirVA,
        lpName,
        lpType,
        MAKELANGID (LANG_NEUTRAL, SUBLANG_NEUTRAL),
        lpwErrorCode,
        lpwLanguageFound
        );
}


IMAGE_RESOURCE_DATA_ENTRY *MyFindResource (
    HMODULE ImageBase,  // dwImageBase
    LPCTSTR lpName,     // Name of the resource or resource ID.
    LPCTSTR lpType,     // One of the RT_XXX constants 
        // (a MAKEINTRESOURCE() value).
    WORD *lpwErrorCode, // Pointer to a variable that 
        // receives error code on failure or zero on success.
    WORD *lpwLanguageFound  // Language of the resource found. 
        // It may differ from wLanguage if that language is absent.
    )
{
    return InternalFindResourceEx (
        ImageBase,
        lpName,
        lpType,
        MAKELANGID (LANG_NEUTRAL, SUBLANG_NEUTRAL),
        lpwErrorCode,
        lpwLanguageFound
        );
}

    /*
    // MyFindResourceEx:
    // Works the same way as the original Win32 FindResourceEx.
    // Its similar to InternalFindResourceEx except that
    // this function will fail if the resource with the specified
    // language was not found. You can use InternalFindResourceEx
    // as well despite its name.
    */


IMAGE_RESOURCE_DATA_ENTRY *MyFindResourceEx (
    DWORD dwResDirVA,   // Pointer (VA) to the root of resource tree.
    LPCTSTR lpName,     // Name or ID of the resource.
    LPCTSTR lpType,     // One of the RT_XXX constants (a MAKEINTRESOURCE() value).
    WORD wLanguage,     // LANGID forged with MAKELANGID().
    WORD *lpwErrorCode  // Pointer to a variable that 
        // receives error code on failure or zero on success.
    )
{
    IMAGE_RESOURCE_DATA_ENTRY   *result;
    WORD                        wLanguageFound;

    result = InternalFindResourceEx (
        dwResDirVA,
        lpName,
        lpType,
        wLanguage,
        lpwErrorCode,
        &wLanguageFound
        );

    if (result && wLanguage != wLanguageFound) 
    {
        result = NULL;

        __try
        {
            if (lpwErrorCode) *lpwErrorCode = RES_LANG_NOT_FOUND;
        }
        __except (EXCEPTION_EXECUTE_HANDLER) {}

    }

    return result;
}

IMAGE_RESOURCE_DATA_ENTRY *MyFindResourceEx (
    HMODULE ImageBase,  // dwImageBase
    LPCTSTR lpName,     // Name or ID of the resource.
    LPCTSTR lpType,     // One of the RT_XXX constants (a MAKEINTRESOURCE() value).
    WORD wLanguage,     // LANGID forged with MAKELANGID().
    WORD *lpwErrorCode  // Pointer to a variable that 
            // receives error code on failure or zero on success.
    )
{
    IMAGE_RESOURCE_DATA_ENTRY   *result;
    WORD                        wLanguageFound;

    result = InternalFindResourceEx (
        ImageBase,
        lpName,
        lpType,
        wLanguage,
        lpwErrorCode,
        &wLanguageFound
        );

    if (result && wLanguage != wLanguageFound) 
    {
        result = NULL;

        __try
        {
            if (lpwErrorCode) *lpwErrorCode = RES_LANG_NOT_FOUND;
        }
        __except (EXCEPTION_EXECUTE_HANDLER) {}

    }

    return result;
}

    /*
    // CmpLPCTSTRStringU:
    // Performs comparison of the lpStr ANSI/UNICODE string and the lpStrU
    // unicode string that is stored in the resource data of a PE file.
    // In the ANSI version the comparison will be done in ANSI format.
    // It may raise an exception on fatal errors.
    // Comparison result:
    // * negative: lpStr < lpStrU
    // * zero:     lpStr == lpStrU
    // * positive: lpStr > lpStrU
    */

int CmpLPCTSTRStringU (LPCTSTR lpStr, IMAGE_RESOURCE_DIR_STRING_U *lpStrU)
{
    int     result;
    DWORD   StrLen = (DWORD)lstrlen (lpStr);

    if (StrLen < lpStrU->Length) return -1;
    else if (StrLen > lpStrU->Length) return 1;
    if (!StrLen) return 0;  // zero lengths

    LPTSTR  UniStr;

    /*
    // In ANSI version we convert the unicode resource string to ANSI.
    */

#ifdef UNICODE

    UniStr = (LPWSTR)&lpStrU->NameString;

#else /* UNICODE */

    UniStr = (LPSTR)malloc (lpStrU->Length);
    if (!UniStr) RAISE_EXCEPTION_RESULT (RES_MEMORY_ERR);

    __try
    {
        if (WideCharToMultiByte (CP_ACP, 0, (LPCWSTR)&lpStrU->NameString,
            lpStrU->Length, UniStr, lpStrU->Length, NULL, NULL) != lpStrU->Length)
            RAISE_EXCEPTION_RESULT (RES_UNKNOWN_ERR);

#endif /* UNICODE */

        result = CompareString (
            LOCALE_USER_DEFAULT, 
            0,
            lpStr,
            StrLen,
            UniStr,
            lpStrU->Length
            ) - CSTR_EQUAL;  /* subtract 2 to be consistent with normal strcmp() */

#ifndef UNICODE

    }
    __finally
    {
        free (UniStr);
    }

#endif /* !UNICODE */

    return result;
}

    /*
    // CmpLPCTSTRName:
    // Compares lpStr to Name that is the field of an
    // IMAGE_RESOURCE_DIRECTORY_ENTRY.
    // If lpStr is an ID (high-word is zero) than it will be compared
    // to the low-word of Name. If lpStr is not an ID than it points
    // to a null terminated string and the lower 31 bits of Name
    // should be an offset to a IMAGE_RESOURCE_DIR_STRING_U structure.
    // This offset is relative to dwResDirVA.
    // Comparison result:
    // * negative: lpStr < Name
    // * zero:     lpStr == Name
    // * positive: lpStr > Name
    */

int CmpLPCTSTRName (DWORD dwResDirVA, LPCTSTR lpStr, DWORD Name)
{
    if (IS_RES_ID (lpStr))
    {
        if ((WORD)lpStr < (WORD)Name) return -1;
        else if ((WORD)lpStr > (WORD)Name) return 1;
        else return 0;
    }
    else
    {
        return CmpLPCTSTRStringU (
            lpStr, 
            (IMAGE_RESOURCE_DIR_STRING_U*)(dwResDirVA + (Name & 0x7FFFFFFF))
            );
    }
}

    /*
    // ConvertResName:
    // If the high-word of lpStr is not zero and the string it points to
    // begins with a '#' character than the following characters will be
    // interpreted as decimal digits. The number will be converted to
    // its binary representation and stored in lpStr.
    // The function fails only when the string begins with '#' so
    // conversion is needed but the following characters do not represent
    // a valid decimal number, or the number is so big that it can not be
    // stored using 16bits. In this case the function raises an exception
    // but other type of exception may occur (access violation).
    */

void ConvertResName (LPCTSTR *lplpStr)
{
    if (!IS_RES_ID (*lplpStr))
        if ((*lplpStr)[0] == '#')
    {
        errno = 0;
        LPTSTR endptr;
        long num = _tcstol (*lplpStr + 1, &endptr, 10); // strtol() or wcstol()

        if (errno != 0 ||                   // conversion error
            (*lplpStr + 1) == endptr ||     // there were no decimal digits
            (DWORD)num > 0xFFFF)            // too big ID
            RAISE_EXCEPTION_RESULT (RES_INVALID_RESNAME);

        *lplpStr = (LPCTSTR)num;
    }
}

    /*
    // FindResDirEntry:
    // Search for the specified resource directory entry by its Name
    // or ID in a resource directory. The name can be a string that
    // represents the ID in decimal format eg.: "#238".
    // The return value is NULL if the specified Name or ID does not
    // exist in the directory.
    // Unhandled exception may occur.
    */

IMAGE_RESOURCE_DIRECTORY_ENTRY *FindResDirEntry (
    DWORD dwResDirVA,                   // Pointer (VA) to the root of the resource directory.
    IMAGE_RESOURCE_DIRECTORY *lpDir,    // The directory to search in.
    LPCTSTR lpName                      // The name or id to search for.
    )
{
    DWORD   low, high, mid;

    IMAGE_RESOURCE_DIRECTORY_ENTRY *entries = (IMAGE_RESOURCE_DIRECTORY_ENTRY*)(lpDir + 1);

    /*
    // Since the IMAGE_RESOURCE_DIRECTORY_ENTRY structures of a node
    // are always in ascending order by their Name fields we can use
    // binary halving to reduce search time to log2 (n) + 1 steps at
    // worst case.
    */

    ConvertResName (&lpName);

    if (IS_RES_ID (lpName))
    {
        low = lpDir->NumberOfNamedEntries;
        high = low + lpDir->NumberOfIdEntries;
    }
    else
    {
        low = 0;
        high = lpDir->NumberOfNamedEntries;
    }

    /*
    // low: index of the first directory entry we must search in.
    // high: index of the last directory entry we want to search in + 1.
    */

    while (high > low)
    {
        mid = (high + low) >> 1;
        int cmpres = CmpLPCTSTRName (dwResDirVA, lpName, entries[mid].Name);
        if (!cmpres) return &entries[mid];

        if (cmpres < 0)
            high = mid;
        else
            low = mid + 1;
    }

    return NULL;
}

    /*
    // InternalFindResourceEx:
    // Search for a specified type of resource with a given name
    // or ID in the specified Resource Directory. We will look
    // for resource with the specified language but if the
    // resource does not have an instance with that language then
    // we return the first language occurrence of that resource.
    // The language of the returned data entry will be stored at
    // *lpwLanguageFound.
    */


IMAGE_RESOURCE_DATA_ENTRY *InternalFindResourceEx (
    DWORD dwResDirVA,   // Pointer (VA) to the root of resource tree.
    LPCTSTR lpName,     // Name of the resource or resource ID.
    LPCTSTR lpType,     // One of the RT_XXX constants (a MAKEINTRESOURCE() value).
    WORD wLanguage,     // LANGID forged with MAKELANGID().
    WORD *lpwErrorCode,     // Pointer to a variable that 
        // receives error code on failure or zero on success.
    WORD *lpwLanguageFound  // Language of the resource found. 
        // It may differ from wLanguage if that language is absent.
    )
{
    IMAGE_RESOURCE_DATA_ENTRY       *result = NULL;
    IMAGE_RESOURCE_DIRECTORY_ENTRY  *entry;
    IMAGE_RESOURCE_DIRECTORY        *lang_dir;

    WORD    error_code = RES_UNKNOWN_ERR;

    __try
    {
        /*
        // level 1: search by type
        */

        entry = FindResDirEntry (
                    dwResDirVA,
                    (IMAGE_RESOURCE_DIRECTORY*)dwResDirVA,
                    lpType
                    );

        if (!entry) RAISE_EXCEPTION_RESULT (RES_TYPE_NOT_FOUND);

        /*
        // level 2: search by Name or ID
        */

        entry = FindResDirEntry (
                    dwResDirVA,
                    (IMAGE_RESOURCE_DIRECTORY*)(dwResDirVA + (entry->OffsetToData & 0x7FFFFFFF)),
                    lpName
                    );

        if (!entry) RAISE_EXCEPTION_RESULT (RES_NOT_FOUND);

        /*
        // level 3: search by LANGID
        */

        lang_dir = (IMAGE_RESOURCE_DIRECTORY*)
            (dwResDirVA + (entry->OffsetToData & 0x7FFFFFFF));

        if (wLanguage == MAKELANGID (LANG_NEUTRAL, SUBLANG_NEUTRAL))
            wLanguage = LANGIDFROMLCID (GetThreadLocale ());

        entry = FindResDirEntry (dwResDirVA, lang_dir, (LPCTSTR)wLanguage);

        if (!entry)
        {
            /*
            // Number of language entries is zero. (???)
            // This should never happen.
            */

            if (!lang_dir->NumberOfIdEntries) 
                RAISE_EXCEPTION_RESULT (RES_LANG_NOT_FOUND);

            /*
            // If the specified LANGID was not found then we return the
            // resource with the first language of the directory.
            */

            entry = (IMAGE_RESOURCE_DIRECTORY_ENTRY*)(lang_dir + 1);
        }

        result = (IMAGE_RESOURCE_DATA_ENTRY*)
            (dwResDirVA + (entry->OffsetToData & 0x7FFFFFFF));
        error_code = RES_OK;
    }
    __except (EXCEPT_EVAL) {}

    __try
    {
        if (lpwErrorCode) *lpwErrorCode = error_code;
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {}

    __try
    {
        if (lpwLanguageFound) *lpwLanguageFound = (WORD)entry->Name;
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {}

    return result;
}

    /*
    // InternalFindResourceEx that receives dwImageBase (HMODULE) instead of
    // dwResDirVA. This function will calculate the dwResDirVA value
    // from the NT headers.
    */

IMAGE_RESOURCE_DATA_ENTRY *InternalFindResourceEx (
    HMODULE ImageBase,  // dwImageBase
    LPCTSTR lpName,     // Name of the resource or resource ID.
    LPCTSTR lpType,     // One of the RT_XXX constants (a MAKEINTRESOURCE() value).
    WORD wLanguage,     // LANGID forged with MAKELANGID().
    WORD *lpwErrorCode,     // Pointer to a variable that 
        // receives error code on failure or zero on success.
    WORD *lpwLanguageFound  // Language of the resource found. 
        // It may differ from wLanguage if that language is absent.
    )
{
    DWORD dwResDirVA = ResDirVAFromHMODULE (ImageBase, lpwErrorCode);
    if (!dwResDirVA) return NULL;

    return InternalFindResourceEx (
        dwResDirVA,
        lpName,
        lpType,
        wLanguage,
        lpwErrorCode,
        lpwLanguageFound
        );
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionShould not be a tip Pin
PJ Arends24-Mar-14 6:19
professionalPJ Arends24-Mar-14 6:19 
AnswerRe: Should not be a tip Pin
pasztorpisti24-Mar-14 19:30
pasztorpisti24-Mar-14 19:30 
Questionvery nice Pin
BillW3324-Mar-14 4:49
professionalBillW3324-Mar-14 4:49 
QuestionMy vote 5, Nice Article about resource Pin
Member 381618819-Jan-13 9:57
Member 381618819-Jan-13 9:57 
AnswerRe: My vote 5, Nice Article about resource Pin
pasztorpisti15-Mar-13 14:23
pasztorpisti15-Mar-13 14:23 
GeneralRe: My vote 5, Nice Article about resource Pin
Member 381618823-Mar-14 23:01
Member 381618823-Mar-14 23:01 
GeneralRe: My vote 5, Nice Article about resource Pin
pasztorpisti24-Mar-14 1:43
pasztorpisti24-Mar-14 1:43 
GeneralRe: My vote 5, Nice Article about resource Pin
pasztorpisti24-Mar-14 2:52
pasztorpisti24-Mar-14 2:52 
GeneralThanks for sharing. Pin
marc ochsenmeier9-Aug-12 2:54
marc ochsenmeier9-Aug-12 2:54 
GeneralMy vote of 5 Pin
gndnet30-Jul-12 21:05
gndnet30-Jul-12 21:05 
GeneralRe: My vote of 5 Pin
pasztorpisti31-Jul-12 0:28
pasztorpisti31-Jul-12 0:28 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.