Shared Handles in C++ on Win32

Shared pointers are objects in C++ that manage pointers. As a pointer to an object is passed around, copied, or deleted a shared pointer keeps track of how many references there are to the object that it refers to. When all references to the object are destroyed or go out of scope, the shared pointer will delete the object and free its memory. This has the effect of smart pointers in C++ acting almost like a managed memory environment. The burden on the developer to managming emory is pleasantly diminished.

The standard template library offers, among others, the class std::shared_ptr for creating shared pointers. There are some other classes, such as std::unique_ptr with special behaviours (in this case, ensuring that only one reference to the object exists). std::shared_ptr also lets the developer specify a custom delete for the object; if there is some specific behaviour needed for when an object is being deallocated, this feature could be used to support that. These are the signatures for some of the constructors that allow custom deleters

template< class Y, class Deleter> shared_ptr( Y* ptr, Deleter d );
template< class Deleter> shared_ptr( std::nullptr_t ptr, Deleter d );
template< class Y, class Deleter, class Alloc > shared_ptr( Y* ptr, Deleter d, Alloc alloc );
template< class Deleter, class Alloc> shared_ptr( std::nullptr_t ptr, Deleter d, Alloc alloc );
template< class Y, class Deleter> shared_ptr( std::unique_ptr<Y, Deleter>&& r );

Structures like this are not limited to being used only for pointers. They can be used for other resources too. My interest was in using them to manage handles for Windows objects, specificly handles. Handles are values that identify a system resource, such as a file. Their value is not for a memory address, but is a generally opaque numeric identifier. Think of it as an ID number. When the object that a handle refers to is nolonger needed, it should be freed with a call to CloseHandle().

I was working with a program written in C/C++ for Windows and writing a function to load the contents of a file. This is the original function.

vector<unsigned char> LoadFileContents(std::wstring sourceFileName)
{
    vector<unsigned char> retVal;
    auto hFile = CreateFile(sourceFileName.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile != INVALID_HANDLE_VALUE)
    {
        DWORD fileSize = GetFileSize(hFile, NULL);

        retVal.resize(fileSize);
        DWORD bytesRead;
        HRESULT result = ReadFile(hFile, retVal.data(), fileSize, &bytesRead, FALSE);
        CloseHandle(hFile);
    }
    return retVal;
}

Well, that’s not actually the original. In the original, I forgot to make the call to CloseHandle(). Forgetting to do this could lead to resource leaks in the program or the file not being available for writing later because a read handle is still open. For my end goal, this won’t be the only file that I use, nor will files be the only type of handles. I wanted to manage these in a safer way. Here, I use the std::unique_ptr to manage handles. I’ll make a custom deleter that will close a handle.

My custom deleter is implemented as a functor. A functor is a type of object that can be used as a function. Often these are used in callback operations. Functors, unlike typical functions, can also have state. In C++ functors are generally constructed by defining the operator() for the object. operator() can take any number of arguments. For my purposes, it only needs one argument. That’s the HANDLE to be closed. A HANDLE can have two values that indicate it isn’t referencing a value object. There is a constant, INVALID_HANDLE_VALUE (whose literal value is -1) and 0. To ensure CloseHandle() isn’t called on an invalid value, I need to check that the value passed is not either of these values and only call CloseHandle() if neither of these values was passed.

struct HANDLECloser
{
	void operator()(HANDLE handle) const
	{
		if (handle != INVALID_HANDLE_VALUE && handle != 0)
		{
			CloseHandle(handle);
		}
	}
};

Since there will only ever be one object accessing my file handles, I’ll be using std::unique_ptr for my file handles. With the above declaration I could begin using std::unique_ptr objects immediately.

auto myFileHandle = std::unique_ptr<void, HANDLECloser>(hFile);

That’s a lot to type though. In the interest of brevity, let’s make a declaration so that we can invoke that with less keystrokes.

using HANDLE_unique_ptr = std::unique_ptr<void, HANDLECloser);

With that in place, the previous call to initialize a unique pointer could be shortened to the following.

auto myFileHandle = HANDLE_unique_ptr(hFile);

That’s a bit more concise. Let’s add one more thing. Generally, I would be using this with the Win32 CreateFile function. Let’s make a CreateFileHandle() function that takes the same parameters as CreateFile but returns our std::unique_ptr for our file handle.

HANDLE_unique_ptr CreateFileHandle(std::wstring fileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile)
{
	HANDLE handle = CreateFile(fileName.c_str(), dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
	if (handle == INVALID_HANDLE_VALUE || handle == nullptr)
	{
		return nullptr;
	}
	return HANDLE_unique_ptr(handle);
}

Using these new classes that I’ve put in place,

vector<unsigned char> LoadFileContents(std::wstring sourceFileName)
{
    vector<unsigned char> retVal;
    auto hFile = CreateFileHandle(sourceFileName.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile)
    {
        DWORD fileSize = GetFileSize(hFile.get(), NULL);
        retVal.resize(fileSize);
        DWORD bytesRead;
        HRESULT result = ReadFile(hFile.get(), retVal.data(), fileSize, &bytesRead, FALSE);    
    }
    return retVal;
}

There are some other good bits of code in the project from which I took this code that I plan to share in the common weeks. Some parts are simple but useful, other parts are more complex. Come back in a couple of weeks for the next bit that I have to share.


Mastodon: @j2inet@masto.ai
Instagram: @j2inet
Facebook: @j2inet
YouTube: @j2inet
Telegram: j2inet
Twitter: @j2inet
Bluesky: j2inet.bsky.social

Posts may contain products with affiliate links. When you make purchases using these links, we receive a small commission at no extra cost to you. Thank you for your support.


One thought on “Shared Handles in C++ on Win32

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.