The Windows SDK and Pointers
Returning a pointer from a function has its inherent dangers: the responsibility for the memory is passed to the caller, and the caller must ensure that the memory is appropriately de-allocated, otherwise this could cause a memory leak with a corresponding loss of performance. In this section, we will look at some ways that the Window's Software Development Kit (SDK) provides access to memory buffers and learn some techniques used in C++.
First, it is worth pointing out that any function in the Windows SDK that returns a string, or has a string parameter, will come in two versions. The version suffixed with A indicates that the function uses ANSI strings, and the W version will use wide character strings. For the purpose of this discussion, it is easier to use the ANSI functions.
The GetCommandLineA function has the following prototype (taking into account the Windows SDK typedef):
char * __stdcall GetCommandLine();
All Windows functions are defined as using the __stdcall calling convention. Usually, you will see the typedef of WINAPI used for the __stdcall calling convention.
The function can be called like this:
//#include <windows.h>
cout << GetCommandLineA() << endl;
Notice that we are making no effort to do anything about freeing the returned buffer. The reason is that the pointer points to memory that lives the lifetime of your process, so you should not release it. Indeed, if you were to release it, how would you do it? You cannot guarantee that the function was written with the same compiler, or the same libraries that you are using, so you cannot use the C++ delete operator or the C free function.
When a function returns a buffer, it is important to consult the documentation to see who allocated the buffer, and who should release it.
Another example is GetEnvironmentStringsA:
char * __stdcall GetEnvironmentStrings();
This also returns a pointer to a buffer, but this time the documentation is clear that after using the buffer you should release it. The SDK provides a function to do this called FreeEnvironmentStrings. The buffer contains one string for each environment variable in the form name=value and each string is terminated by a NUL character. The last string in the buffer is simply a NUL character, that is, there are two NUL characters at the end of the buffer. These functions can be used like this:
char *pBuf = GetEnvironmentStringsA();
if (nullptr != pBuf)
{
char *pVar = pBuf;
while (*pVar)
{
cout << pVar << endl;
pVar += strlen(pVar) + 1;
}
FreeEnvironmentStringsA(pBuf);
}
The strlen function is part of the C runtime library and it returns the length of a string. You do not need to know how the GetEnvironmentStrings function allocates the buffer because the FreeEnvironmentStrings will call the correct deallocation code.
There are cases when the developer has the responsibility of allocating a buffer. The Windows SDK provides a function called GetEnvironmentVariable to return the value of a named environment variable. When you call this function, you do not know if the environment variable is set, or if it is set, or how big its value is, so this means that you will most likely have to allocate some memory. The prototype of the function is:
unsigned long __stdcall GetEnvironmentVariableA(const char *lpName,
char *lpBuffer, unsigned long nSize);
There are two parameters that are pointers to C strings. There is a problem here, a char* pointer could be passing in a string to the function, or it could be used to pass in a buffer for a string to be returned out. How do you know what a char* pointer is intended to be used for?
You are given a clue with the full parameter declaration. The lpName pointer is marked const so the function will not alter the string it points to; this means that it is an in parameter. This parameter is used to pass in the name of the environment variable you want to obtain. The other parameter is simply a char* pointer, so it could be used to pass a string in to the function or out, or indeed, both in and out. The only way to know how to use this parameter is to read the documentation. In this case, it is an out parameter; the function will return the value of the environment variable in lpBuffer if the variable exists, or if the variable does not exist, the function will leave the buffer untouched and return the value 0. It is your responsibility to allocate this buffer in whatever way you see fit, and you pass the size of this buffer in the last parameter, nSize.
The function's return value has two purposes. It is used to indicate that an error has occurred (just one value, 0, which means you have to call the GetLastError function to get the error), and it is also used to give you information about the buffer, lpBuffer. If the function succeeds, then the return value is the number of characters copied into the buffer excluding the NULL terminating character. However, if the function determines that the buffer is too small (it knows the size of the buffer from the nSize parameter) to hold the environment variable value, no copy will happen, and the function will return the required size of the buffer, which is the number of characters in the environment variable including the NULL terminator.
A common way to call this function is to call it twice, first with a zero-sized buffer and then use the return value to allocate a buffer before calling it again:
unsigned long size = GetEnvironmentVariableA("PATH", nullptr, 0);
if (0 == size)
{
cout << "variable does not exist " << endl;
}
else
{
char *val = new char[size];
if (GetEnvironmentVariableA("PATH", val, size) != 0)
{
cout << "PATH = ";
cout << val << endl;
}
delete [] val;
}
In general, as with all libraries, you have to read the documentation to determine how the parameters are used. The Windows documentation will tell you if a pointer parameter is in, out, or in/out. It will also tell you who owns the memory and whether you have the responsibility for allocating and/or freeing the memory.
Whenever you see a pointer parameter for a function, take special care to check the documentation as to what the pointer is used for and how the memory is managed.