Click here to Skip to main content
15,867,453 members
Articles / Desktop Programming / MFC
Article

XResFile - Files Stored in Resources: Part 1 - Text and Binary

Rate me:
Please Sign up or sign in to vote.
4.96/5 (47 votes)
6 Jul 2007CPOL9 min read 69.5K   2K   121   4
This series of articles is a step-by-step guide to reading files stored in your program's resources. Along the way I will present some non-MFC classes to assist you in reading text, binary, zip, and even encrypted files that have been compiled into your program as resources.

Introduction

This series of articles discusses three typical requirements when working with file resources (by that I mean, files that have been compiled into your program as resources):

Part 1Text and binary files
Part 2Zip files
Part 3Encrypted files

Please Read This

One of the most frequent questions I see about resources is, 'How do I update a resource while the EXE is still running?' Short answer: you can't. Theoretically, you could write a second program that would update a resource of the first program when it shuts down, but this is a Really Bad Idea. It is very common for programs to be installed in directories that are set to read-only for ordinary users, and with Microsoft tightening security, it is unlikely that any kludge would work for long.

So, no, the classes that I am presenting in these articles do not offer write access to a program's resources.

Embedding a File as a Resource

For this article's demo program, I have set up resources to include seven files - six text and one binary. Here are actual lines taken from XResFileTest.rc:

/////////////////////////////////////////////////////////////////////////////
//
// TEXT
//
IDU_FILE1               TEXT    DISCARDABLE     "test1.txt"
IDU_FILE2               TEXT    DISCARDABLE     "test2.txt"
IDU_FILE3               TEXT    DISCARDABLE     "test3.txt"
IDU_FILE4               TEXT    DISCARDABLE     "test4.txt"
IDU_FILE5               TEXT    DISCARDABLE     "test5.txt"
IDU_FILE6               TEXT    DISCARDABLE     "test6.txt"

/////////////////////////////////////////////////////////////////////////////
//
// BINARY
//
IDU_FILE7               BINARY  DISCARDABLE     "test7.bin"

There is nothing special about these lines at all - you will see similar lines for icons, bitmaps, etc., in your own *.rc files. There are a few things worth noting:

  • Column 1 contains resource ID - IDU_FILE1, IDU_FILE2, through IDU_FILE7. This is clear enough. However, what isn't widely known is that resource ID can be a string. In other words, instead of defining IDU_FILE1, etc., in resource.h, you can simply not define them. This has the effect of forcing Resource Compiler to treat them as strings. In the demo app, I will show you how to deal with resources both ways - using integer resource IDs, and using string resource IDs.
  • Column 2 contains resource type - MSDN lumps all non-standard resource types into what it calls User Defined Resources. This includes types such as TEXT and BINARY that you see in the above snippet. In fact, you can name resources anything you want - BOFFORESOURCE would be perfectly acceptable. But, you should be consistent, and (hopefully) the name you use should be indicative of what the resource is.

    So at last we get to the question, What is resource type used for? Something important, it turns out. The two pieces of information that Win32 API FindResource() needs in order to locate resource are the resource ID and the resource type.
  • Column 3 contains resource attribute. This is an anachronism that I have gotten in the habit of using. According to this article on MSDN, it is ignored.
  • Column 4 contains the name of the file that you want to include. Nothing new here.

The effect of including seven files as resources is what you would expect - the size of the EXE gets larger. This is the first thing to pay attention to - there is no kind of compression applied to these files. You can see this clearly with a hex editor such as excellent free XVI32 Hex Editor:

screenshot

If you are worried about people stealing your embedded resources, then I can set your mind at ease right now - you can stop worrying, because there is no doubt that it will happen. The situation is really much worse than simple hex editors. Take a look at freeware Resource Hacker:

screenshot

With this utility, anyone can inspect, extract, and copy your embedded resources. In Parts 2 and 3, I will discuss ways to defeat utilities such as hex editors and resource extractors, but for the remainder of this article, I will talk about how to access and read the files that are embedded in the demo app.

