Embedding (and Extracting) Binary Files like DLLs into an EXE as Resources

A Windows .EXE executable file can contain binary resources, which are basically arbitrary binary data embedded in the file.

In particular, it’s possible to embed one or more DLLs as binary resources into an EXE. In this article, I’ll first show you how to embed a DLL as a binary resource into an EXE using the Visual Studio IDE; then, you’ll learn how to access that binary resource data using proper Windows API calls.

A Windows EXE file can contain one or more DLLs embedded as binary resources.

Embedding a Binary Resource Using Visual Studio IDE

If you are using Visual Studio to develop your Windows C++ applications, from Solution Explorer you can right-click your EXE project node, then choose Add > Resource from the menu.

Menu command to add a resource using Visual Studio.
Adding a resource from the Visual Studio IDE

Then click the Import button, and select the binary resource to embed into the EXE, for example: TestDll.dll.

The Add Resource dialog box in Visual Studio
Click the Import button to add the binary resource (e.g. DLL)

In the Custom Resource Type dialog box that appears next, enter RCDATA as Resource type.

Then click the OK button.

A hex representation of the resource bytes is shown in the IDE. Type Ctrl+S or click the diskette icon in the toolbar to save the new resource data.

You can close the binary hex representation of the resource.

The resource was automatically labeled by the Visual Studio IDE as IDR_RCDATA1. To change that resource ID, you can open Resource View. Then, expand the project node, until you see the RCDATA virtual folder, and then IDR_RCDATA1 inside it. Click the IDR_RCDATA1 item to select it.

In the Properties grid below, you can change the ID field, for example: you can rename the resource ID as IDR_TEST_DLL.

Type Ctrl+S to save the modifications.

The binary resource ID under the RCDATA virtual folder in Resource View
The binary resource ID (IDR_TEST_DLL) under the RCDATA virtual folder in Resource View
The resource properties grid
The Properties grid to edit the resource properties

Don’t forget to #include the resource header (for example: “resource.h”) in your C++ code when you need to refer to the embedded resource by its ID.

In particular, if you open the resource.h file that was created and modified by Visual Studio, you’ll see a #define line that associates the “symbolic” name of the resource (e.g. IDR_TEST_DLL) with an integer number that represents the integer ID of the resource, for example:

#define IDR_TEST_DLL            101

Accessing an Embedded Binary Resource from C/C++ Code

Once you have embedded a binary resource, like a DLL, into your EXE, you can access the resource’s binary data using some specific Windows APIs. In particular:

  1. Invoke FindResource to get the specified resource’s information block (represented by an HRSRC handle).
  2. Invoke LoadResource, passing the above handle to the resource information block. On success, LoadResource will return another handle (declared as HGLOBAL for backward compatibility), that can be used to access the first byte of the resource.
  3. Invoke LockResource passing the resource handle returned by LoadResource, to get access to the first byte of the resource.

To get the size of the resource, you can call the SizeofResource API.

The above “API dance” can be translated into the following C++ code:

#include "resource.h"  // for the resource ID (e.g. IDR_TEST_DLL)


// Locate the embedded resource having ID = IDR_TEST_DLL
HRSRC hResourceInfo = ::FindResource(hModule,
                                     MAKEINTRESOURCE(IDR_TEST_DLL),
                                     RT_RCDATA);
if (hResourceInfo == nullptr)
{
    // Handle error...
}

// Get the handle that will be used to access 
// the first byte of the resource
HGLOBAL hResourceData = ::LoadResource(hModule, hResourceInfo);
if (hResourceData == nullptr)
{
    // Handle error...
}

// Get the address of the first byte of the resource
const void * pvResourceData = ::LockResource(hResourceData);
if (pvResourceData == nullptr)
{
    // Handle error...
}

// Get the size, in bytes, of the resource
DWORD dwResourceSize = ::SizeofResource(hModule, hResourceInfo);
if (dwResourceSize == 0)
{
    // Handle error...
}

I uploaded on GitHub a C++ demo code that extracts a DLL embedded as a resource in the EXE, and, for testing purposes, invokes a function exported from the extracted DLL. In particular, you can take a look at the ResourceBinaryView.h file for a reusable C++ class to get a read-only binary view of a resource.

P.S. An EXE is not the only type of Windows Portable Executable (PE) file that can have embedded resources. For example: DLLs can contain resources, as well.