Working with Text and Binary Files

To give you an idea of what CResourceFile and CResourceTextFile classes are capable of, let me show you some screenshots of the demo app. The first one shows ANSI text being displayed from files test1.txt, test2.txt, and test3.txt:

screenshot

This next one shows Unicode text being displayed from files test4.txt, test5.txt, and test6.txt. Notice that test5.txt has a BOM:

screenshot

The last one shows a binary file being displayed in hex from file test7.bin:

screenshot

Reading Binary Files

Each of the classes I will present in this and later articles is based on CResourceFile. This class interfaces directly with resources via holy trinity of Win32 resource APIs: FindResource(), LoadResource(), and LockResource(). The result of calling these three in succession is a pointer to a byte buffer. You can find out the size of the resource (in bytes) by calling SizeofResource(). With a pointer to buffer and its size, you can copy resource to your own buffer, for later use. This is essentially all there is to CResourceFile::Open():

C++
///////////////////////////////////////////////////////////////////////////////
//
// Open()
//
// Purpose:     Open a file resource
//
// Parameters:  hInstance      - instance handle for the resource.  A value of 
//                               NULL specifies the exe (default).
//              lpszResId      - resource name or id 
//                               (passed via MAKEINTRESOURCE)
//              lpszResType    - resource type string to look for
//
// Returns:     BOOL           - returns TRUE if resource opened ok, 
//                               otherwise FALSE
//
BOOL CResourceFile::Open(HINSTANCE hInstance, 
                         LPCTSTR lpszResId, 
                         LPCTSTR lpszResType)
{
    BOOL rc = FALSE;

    Close();

    _ASSERTE(lpszResId);
    _ASSERTE(lpszResType);

    if (lpszResId && lpszResType)
    {
        TCHAR *pszRes = NULL;

        // is this a resource name string or an id?
        if (HIWORD(lpszResId) == 0)
        {
            // id
            pszRes = MAKEINTRESOURCE(LOWORD((UINT)(UINT_PTR)lpszResId));
        }
        else
        {
            // string
            pszRes = (TCHAR *)lpszResId;
            TRACE(_T("pszRes=%s\n"), pszRes);
        }

        HRSRC hrsrc = FindResource(hInstance, pszRes, lpszResType);
        _ASSERTE(hrsrc);

        if (hrsrc)
        {
            DWORD dwSize = SizeofResource(hInstance, hrsrc);    // in bytes
            TRACE(_T("dwSize=%d\n"), dwSize);

            HGLOBAL hglob = LoadResource(hInstance, hrsrc);
            _ASSERTE(hglob);

            if (hglob)
            {
                LPVOID lplock = LockResource(hglob);
                _ASSERTE(lplock);

                if (lplock)
                {
                    // save resource as byte buffer

                    m_pBytes = new BYTE [dwSize+16];
                    memset(m_pBytes, 0, dwSize+16);
                    m_nBufLen = (int) dwSize;
                    memcpy(m_pBytes, lplock, m_nBufLen);

                    m_nPosition = 0;
                    m_bIsOpen = TRUE;
                    m_bDoNotDeleteBuffer = FALSE;    // ok to delete the buffer
                    rc = TRUE;
                }
            }
        }
    }

    return rc;
}

The buffer that is allocated in Open() is deleted in ~CResourceFile().

CResourceFile makes it easy to deal with binary resources. Here is the code that opens and displays test7.bin in the demo app:

C++
void CXResFileTestDlg::OnTestBinary() 
{
    m_List.ResetContent();

    CFont *pFont = m_List.GetFont();
    LOGFONT lf = { 0 };
    pFont->GetLogFont(&lf);
    if (m_strFaceName.IsEmpty())
    {
        m_strFaceName = lf.lfFaceName;
        // change to Courier so everything will line up
        _tcscpy(lf.lfFaceName, _T("Courier New"));
        m_font.DeleteObject();
        m_font.CreateFontIndirect(&lf);
    }
    m_List.SetFont(&m_font, TRUE);

    BYTE alphabet[52] = { 0 };
    BYTE b = 0x61;
    for (int i = 0; i < 52; i++)
        alphabet[i++] = b++;

    // test5.bin is actually UTF-16
    m_List.AddLine(CXListBox::Blue, CXListBox::White, _T
                ("=== Checking BINARY file test7.bin ==="));
    m_List.AddLine(CXListBox::Blue, CXListBox::White, _T(""));
    TestBinaryResource(_T("IDU_FILE7"), alphabet, 52);
}

And here is the function TestBinaryResource() that does the work:

C++
void CXResFileTestDlg::TestBinaryResource(LPCTSTR lpszResource, 
                    BYTE * values, int nResSize)
{
    CResourceFile rf;
    UINT nResId = 0;
    CString strRes = _T("");

    if (HIWORD(lpszResource) == 0)
    {
        nResId = (UINT)(UINT_PTR)lpszResource;
        strRes.Format(_T("%u"), nResId);
    }
    else
    {
        strRes = lpszResource;
    }

    if (rf.Open(NULL, lpszResource, _T("BINARY")))
    {
        int nSize = rf.Read(NULL, 0);

        if (nSize == nResSize)
        {
            m_List.Printf(CXListBox::Green, CXListBox::White, 0,
                _T("Binary resource '%s' size ok, %d bytes"), strRes, nSize);

            BYTE * buf = rf.GetByteBuffer();
            BOOL bContentsOk = TRUE;

            for (int i = 0; i < nResSize; i++)
            {
                if (values[i] != buf[i])
                {
                    bContentsOk = FALSE;
                    break;
                }
            }

            if (bContentsOk)
            {
                m_List.Printf(CXListBox::Green, CXListBox::White, 0,
                    _T("Binary resource '%s' contents ok"), strRes);
            }
            else
            {
                m_List.Printf(CXListBox::Red, CXListBox::White, 0,
                    _T("Binary resource '%s' contents incorrect"), strRes);
            }

            m_List.AddLine(CXListBox::Blue, CXListBox::White, _T(""));
            m_List.AddLine(CXListBox::Blue, 
                CXListBox::White, _T("Actual contents:"));
            DisplayHex(buf, rf.GetLength(), _T(""));

            m_List.AddLine(CXListBox::Blue, CXListBox::White, _T(""));
            m_List.AddLine(CXListBox::Blue, 
                CXListBox::White, _T("Contents should be:"));
            DisplayHex(values, rf.GetLength(), _T(""));
        }
        else
        {
            m_List.Printf(CXListBox::Red, CXListBox::White, 0,
                _T("Binary resource '%s' size = %d, should be %d"), 
                    strRes, nSize, nResSize);
        }

        rf.Close();
    }
    else
    {
        m_List.Printf(CXListBox::Red, CXListBox::White, 0,
            _T("Failed to open resource '%'s"), strRes);
    }
}

The highlighted lines show how the resource file is opened and its size retrieved, by calling Read() with a NULL buffer pointer. (Tip: GetLength() will do the same thing.) To do something with the buffer, you call GetByteBuffer(), which returns a pointer to CResourceFile internal byte buffer.

Summary: Reading Binary Files

The code presented above can be boiled down to:

C++
CResourceFile rf;
if (rf.Open(NULL, _T("IDU_FILE7"), _T("BINARY")))   // open resource
{
    int len = rf.GetLength();       // get length of byte buffer
    BYTE *p = rf.GetByteBuffer();   // get pointer to internal buffer
    if (p)
    {
        --- do something ---
    }
    .
    .
    .
    // rf goes out of scope and resource file is closed
}