How to Load CString from Resources

Very easy! It’s just a matter of invoking a constructor with a type cast.

Last time we saw how to load a string resource into a wstring. As already explained, ATL/MFC’s CString does offer very convenient methods for Win32 C++ programming. So, how can you load a string resource into a CString instance?

Well, it’s very simple! Just pass the string ID to a CString constructor overload with a proper type cast:

// Load string with ID IDS_MY_STRING from resources 
// into a CString instance
CString myString( (PCTSTR) IDS_MY_STRING );

You can even #define a simple preprocessor macro that takes the string resource ID and and creates a CString object storing the string resource:

#define _S(stringResId) (CString((PCTSTR) (stringResId)))

When you need to pass a (localized) string loaded from resources to a Win32 API or ATL/MFC class member function that expects a string pointer (typically in the form of LPCTSTR/PCTSTR, i.e. const TCHAR*), then you can simply use the convenient macro defined above:

// Show a message-box to the user, 
// displaying strings loaded from resources
MessageBox(nullptr,
           _S(IDS_SOME_MESSAGE_FOR_THE_USER),
           _S(IDS_SOME_TITLE),
           MB_OK);

How does that work?

Well, first the _S macro defined above creates a (temporary) CString object and loads the string resource into it. Then, the implicit LPCTSTR conversion operator provided by CString is invoked by the C++ compiler, so the (read-only) string pointer is passed to the proper parameter of the MessageBox API.

See how simple is that compared to using std::wstring, where we needed to create an ad hoc non-trivial function to load wstring from resources!

How to Load std::wstring from Resources

CString easily allows loading strings from resources “out of the box”. Let’s try to implement this feature for std::wstring.

As discussed in the previous blog post, CString offers convenient methods for Windows C++ programming, including those to load strings from resources.

What Is a String Resource and Why Should You Care?

Basically, in a Windows Win32 C++ application you can store your strings (like messages for the user) as resources, and then reference them in your C++ code via their IDs. If you want to localize your application for a different language, a translator can take the string resources and translate them into the destination language. The C++ source code remains the same, as it references strings not by their language-specific literal form (e.g. “Cannot access the Registry key XYZ”), but using language-neutral resource IDs (e.g. IDS_CANNOT_ACCESS_REGISTRY_KEY_XYZ).

Accessing String Resources via the LoadString(W) API

To access a Unicode (UTF-16) string resource, you can use the LoadStringW Win32 API.

The Microsoft documentation for the LoadStringW API reads:

Loads a string resource from the executable file associated with a specified module and either copies the string into a buffer with a terminating null character or returns a read-only pointer to the string resource itself.

From that paragraph, we get that there are two different behaviors available for this API:

  1. Copy the string resource into a user-provided buffer.
  2. Return a read-only pointer to the string resource itself.

Behavior #1: Copying the String Resource into a User-provided Buffer

The first behavior is shown several times in Microsoft documentation. For example, the doc page about LoadStringW refers to the following sample code:

Sample code for LoadString, referenced by the official Microsoft documentation of that API.
Example code for LoadString referenced by the official Microsoft documentation

Basically, the idea here is to create a TCHAR buffer, and invoke LoadString to load the string resource into that buffer:

TCHAR sz[160];
LoadString(hInst, IDS_UNTITLED, sz, sizeof(sz)/sizeof(TCHAR));

In Unicode builds, TCHAR becomes WCHAR (i.e. wchar_t), and LoadString becomes LoadStringW.

One of the problem of this kind of code is that the buffer is pre-allocated with a given fixed size. Then, what happens if the string read from resources is larger than the pre-allocated buffer? The string is silently truncated, as per the official MS documentation about the cchBufferMax parameter:

The string is truncated and null-terminated if it is longer than the number of characters specified.

This could make the UI ugly if it is just a user message string that gets truncated; or it can become a much more serious bug if the truncated string is something like the format specifier for some printf-like function.

Moreover, in that code there is no check on the return value of LoadString. So, if the function fails, the buffer could contain garbage, and that could cause other bugs. (Although, I understand that often MS documentation doesn’t include error checking; but it’s important to point that out, as often people copy-paste code from MS doc samples, and silent bugs are introduced in their own code.)

In addition, it’s important to remember to properly scale the “sizeof” the destination buffer, dividing it by sizeof(TCHAR), which is sizeof(WCHAR) == 2 (bytes) in Unicode builds. Failing to do so would cause additional buffer overflow problems.

You can use a std::wstring of proper size, and pass a pointer to the internal string characters instead of using a raw wchar_t array, but the aforementioned problems still remain.

Behavior #2: Getting a Read-only Pointer to the String Resource

To me the behavior #2 (which is: get a read-only pointer to the string resource itself) is what leads to better and easier code (despite some type cast). However, it’s not very clear from the current MS documentation how to operate the LoadStringW API in this “behavior #2” mode.

So, let’s explain that here. Let’s start with the function declaration:

int LoadStringW(
  [in, optional] HINSTANCE hInstance,
  [in]           UINT      uID,
  [out]          LPWSTR    lpBuffer,
  [in]           int       cchBufferMax
);

To enable behavior #2 (i.e. return a read-only pointer to the string resource itself), you need to pass 0 (zero) for the last parameter cchBufferMax.

If you pass zero for cchBufferMax, the LoadStringW API says: “Hey, the caller wants me to just return a read-only pointer to the string resource itself, with no deep-copies in a user-provided buffer. Ok, let’s do that!”

Where does the API get the information about the destination string pointer? In other words: Where can the LoadStringW API write the string resource’s address? Well, it uses the third parameter for that: lpBuffer.

However, there’s a gotcha here (that I think is not well documented in the official MS web page). Basically, you need to create a const wchar_t pointer that will store the address of the resource string (which is read-only, hence the const):

// Pointer to the start of the string resource
const wchar_t* pchStringBegin = nullptr;

Then you need to pass the address of this pointer as the lpBuffer parameter to LoadStringW: &pchStringBegin. Note that, in this way, you are using a double-indirection, i.e. a pointer-to-a-pointer. Since the lpBuffer parameter is declared as LPWSTR (i.e. wchar_t*), you have a type mismatch for this parameter. In fact, LoadStringW expects a simple wchar_t* pointer; but you are passing a pointer-to-a-wchar_t-pointer (double indirection). However, this type mismatch is needed and expected by the behavior #2, so in this case you can safely use reinterpret_cast to make the C++ compiler happy about that type mismatch.

So, the LoadStringW call can look like this:

// Invoke LoadStringW, requesting a pointer 
// to the beginning of the string resource
int cchStringLength = ::LoadStringW(
        hInstance, 
        resourceId, 
        reinterpret_cast<PWSTR>(&pchStringBegin), 
        0);

Note that the string resource is not necessarily NUL-terminated! But, thankfully, on success, the LoadStringW API returns the number of characters (as count of wchar_ts) in the string resource.

So, you can use these two pieces of information to build a std::wstring instance to safely store the string resource:

  1. The pointer to the start of the string resource
  2. The number of wchar_ts in the string resource

In code, this becomes:

if (cchStringLength > 0) // LoadStringW succeeded
{
    // Create a std::wstring storing the string loaded from resources
    return std::wstring(pchStringBegin, cchStringLength);
}

On failure, LoadStringW returns zero. Then you can choose if you want to throw an exception, log an error message, return an empty string to the user, return a std::optional<std::wstring>, or whatever.

Wrapping Up: An Easy-to-Use and Hard-to-Misuse Helper Function for Loading String Resources into std::wstring

To wrap up this blog post, this is a nice LoadStringFromResources reusable C++ function that you can invoke to load string resources into std::wstring:

//
// Load a string resource into a std::wstring
//
[[nodiscard]] std::wstring LoadStringFromResources(
    _In_opt_ HINSTANCE hInstance, 
    _In_     UINT resourceId
)
{
	// Pointer to the first character of the string.
	// NOTE: *Not* necessarily null-terminated!!
	const wchar_t* pchStringBegin = nullptr;

	// Invoke LoadStringW, requesting a pointer 
    // to the beginning of the string resource itself
	const int cchStringLength = ::LoadStringW(
            hInstance, 
            resourceId, 
            reinterpret_cast<PWSTR>(&pchStringBegin),
            0);

	ATLASSERT(cchStringLength >= 0);
	if (cchStringLength > 0)
	{
        // Success
		return std::wstring(pchStringBegin, cchStringLength);
	}
	else
	{
        // Failure: Throw an exception
		// const DWORD error = ::GetLastError();
		AtlThrowLastWin32();
	}
}