The above example shows how to open test7.bin using string resource ID - since IDU_FILE7 is not defined in resource.h, it is treated as a string by the Resource Compiler, and to use it you must enclose it in quotes as above.

What if IDU_FILE7 was defined in resource.h? In that case, the code above would become:

C++
CResourceFile rf;
// open resource
if (rf.Open(NULL, MAKEINTRESOURCE(IDU_FILE7), _T("BINARY")))

{
    --- rest of code is unchanged ---

The Win32 MAKEINTRESOURCE() macro converts the int value IDU_FILE7 into a LPTSTR value suitable for passing as a string pointer. When CResourceFile::Open() sees this value, it can recognize it because resource ID is in low-order word, and there is zero in high-order word.

It is interesting to note difference in resource IDs, as seen by resource extractors:

screenshot

The file test2.txt is associated with an integer resource ID, defined in resource.h:

C++
#define IDU_FILE2          130

This is why resource ID is displayed in Resource Hacker as 130. By itself, using integer resource IDs will not prevent your embedded resources from being ripped, but it is one step you can take to reduce transparency of your app's inner workings - seeing IDU_PASSWORD_FILE, for example, is much more helpful to a hacker than seeing the value 130.

One last point about CResourceFile::Open(): in the demo app, you will see this line of code:

C++
rf.Open(NULL, lpszResource, _T("BINARY"))

The first parameter is NULL, which has a special meaning to FindResource() - according to MSDN,

A value of NULL specifies the module handle associated with the image file that the operating system used to create the current process.

What this means is that you can use NULL if the resource is in your EXE. But if the resource is in a DLL, you need to use the DLL's instance handle, which you can get when you call LoadLibrary():

C++
hInstanceDll = LoadLibrary("MyDll.dll");

CResourceFile Quick Reference

Here is complete list of functions available in CResourceFile:

C++
//   Close()                Close a file resource
//   DetachByteBuffer()     Return pointer to byte buffer and set flag so 
//                          buffer will not be deleted when resource is closed
//   DuplicateByteBuffer()  Duplicate the buffer associated with the binary  
//                          file resource
//   GetByteBuffer()        Get pointer to binary file resource buffer
//   GetLength()            Get length of file resource
//   GetPosition()          Get current file position of file resource
//   IsAtEOF()              Check if file pointer is at end of file
//   IsOpen()               Check if file resource is open
//   Open()                 Open a file resource
//   Read()                 Read bytes from the binary file resource
//   Seek()                 Move pointer to specified position in resource 
//                          buffer
//   SeekToBegin()          Position pointer to beginning of resource buffer
//   SeekToEnd()            Position pointer to end of resource buffer
//   SetByteBuffer()        Set a new buffer for the binary file resource

How to Add CResourceFile to Your Project

To integrate CResourceFile class into your app, do the following:

  1. You first need to add the following files to your project:
    • ResourceFile.cpp
    • ResourceFile.h
  2. In Visual Studio settings, select Not using pre-compiled header for ResourceFile.cpp.
  3. Next, include header file ResourceFile.h in the source file where you want to use CResourceFile.
  4. Now you are ready to start using CResourceFile. See above for sample code.

Reading Text Files

The class for reading resources as text files is CResourceTextFile, which is derived from CResourceFile. Why a special class for text files? The main reason is Unicode/ANSI conversion, including dealing with BOM markers. Another reason: the ability to read text resource file line-by-line. You have already seen how this works in the demo app screenshots above. When you compile VS6 project, the resulting ANSI EXE will convert Unicode text resource files to ANSI; and when you compile VS2005 project, the resulting Unicode EXE will convert ANSI text resource files to Unicode.

The primary interface to access text resource files is CResourceTextFile::Open(). Its first three parameters are the same as the base class CResourceFile::Open(). It has an additional two parameters, to specify ANSI/Unicode conversion action, and how to deal with BOM markers:

C++
///////////////////////////////////////////////////////////////////////////////
//
// Open()
//
// Purpose:     Open a file resource
//
// Parameters:  hInstance      - instance handle for the resource.  A value of 
//                               NULL specifies the exe (default).
//              lpszResId      - resource name or id 
//                               (passed via MAKEINTRESOURCE)
//              lpszResType    - resource type string to look for
//              eConvertAction - convert action to take
//              eBomAction     - action to take with BOM
//
// Returns:     BOOL           - returns TRUE if resource opened ok, 
//                               otherwise FALSE
//
BOOL CResourceTextFile::Open(HINSTANCE hInstance, 
                             LPCTSTR lpszResId, 
                             LPCTSTR lpszResType /*= _T("TEXT")*/,
                             ConvertAction eConvertAction /*= NoConvertAction*/,
                             BomAction eBomAction /*= NoBomAction*/)

The types for the two additional parameters are defined as:

C++
enum ConvertAction
{
    NoConvertAction = 0, ConvertToUnicode, ConvertToAnsi
};

enum BomAction
{
    NoBomAction = 0, RemoveBom, AddBom
};

These two parameters allow you to control how text files are converted and read. Note that you have total control over this - if you do not specify any conversion action, then none will be taken. This includes processing of the BOM markers - even if you specify a BomAction, no ANSI/Unicode conversion will automatically be done.

The sample app shows how to handle different conversion scenarios. Here is the code that displays ANSI text file resources:

C++
    CResourceTextFile::ConvertAction eConvertAction = 
                CResourceTextFile::NoConvertAction;

#ifdef _UNICODE
    eConvertAction = CResourceTextFile::ConvertToUnicode;
#endif

    // test1 - 3 are ANSI
    m_List.AddLine(CXListBox::Blue, CXListBox::White, 
        _T("=== Checking  ANSI TEXT file test1.txt ==="));
    TestTextResource(_T("IDU_FILE1"), 1, 15, eConvertAction, 
        CResourceTextFile::NoBomAction);

    m_List.AddLine(CXListBox::Blue, CXListBox::White, _T(""));
    m_List.AddLine(CXListBox::Blue, CXListBox::White, 
        _T("=== Checking  ANSI TEXT file test2.txt ==="));
    TestTextResource(MAKEINTRESOURCE(IDU_FILE2), 2, 15, 
        eConvertAction, CResourceTextFile::NoBomAction);

    m_List.AddLine(CXListBox::Blue, CXListBox::White, _T(""));
    m_List.AddLine(CXListBox::Blue, CXListBox::White, 
        _T("=== Checking  ANSI TEXT file test3.txt ==="));
    TestTextResource(_T("IDU_FILE3"), 3, 15, eConvertAction, 
        CResourceTextFile::NoBomAction);

And here is the code that displays Unicode text file resources:

C++
    CResourceTextFile::ConvertAction eConvertAction = 
        CResourceTextFile::NoConvertAction;

#ifndef _UNICODE
    eConvertAction = CResourceTextFile::ConvertToAnsi;
#endif

    // test4.txt is UTF-16 (no BOM)
    m_List.AddLine(CXListBox::Blue, CXListBox::White, 
        _T("=== Checking UNICODE TEXT file test4.txt ==="));
    TestTextResource(_T("IDU_FILE4"), 1, 15, eConvertAction, 
        CResourceTextFile::NoBomAction);

    // test5.txt is UTF-16 (with BOM)
    m_List.AddLine(CXListBox::Blue, CXListBox::White, _T(""));
    m_List.AddLine(CXListBox::Blue, CXListBox::White, 
        _T("=== Checking UNICODE TEXT WITH BOM file test5.txt ==="));
    TestTextResource(_T("IDU_FILE5"), 2, 15, eConvertAction, 
        CResourceTextFile::RemoveBom);

    // test6.txt is UTF-16 (no BOM)
    m_List.AddLine(CXListBox::Blue, CXListBox::White, _T(""));
    m_List.AddLine(CXListBox::Blue, CXListBox::White, 
        _T("=== Checking UNICODE TEXT file test6.txt ==="));
    TestTextResource(_T("IDU_FILE6"), 3, 15, eConvertAction, 
        CResourceTextFile::NoBomAction);

By knowing the code set of your EXE, and format of text file resource, you can set up your app to read both ANSI and Unicode text file resources.

Summary: Reading Text Files

Here is a stripped-down version of code presented in the demo app for reading text files:

C++
CResourceTextFile rf;

if (rf.Open(NULL, lpszResource, _T("TEXT"), eConvertAction, eBomAction))
{
    while (!rf.IsAtEOF())
    {
        TCHAR s[100] = { _T('\0') };
        int nLen = rf.ReadLine(s, sizeof(s)/sizeof(TCHAR)-1);
        if (nLen > 0)
        {
            --- do something ---
        }
        else
        {
            --- line was empty ---
        }
    }

    rf.Close();      // optional - will close when rf goes out of scope
}

CResourceTextFile Quick Reference

Here is a complete list of functions implemented in CResourceTextFile:

C++
//   Close()                Close a file resource
//   DetachTextBuffer()     Return pointer to text buffer and 
//                          set flag so buffer
//                          will not be deleted when resource is closed
//   DuplicateTextBuffer()  Duplicate the buffer associated with the text file 
//                          resource
//   GetBomAction()         Get action to take for BOM
//   GetConvertAction()     Get convert action
//   GetTextBuffer()        Get pointer to text file resource buffer
//   Open()                 Open a file resource
//   ReadLine()             Read a line of text from the text file resource
//   SetTextBuffer()        Set a new buffer for the text file resource

How to Add CResourceTextFile To Your Project

To integrate CResourceTextFile class into your app, do the following:

  1. You first need to add following files to your project:
    • ResourceFile.cpp
    • ResourceFile.h
    • ResourceTextFile.cpp
    • ResourceTextFile.h
  2. In Visual Studio settings, select Not using pre-compiled header for ResourceFile.cpp and ResourceTextFile.cpp.
  3. Next, include header file ResourceTextFile.h in the source file where you want to use CResourceTextFile.
  4. Now you are ready to start using CResourceTextFile. See above for sample code.

Implementation Notes

CResourceFile and CResourceTextFile have been implemented using C++, without any MFC or STL. They have been compiled under both VS6 and VS2005, and have been tested on XP and Vista. They should also work on Win2000 and Win98, but have not been tested on those platforms.

Summary

I have presented two classes that take care of mundane aspects of accessing text and binary files that have been included as resources in your app. In the next article, I will discuss the possibility of including zip file as resource.

Revision History

Version 1.0 — 2007 July 7

  • Initial public release

Usage

This software is released into the public domain. You are free to use it in any way you like, except that you may not sell this source code. If you modify it or extend it, please consider posting new code here for everyone to share. This software is provided "as is" with no expressed or implied warranty. I accept no liability for any damage or loss of business that this software may cause.

License

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


Written By
Software Developer (Senior) Hans Dietrich Software
United States United States
I attended St. Michael's College of the University of Toronto, with the intention of becoming a priest. A friend in the University's Computer Science Department got me interested in programming, and I have been hooked ever since.

Recently, I have moved to Los Angeles where I am doing consulting and development work.

For consulting and custom software development, please see www.hdsoft.org.






Comments and Discussions

 
GeneralLockResource Pin
bob169726-Jun-08 10:43
bob169726-Jun-08 10:43 
GeneralRunning binary resources Pin
Waldermort21-Aug-07 20:35
Waldermort21-Aug-07 20:35 
GeneralGot my 5 Pin
Cristian Amarie15-Jul-07 5:58
Cristian Amarie15-Jul-07 5:58 
GeneralNice! Pin
Wes Aday7-Jul-07 5:28
professionalWes Aday7-Jul-07 5: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.