Enumerating, Creating, and Deleting Registry Keys and the Windows API

The RegOpenKeyEx() function retrieves an HKEY open handle that is assigned to an HKEY variable via a pointer to that variable, which is passed in as the fifth and final parameter of the function. The return value for RegOpenKeyEx() is normally ERROR_SUCCESS.

The RegEnumKeyEx() function enumerates the subkey names of an open registry key. The RegEnumKeyEx() function takes 8 parameters. The first parameter of RegOpenKeyEx() is a handle to an open registry key that was opened with the KEY_ENUMERATE_SUB_KEYS access right. The KEY_READ access right encompasses the KEY_ENUMERATE_SUB_KEYS access right.

The second parameter for the RegEnumKeyEx() function is a DWORD value indicating the index of the subkey to retrieve. This DWORD value should be 0 on the first call and then should be incremented on each subsequent call.

The third parameter for the RegNumKeyEx() function is a pointer to the buffer that stores the name of the subkey, including a terminating NULL character. The size limit for a key name is 255 characters. The fourth parameter is a pointer to a DWORD value that indicates the size limit of the buffer we are sending into the function; this size is in characters and includes the terminating NULL character.

The fifth parameter is reserved and must be NULL; the sixth and seventh parameters can be set to NULL, so we will. To be fair, they are rarely used. The final parameter is a pointer to a FILETIME structure.

If the function succeeds, the value is ERROR_SUCCESS.

#include "stdafx.h"
#include <Windows.h>

int main()
{
 const int Max_Key_Length = 256;
 LONG lRValue;
 DWORD dwIndex;
 DWORD dwKeyNameLength = Max_Key_Length;
 HKEY hOpenKey; 

 //let's get an open key 
 //use the HKEY_CURRENT_USER predefined key
 //let's look for the Console subkey
 //third argument is reserved, so 0
 //we want to be able to read and enumerate the subkeys
 //final argument is a pointer to the 
 //HKEY variable
 lRValue = RegOpenKeyEx(HKEY_CURRENT_USER, TEXT("SOFTWARE"), 0, KEY_READ, &hOpenKey);
 
 //check to see that we opened the key
 if (lRValue == ERROR_SUCCESS) {
 printf("Opened the subkey successfully.\n\n");
 }
 else {
 printf("Error encountered.\n");
 exit(EXIT_FAILURE);
 }

 //let's enumerate the subkeys
 
 //priming read 
 lRValue = RegEnumKeyEx(hOpenKey, dwIndex, keyName, &dwKeyNameLength, 0, NULL, NULL, &structFT);
 while (lRValue == ERROR_SUCCESS) {
 _tprintf(_T("%s\n"), keyName);
 dwKeyNameLength = Max_Key_Length;
 lRValue = RegEnumKeyEx(hOpenKey, ++dwIndex, keyName, &dwKeyNameLength, 0, NULL, NULL, &structFT);
 }

 if (lRValue == ERROR_NO_MORE_ITEMS) {
 printf("\nSubkeys all enumerated successfully.\n\n");
 }

 return 0;
}

We can create a new Registry key with the RegCreateKeyEx() function. If the subkey already exists, then the RegCreateKeyEx() function opens it. The RegCreateKeyEx() function takes 9 parameters.

The first parameter for RegCreateKeyEx() is an open handle to a registry key. We must have KEY_CREATE_SUB_KEY access to the specified key. The second parameter is the name of the subkey to be created or opened. This parameter cannot be NULL.

The third parameter for RegCreateKeyEx() is reserved and so must be set to 0. The fourth parameter can be set to NULL. The fifth parameter is usually set to 0; however, we will set the option to REG_OPTION_VOLATILE, which means that the key created by the function will not be saved. Nonvolatile registry information is stored in a file and saved for when Windows restarts. Volatile registry keys only exist in memory and do not persist between system restarts.

The sixth parameter specifies the security and access rights for the new key. We will use KEY_ALL_ACCESS for this parameter. The seventh parameter is a pointer to a SECURITY_ATTRIBUTES structure; if we specify NULL for this parameter, then the key receives the default security descriptor.

The eighth argument is a pointer to an HKEY handle that will receive the handle to the new key. The ninth and final parameter is a pointer to a DWORD variable the will store one of two values, REG_OPENED_EXISTING_KEY or REG_CREATED_NEW_KEY.

#include "stdafx.h"
#include <Windows.h>

int main()
{
 HKEY hKey, hSubkey;
 DWORD dwDisposition;
 TCHAR *tszSubkeyName = _T("Volatile Environment");
 TCHAR *tszNewkeyName = _T("testing");
 LONG lReturnValue = 0;

 lReturnValue = RegOpenKeyEx(HKEY_CURRENT_USER, tszSubkeyName, 0, KEY_CREATE_SUB_KEY, &hKey);

 if (lReturnValue == ERROR_SUCCESS) {
 _tprintf(_T("Accessed HKEY_CURRENT_USER\\%s\n"), tszSubkeyName);
 }
 else {
 exit(EXIT_FAILURE);
 }

 //create new key
 lReturnValue = RegCreateKeyEx(hKey, tszNewkeyName, 0, NULL, REG_OPTION_VOLATILE, KEY_ALL_ACCESS, NULL, &hSubkey, &dwDisposition);

 if (lReturnValue == ERROR_SUCCESS) {
 printf("Created/opened new subkey.\n");
 if (dwDisposition == REG_OPENED_EXISTING_KEY) {
 _tprintf(_T("%s already exists, is now opened.\n"), tszNewkeyName);
 }
 else {
 _tprintf(_T("%s created, is now opened.\n"), tszNewkeyName);
 }
 }

 //clean up after ourselves
 RegCloseKey(hKey);

 return 0;
}

Note that we use the RegCloseKey() function to close an open handle to a Registry key.

We use the RegDeleteKey() function to delete a key. It is a simple function to use; it takes only two arguments. The first argument is an open key handle, the second is the name of the subkey to be deleted.

#include "stdafx.h"
#include <Windows.h>

int main()
{

 LONG lRValue = 0;

 lRValue = RegDeleteKey(HKEY_CURRENT_USER, _T("Volatile Environment\\testing"));
 
 if (lRValue == ERROR_SUCCESS) {
 printf("The testing subkey was deleted successfully.\n");
 }
 else {
 printf("The testing subkey was not deleted successfully.\n");
 printf("Error %d\n", lRValue);
 if (lRValue == ERROR_FILE_NOT_FOUND) {
 printf("The registry key was not found.\n");
 }
 }

 return 0;
}

Working with Temporary Files Using win32

To retrieve the designated directory for temporary files, we can use the GetTempPath() function. The function takes two arguments, the first argument indicates the size of the buffer to store the path in TCHARs, and the second argument is the buffer to store the path itself.

The GetTempFileName() function generates a unique file name, with the .tmp suffix, in a specified directory, and optionally will create the file. The function takes four arguments. The first argument is the directory for the temporary file. We can use the GetTempPath() function to fetch the path to to the directory for temporary files, or we can use the current directory by entering a period here. The second argument is the prefix of the temporary name; it should be three characters or less. The third argument is usually set to zero. If the value is not zero, the file will not be created. The final argument is the buffer that receives the temporary file name. The length of this buffer should at the least be set to MAX_PATH.

#include "stdafx.h"
#include <Windows.h>

int main()
{
 TCHAR tchTmpPath[MAX_PATH];
 TCHAR tchTmpBuffer[MAX_PATH];

 //GetTempPath() returns nonzero on success
 //size is in TCHARs
 if (GetTempPath(MAX_PATH, tchTmpPath) == 0) {
 printf("Error getting temporary file directory.\n");
 exit(EXIT_FAILURE);
 }

 //returns 0 on failure
 if (GetTempFileName(tchTmpPath, _T("TMP"), 0, tchTmpBuffer) == 0) {
 printf("Error getting temporary file name.\n");
 exit(EXIT_FAILURE);
 }

 _tprintf(_T("Temporary file created: %s\n"), tchTmpBuffer);

 return 0;
}

For our next example, we will implement a variant of the *nix touch command that updates the file access and modification time of all the files in the directory to the current system time. We will use the FindFirstFile() and FindNextFile() functions to list all the files in the current working directory.

The GetSystemTimeAsFileTime() function retrieves the current system date and time and stores it in a FILETIME structure.

#include "stdafx.h"
#include <Windows.h>

int UpdateFileTime(const TCHAR *tchFileNamePtr);

int main()
{
	WIN32_FIND_DATA structWIN32Data;
	HANDLE hSearchFile;

	hSearchFile = FindFirstFile(_T("*"), &structWIN32Data);

	if (hSearchFile == INVALID_HANDLE_VALUE) {
		printf("Error getting search handle.\n");
		exit(EXIT_FAILURE);
	}
	//use do-while loop to loop through 
        //all files in the current directory
	do {
		if (UpdateFileTime(structWIN32Data.cFileName) > 0) {
			_tprintf(_T("Error updating %s\n"), structWIN32Data.cFileName);
		}
	} while (FindNextFile(hSearchFile, &structWIN32Data));

    return 0;
}

int UpdateFileTime(const TCHAR *tchFileNamePtr) {
	FILETIME structFT;
	HANDLE hFile;

	_tprintf(_T("Updating file time for %s\n"), tchFileNamePtr);
	//retrieves the current date and time
	GetSystemTimeAsFileTime(&structFT);
	hFile = CreateFile(tchFileNamePtr, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	//could not get handle?
	if (hFile == INVALID_HANDLE_VALUE) {
		return GetLastError();
	}

	//change the file time
	SetFileTime(hFile, NULL, &structFT, &structFT);

	//close the file handle
	CloseHandle(hFile);

	return 0;

}

File locking is a limited method of managing concurrency between process and threads.

Windows can lock files so that no other process, or thread within the process, can access the locked region of the file. File locks can be shared or exclusive. Any attempt to write to the locked region, or to obtain a conflicting lock, will fail.

The LockFileEx() function locks a byte range in an open file for either shared or exclusive access. The first argument to the function is the handle of an open file, which must at least have GENERIC_READ access. The second argument is a flag that determines the lock mode and whether to wait for the lock to become available.

 #include "stdafx.h"
#include <Windows.h>

int main()
{
 //exclusive, read-write lock (default is write lock)
 printf("LOCKFILE_EXCLUSIVE_LOCK = %d\n", LOCKFILE_EXCLUSIVE_LOCK);
 
 //return immediately with FALSE if lock can't be 
 //acquired
 printf("LOCKFILE_FAIL_IMMEDIATELY = %d\n", LOCKFILE_FAIL_IMMEDIATELY);

 return 0;
}

We’ll look at file locking in greater depth at another time.

File Pointers and File Attributes in win32

Windows keeps a file pointer for each open file handle, indicating the current position in the file. When we open the file with CreateFile(), the pointer is set to the start of the file. The WriteFile() and ReadFile() functions transfer data to or from that position and increment the file pointer by the number of bytes processed. We can move the file pointer around the file using the SetFilePointerEx() function, which can handle 64-bit pointers.

The SetFilePointerEx() function uses the LARGE_INTEGER union, which is a 64-bit data type, for both the requested position and the actual position reached. The return value is a Boolean value that indicates success or failure. The function takes four parameters. The first parameter is the handle to the file. The second and third parameters are of type LARGE_INTEGER; the third parameter is a pointer to a LARGE_INTEGER. The final parameter is a DWORD value, which can be set to one of three named constants, FILE_BEGIN, FILE_CURRENT, and FILE_END.

#include "stdafx.h"

#include <Windows.h>

int main()
{
 HANDLE hFile;
 LARGE_INTEGER liMoveTo;
 LARGE_INTEGER liNewLocation;
 DWORD dwBytesWritten, dwBytesRead;
 TCHAR *tszText = _T("The President has been kidnapped by ninjas. Are you a bad enough dude to rescue the President?");
 TCHAR tszBuffer[256];

 hFile = CreateFile(_T("test.txt"), GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);

 if (hFile == INVALID_HANDLE_VALUE) {
 printf("Error creating file.\n");
 exit(EXIT_FAILURE);
 }

 if (WriteFile(hFile, tszText, _tcsclen(tszText) * sizeof(TCHAR), &dwBytesWritten, 0)) {
 printf("%d bytes written to the file.\n", dwBytesWritten);
 }
 else {
 printf("Error writting to the file.\n");
 exit(EXIT_FAILURE);
 }

 //for the union LARGE_INTEGER
 //we only need to set the field 'QuadPart'
 //let's jump ahead 44 characters
 liMoveTo.QuadPart = 44 * sizeof(TCHAR);

 SetFilePointerEx(hFile, liMoveTo, &liNewLocation, FILE_BEGIN);

 if (ReadFile(hFile, tszBuffer, 256, &dwBytesRead, 0)) {
 printf("File read from successfully.\n");
 printf("%d bytes read.\n", dwBytesRead);
 }

 tszBuffer[(dwBytesRead / sizeof(TCHAR))] = '';

 _tprintf(_T("%s\n"), tszBuffer);

 return 0;
}

We can get the file size via the GetFileSizeEx() function. Like SetFilePointerEx(), the GetFileSizeEx() function uses the LARGE_INTEGER union.

#include "stdafx.h"

#include <Windows.h>

int main()
{
 HANDLE hFile;
 LARGE_INTEGER liLength;
 

 hFile = CreateFile(_T("test.txt"), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);

 if (hFile == INVALID_HANDLE_VALUE) {
 printf("Error acquiring file handle.");
 exit(EXIT_FAILURE);
 }

 GetFileSizeEx(hFile, &liLength);

 printf("The file size is %lld bytes\n", liLength.QuadPart);


 return 0;
}

File have attribute bits that store special information about the file. We can examine attribute bits with the GetFileAttributes() function. The GetFileAttributes() function returns a DWORD value that stores the attribute bits.

#include <Windows.h>

int main()
{
 DWORD dwValue = GetFileAttributes(_T("test.txt"));

 if (dwValue == INVALID_FILE_ATTRIBUTES) {
 printf("Error retrieving the file attribtues for the file.\n");
 exit(EXIT_FAILURE);
 }

 if (dwValue & FILE_ATTRIBUTE_ARCHIVE) {
 printf("Archive.\n");
 }
 if (dwValue & FILE_ATTRIBUTE_DIRECTORY) {
 printf("Directory.\n");
 }
 if (dwValue & FILE_ATTRIBUTE_HIDDEN) {
 printf("File is hidden.\n");
 }
 if (dwValue & FILE_ATTRIBUTE_NORMAL) {
 printf("File is normal.\n");
 }
 if (dwValue & FILE_ATTRIBUTE_ENCRYPTED) {
 printf("File is encrypted.\n");
 }
 if (dwValue & FILE_ATTRIBUTE_TEMPORARY) {
 printf("File is temporary.\n")
 }

 return 0;
}

It is also possible to set the file attributes using the SetFileAttributes() function. The SetFileAttributes() function accepts a file name and a DWORD value that represents a set of attribute bits to be set.

#include "stdafx.h"

#include <Windows.h>

int main()
{
 DWORD dwFileAttributes = FILE_ATTRIBUTE_ENCRYPTED | FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_HIDDEN;

 if (SetFileAttributes(_T("test.txt"), dwFileAttributes)) {
 printf("File attributes altered.\n");
 }

 return 0;
}

Note that we combine multiple attributes with the binary or operator, |.

Threads and Processes with win32

We can create a separate process from within a running application using the CreateProcess() function. In our program, we will retrieve the current process’s startup information using the GetStartupInfo() function.

#include "stdafx.h"
#include <Windows.h>


int main()
{
 TCHAR szBuffer[MAX_PATH];
 STARTUPINFO structStartupInfo;
 PROCESS_INFORMATION structProcInfo;
 BOOL bSuccess;
 DWORD dwCharsRead;


 //get the name of the executable to run in the 
 //new process
 printf("Enter the command to execute, please: ");
 ReadConsole(GetStdHandle(STD_INPUT_HANDLE), szBuffer, _tcslen(szBuffer) - 2, &dwCharsRead, NULL);
 szBuffer[dwCharsRead - 2] = '';

 GetStartupInfo(&structStartupInfo);


 bSuccess = CreateProcess(0, szBuffer, 0, 0, FALSE, CREATE_NEW_CONSOLE, 0, 0, &structStartupInfo, &structProcInfo);

 if (bSuccess) {
 printf("New process created successfully.\n");
 //wait for child process to complete
 if ((WaitForSingleObject(structProcInfo.hProcess, INFINITE)) == WAIT_OBJECT_0) {
 printf("Process %d ended.\n", structProcInfo.dwProcessId);
 }
 CloseHandle(structProcInfo.hProcess);
 CloseHandle(structProcInfo.hThread);
 }

 printf("Finished!\n");

 return 0;
}

For the CreateProcess() function, we can indicate the executable to run in either the first or second parameter. By and large, most people use the second parameter to define the executable and any command line arguments.

When a process spawns a child process, the child process is completely independent of the parent. It is possible, however, for the child process to inherit handles from the parent if the parent marks any of its handles as inheritable.

The ParentProcess program

#include "stdafx.h"
#include <Windows.h>

//parent process
HANDLE hFile;
const int Max_Buffer = 2056;

int main()
{
 STARTUPINFO structSUpInfo;
 PROCESS_INFORMATION structProcInfo;
 BOOL bSuccess;
 TCHAR tszFileName[MAX_PATH];
 TCHAR tszBuffer[Max_Buffer];
 SECURITY_ATTRIBUTES structSAttrib;
 //we'll use dwFileAttributes to check 
 //for the existence of the child executable
 DWORD dwCharsRead, dwFileAttributes;
 //store file handle number as text
 TCHAR tszFileHandleString[256];
 LARGE_INTEGER uLI;
 
 //set up security attributes
 //so as to allow child process to inherit 
 //file handle
 structSAttrib.nLength = sizeof(SECURITY_ATTRIBUTES);
 structSAttrib.lpSecurityDescriptor = 0;
 structSAttrib.bInheritHandle = TRUE;

 //get file name
 printf("Please enter the name of the file: ");
 bSuccess = ReadConsole(GetStdHandle(STD_INPUT_HANDLE), tszFileName, MAX_PATH, &dwCharsRead, NULL);
 if (bSuccess) {
 printf("File name acquired.\n");
 }
 else {
 exit(EXIT_FAILURE);
 }

 //remove windows newline chars
 tszFileName[dwCharsRead - 2] = '';

 printf("Creating file.\n");

 hFile = CreateFile(tszFileName, GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE, &structSAttrib, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
 
 if (hFile != INVALID_HANDLE_VALUE) {
 printf("File created.\n");
 }
 else {
 printf("Error creating file. Error %d. \n", GetLastError());
 exit(EXIT_FAILURE);
 }
 
 //check to make sure executable for child process 
 //is available
 dwFileAttributes = GetFileAttributes(_T("ChildProcess.exe"));
 if (dwFileAttributes == INVALID_FILE_ATTRIBUTES) {
 printf("Could not find the child process executable.\n");
 exit(EXIT_FAILURE);
 } 

 //get startup info
 GetStartupInfo(&structSUpInfo);

 //convert file handle to string for passing to child process
 printf("Handle value is %d\n", hFile);
 swprintf(tszFileHandleString, 16, L"%d", hFile);


 bSuccess = CreateProcess(_T("ChildProcess.exe"), tszFileHandleString, 0, 0, TRUE, CREATE_NEW_CONSOLE, 0, 0, &structSUpInfo, &structProcInfo);

 if (!bSuccess) {
 printf("Error creating child process.\n");
 exit(EXIT_FAILURE);
 }
 else {
 printf("Child process created.\n");
 }

 WaitForSingleObject(structProcInfo.hProcess, INFINITE);

 CloseHandle(structProcInfo.hProcess);
 CloseHandle(structProcInfo.hThread);

 printf("Child process %d finished.\n", structProcInfo.dwProcessId);
 
 //now let's read from the file
 hFile = CreateFile(tszFileName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
 uLI.QuadPart = 0;
 SetFilePointerEx(hFile, uLI, 0, FILE_BEGIN);
 //don't forget - ReadFile counts number of bytes read, not chars!

 bSuccess = ReadFile(hFile, tszBuffer, Max_Buffer - 1, &dwCharsRead, NULL);
 if (bSuccess) {
 printf("File read successfully.\n");
 }

 tszBuffer[dwCharsRead / sizeof(TCHAR)] = '';

 _tprintf(_T("Read from file: %s\n"), tszBuffer);


 return 0;
}

The ChildProcess program

// ChildProcess.cpp : Defines the entry point for the console application.


#include "stdafx.h"
#include <Windows.h>

HANDLE hFile;
const int Max_Buffer = 2056;

//child process
int main(int argc, char *argv[])
{
 TCHAR tszBuffer[Max_Buffer];
 int iHNum;
 DWORD dwCharsRead, dwCharsWritten;
 BOOL bSuccess;

 printf("In the child process...\n");

 printf("Received argument %s.\n", argv[0]);
 //convert string argument to handle
 iHNum = atoi(argv[0]);

 hFile = (HANDLE)iHNum;
 printf("Handle value is %d\n", hFile);

 printf("Please enter text: ");
 bSuccess = ReadConsole(GetStdHandle(STD_INPUT_HANDLE), tszBuffer, Max_Buffer, &dwCharsRead, NULL);

 if (bSuccess == FALSE) {
 printf("Error reading text.\n");
 exit(EXIT_FAILURE);
 }

 tszBuffer[dwCharsRead] = '';
 _tprintf(_T("Text to be written to file: %s"), tszBuffer);

 bSuccess = WriteFile(hFile, tszBuffer, _tcslen(tszBuffer)*sizeof(TCHAR), &dwCharsWritten, 0);

 if (bSuccess) {
 printf("File written to successfully.\n");
 }
 else {
 printf("File not written to successfully.\n");
 printf("Error %d\n", GetLastError());
 Sleep(2000);
 exit(EXIT_FAILURE);
 }
 
 CloseHandle(hFile);
 printf("Finishing...");
 Sleep(2000);

 return 0;
}

When any process starts in Windows, it contains by default one thread of execution. The thread is what actually executes the code; the process functions as a container for the heap owned by the application, the global variables, and the environment strings. All threads in one process share the global variable space of their parent process.

The CreateThread() function has six parameters. The first parameter is a pointer to a SECURITY_ATTRIBUTES structure. If NULL, the handle to the thread cannot be inherited. The second argument is a DWORD value indicating the stack size for the thread. If 0, the default stack size for the executable is assigned. The third parameter is the name of the function that the thread starts. The fourth parameter is a void pointer used for passing in arguments to the thread. The fifth parameter indicates any flags we wish to pass in; we will put 0 here. The final parameter is a pointer to a DWORD value to store the thread’s ID.

#include "stdafx.h"
#include <Windows.h>

volatile int iCount;
volatile int iCount2;

DWORD WINAPI CountThreadA(void *lpParameter) {
 while (true) {
 iCount++;
 Sleep(200);
 }
}

DWORD WINAPI CountThreadB(void *lpParameter) {
 while (true) {
 iCount2++;
 Sleep(500);
 }
}

int main()
{
 iCount = 1;
 iCount2 = 1;

 char szInput[56];
 HANDLE hThreadOne, hThreadTwo;
 DWORD dwThreadID1, dwThreadID2;

 //start up the threads
 hThreadOne = CreateThread(0, 0, CountThreadA, 0, 0, &dwThreadID1);
 hThreadTwo = CreateThread(0, 0, CountThreadB, 0, 0, &dwThreadID2);

 while (true) {
 printf("Enter A to see the count for the first thread, ");
 printf("B to see the count for the second thread, or ");
 printf("X to end the program: ");

 fgets(szInput, sizeof(szInput), stdin);
 
 if (szInput[0] == 'A'|| szInput[0] == 'a') {
 printf("Thread ID %d Count %d\n", dwThreadID1, iCount);
 }
 if (szInput[0] == 'B'|| szInput[0] == 'b') {
 printf("Thread ID %d Count %d\n", dwThreadID2, iCount2);
 }
 if (szInput[0] == 'X' || szInput[0] == 'x') {
 printf("Exiting");
 break;
 }
 }

 return 0;
}

Note the use of the volatile modifier for the iCount and iCount2 variables. Essentially, we use the volatile modifier for any global variable that will be modified in a multi-threaded application. The volatile keyword is used to basically tell the compiler no to apply any optimizations to the variable.

Introduction to Volume Management with the win32 API

The GetLogicalDrives() function returns a DWORD bitmask that represents the currently available disk drives. If the function fails, the return value is zero.

#include "stdafx.h"
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
	int iCounter = 0;
	int iASCIILetter = (int)'a';

	DWORD dwDrivesMask = GetLogicalDrives();

	if (dwDrivesMask == 0){
		printf("Failed to acquire mask of drives.\n");
		exit(EXIT_FAILURE);
	} 

	printf("Drives available:\n");
        //extract the drives from the mask using 
        //logical and-ing with & and bitshift operator <<
	while (iCounter < 24){
		if (dwDrivesMask & (1 << iCounter)){
			printf("%c:\\ \n", iASCIILetter + iCounter);
		}
		iCounter++;
	}

	return 0;
}

The GetLogicalDriveStrings() function returns the valid drives in a system by filling a buffer with a series of null-terminated strings, one for each drive on the system, plus an additional null character at the end of the list. The GetLogicalDriveStrings() function takes two arguments, the first is the length of the buffer in TCHARS, and the second is a pointer to the buffer. Note that the size does not include the final, terminating null character.

#include "stdafx.h"
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    const int buffer_size = 512;
    TCHAR buffer[buffer_size];
    //we will use the pointer 
    //to iterate through the buffer
    TCHAR *tchPtr = buffer;

    if (GetLogicalDriveStrings(buffer_size - 1, buffer) == 0){
        printf("Unable to get drive strings.\n");
        exit(EXIT_FAILURE);
    }

    //go through buffer until we reach
    //the final double null char that indicates
    //end of the info
    while (*tchPtr != ''){
        printf("%s:\n", tchPtr);
        //_tcslen is the equiv. of strlen
        //does not include the null char, so we 
        //must add one
        tchPtr += _tcslen(tchPtr) + 1;
    }

    return 0;
}

The GetDriveType() function determines whether a disk drive is removable, fixed, CD-ROM, etc. The GetDriveType() function takes a single parameter, a string indicating the root directory for the drive; note that a trailing backslash is required. If the parameter is NULL, then the function uses the root of the current directory.

#include "stdafx.h"
#include <Windows.h>

void DisplayDriveType(int iParam);

int _tmain(int argc, _TCHAR* argv[])
{
    //null param (this drive)
    DisplayDriveType(GetDriveType(NULL));

    //nonexistant drive
    DisplayDriveType(GetDriveType(_T("x:\\")));

    //for all drives
    TCHAR szBuffer[256];
    TCHAR *tchPtr = szBuffer;
    GetLogicalDriveStrings(255, szBuffer);

    //print types for all drives on system
    for (tchPtr; *tchPtr != ''; tchPtr += _tcslen(tchPtr) +1)
    {
        printf("%s:\\ \t", tchPtr);
        DisplayDriveType(GetDriveType(tchPtr));
        printf("\n");
    }

    return 0;
}

void DisplayDriveType(int iParam){
    switch (iParam){
    case DRIVE_UNKNOWN:
        printf("Drive type unknown.\n");
        break;

    case DRIVE_NO_ROOT_DIR:
        printf("No drive for that root path.\n");
        break;

    case DRIVE_REMOVABLE:
        printf("Removable drive.\n");
        break;

    case DRIVE_FIXED:
        printf("Fixed drive.\n");
        break;

    case DRIVE_REMOTE:
        printf("Network drive.\n");
        break;

    case DRIVE_CDROM:
        printf("CD ROM drive.\n");
        break;

    }
}

The GetVolumeInformation() function retrieves file system metadata on a given volume. The GetVolumeInformation() function takes eight parameters. The first parameter is a string that contains the root directory of the volume for which we want information. The second argument is a pointer to a buffer that stores the name of the specified volume. The third argument is the length of the volume name buffer in TCHARS; the maximum buffer size is MAX_PATH + 1. The fourth argument is a pointer to a DWORD variable that receives the volume serial number. The fifth argument is a pointer to a DWORD variable that receives the maximum length, in TCHARs, of a file name component, where a file name component is a section of a filename between backslashes.

#include "stdafx.h"
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    TCHAR tchVolumeNameBuff[MAX_PATH + 1];
    DWORD dwSerialNumber, dwMaxCompLength;

    GetVolumeInformation(_T("C:\\"), tchVolumeNameBuff, MAX_PATH + 1, &dwSerialNumber, &dwMaxCompLength, NULL, NULL, NULL);

    printf("Name: %s Serial Number: %d\n", tchVolumeNameBuff, dwSerialNumber);

    return 0;
}

Diving into File Handling with win32

I’d like to review creating and managing files using win32, and maybe go a bit more in depth as well.

The second to last parameter for CreateFile() specifies a range of attributes for the file in the form of flags. These flags are logically organized into three groups; we will cover the first two groups here. The first group represents a set of file attributes. FILE_ATTRIBUTE_HIDDEN keeps the file from being included in an ordinary directory listing. FILE_ATTRIBUTE_ENCRYPTED will cause the file or directory to be encrypted; in the case of a directory, this means that all newly created files and subdirectories will be, by default, encrypted. FILE_ATTRIBUTE_NORMAL is the default; note that this flag is only valid when used by itself. FILE_ATTRIBUTE_READONLY makes the file available for reading, but not writing or deletion. FILE_ATTRIBUTE_TEMPORARY means that the file is being used for temporary storage.

#include "stdafx.h"
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    
    printf("FILE_ATTRIBUTE_READONLY \t %d 0x%x\n", FILE_ATTRIBUTE_READONLY, FILE_ATTRIBUTE_READONLY);
    printf("FILE_ATTRIBUTE_HIDDEN \t %d 0x%x\n", FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_HIDDEN);
    printf("FILE_ATTRIBUTE_NORMAL \t %d 0x%x\n", FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_NORMAL);
    printf("FILE_ATTRIBUTE_TEMPORARY \t %d 0x%x\n", FILE_ATTRIBUTE_TEMPORARY, FILE_ATTRIBUTE_TEMPORARY);
    printf("FILE_ATTRIBUTE_ENCRYPTED \t %d 0x%x\n", FILE_ATTRIBUTE_ENCRYPTED, FILE_ATTRIBUTE_ENCRYPTED);

    return 0;

}

The FILE_FLAG_BACKUP_SEMANTICS flag indicates that the file is being opened or created for a backup or restore operation. Note that we must specify this flag if we want to work with a directory. The FILE_FLAG_DELETE_ON_CLOSE flag indicates that the file should be deleted as soon as all of its handles are closed. The FILE_FLAG_OVERLAPPED flag indicates that the file or device is to be opened or created for asynchronous I/O. If this flag is specified, the file can be used for simultaneous read and write operations. The FILE_FLAG_RANDOM_ACCESS is pretty self-explanatory, the file will be accessed randomly. The FILE_FLAG_NO_BUFFERING flag indicates that the file or device is to be opened with no system caching for data reads and writes.

#include "stdafx.h"
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    
    printf("FILE_FLAG_DELETE_ON_CLOSE %d.\n", FILE_FLAG_DELETE_ON_CLOSE);
    printf("FILE_FLAG_OVERLAPPED %d.\n", FILE_FLAG_OVERLAPPED);
    printf("FILE_FLAG_BACKUP_SEMANTICS %d.\n", FILE_FLAG_BACKUP_SEMANTICS);
    printf("FILE_FLAG_RANDOM_ACCESS %d.\n", FILE_FLAG_RANDOM_ACCESS);
    printf("FILE_FLAG_NO_BUFFERING %d.\n", FILE_FLAG_NO_BUFFERING);

    return 0;

}

The OPEN_ALWAYS creation disposition will always give us a file handle to the file, unless there has been a grievous error of some sort. If the file doesn’t exist, it will be created, and if it does exist, the function will still succeed but the last-error code will be set to ERROR_ALREADY_EXISTS.

#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    char *szFileName = "kilroy.txt";
    HANDLE hFile;
    //create the file!
    //if file already exists, then we will open it
    //otherwise it will be created and then opened
    hFile = CreateFile(
        szFileName,
        GENERIC_WRITE,
        FILE_SHARE_READ,
        NULL,
        OPEN_ALWAYS,
        FILE_ATTRIBUTE_NORMAL,
        NULL
        );

    if (hFile == INVALID_HANDLE_VALUE){
        printf("Failed to create/open file %s.\n", szFileName);
    }
    else {
        if (GetLastError() == ERROR_ALREADY_EXISTS){
            printf("File already exists, opening...\n");
        }
        else {
            printf("File created, opening...\n");
        }
    }

    //close the handle
    CloseHandle(hFile);

    return 0;

}

The WriteFile() function takes five arguments.The first argument or parameter is a handle to the file or I/O device. The second parameter is a pointer to the buffer that holds the data to be written. The third argument controls the number of bytes to be written. The fourth argument is a pointer to a DWORD value that stores the number of bytes written during a synchronous write operation. The final parameter is a pointer to an OVERLAPPED structure; this parameter is necessary if we are to perform asynchronous I/O.

#include "stdafx.h"
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    const int Buffer_Size = 256;

    HANDLE hFile;
    char *szFileName = "kilroy.txt";
    char szBuffer[Buffer_Size];
    DWORD dwBytesWritten;
    BOOLEAN boolRVal;

    hFile = CreateFile(szFileName, GENERIC_WRITE, NULL, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

    if (hFile == INVALID_HANDLE_VALUE){
        exit(EXIT_FAILURE);
    }

    strncpy_s(szBuffer, "What strength! But don't forget there are many guys like you all over the World.", Buffer_Size);

    //let's write!
    boolRVal = WriteFile(hFile, szBuffer, strlen(szBuffer), &dwBytesWritten, NULL);

    if (boolRVal){
        printf("%s written to successfully!\n", szFileName);
        printf("%d bytes written.\n", dwBytesWritten);
    }

    //close the handle afterwards
    CloseHandle(hFile);

    return 0;

}

The WriteFile() function is designed for both synchronous and asynchronous operations. For asynchronous write operations, the handle must have been opened with CreateFile() using the FILE_FLAG_OVERLAP flag.

The ReadFile() function is a block-reading function. As with WriteFile(), we pass it in a buffer, only this time we indicate the number of bytes to be read, and the function retrieves the specified number of bytes from the file starting at the current offset.

#include "stdafx.h"
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    const int Buffer_Size = 256;
    char *szFileName = "kilroy.txt";
    char szInBuffer[Buffer_Size];
    HANDLE hFile;
    DWORD dwBytesRead;

    hFile = CreateFile(szFileName, GENERIC_READ, NULL, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

    if (hFile == INVALID_HANDLE_VALUE){
        exit(EXIT_FAILURE);
    }

    if (ReadFile(hFile, szInBuffer, Buffer_Size, &dwBytesRead, NULL)){
        printf("%d bytes read.\n", dwBytesRead);
        //use dwBytesRead to place a string termination character
        //at the correct location in the buffer.
        szInBuffer[dwBytesRead] = '';
        printf("%s\n", szInBuffer);
    }

    CloseHandle(hFile);

    return 0;

}

The win32 API has several functions that are useful for retrieving file information. Many of these functions require an open file handle rather than the file’s name.

The GetFileTime() function fetches three different pieces of time information concerning a file: the creation time, the last access time, and the last write time. Each of these times is stored in a FILETIME structure. Data from the FILETIME structure should be extracted via win32 function calls, namely FileTimeToSystemTime() , which converts a FILETIME structure to a SYSTEMTIME structure, which is easy to work with.

#include "stdafx.h"
#include <Windows.h>

void DisplayTime(FILETIME structFT);

int _tmain(int argc, _TCHAR* argv[])
{
    TCHAR *szFilename = _T("kilroy.txt");
    FILETIME structFTCreate, structFTLastWrite, structFTLastAccess;
    BOOLEAN boolSuccess;

    HANDLE hFile = CreateFile(szFilename, GENERIC_READ, FILE_SHARE_WRITE, 0, OPEN_ALWAYS, 0, 0);

    if (hFile == INVALID_HANDLE_VALUE){
        printf("Error no. %d\n", GetLastError());
        exit(EXIT_FAILURE);
    }
    else {
        printf("Acquiring file times...\n");
    }

    boolSuccess = GetFileTime(hFile, &structFTCreate, &structFTLastAccess, &structFTLastWrite);

    printf("Creation time: \n");
    DisplayTime(structFTCreate);
    printf("\n");
    printf("Last write time: \n");
    DisplayTime(structFTLastWrite);

    return 0;
}

void DisplayTime(FILETIME structFT){
    SYSTEMTIME structST;
    FileTimeToSystemTime(&structFT, &structST);
    printf("Month: %d Day: %d Year: %d ", structST.wMonth, structST.wDay, structST.wYear);
    printf("%d:%d\n", structST.wHour, structST.wMinute);
}

The FILETIME structure contains two 32-bit values. The FileTimeToSystemTime() function convert this amalgamated 64 bit value into a local time suitable for output.

The GetFileSize() function returns the size of the file in bytes; however, a more suitable variant for contemporary programming is GetFileSizeEx(). The GetFileSizeEx() function accepts two arguments, the first being a handle to the file, and the second is a pointer to a LARGE_INTEGER union.

#include "stdafx.h"
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    HANDLE hFile;
    TCHAR *szFilename = _TEXT("kilroy.txt");
    LARGE_INTEGER structLargeInt;

    hFile = CreateFile(szFilename, GENERIC_READ, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL);

    if (hFile == INVALID_HANDLE_VALUE){
        printf("Error no. %d\n", GetLastError());
        exit(EXIT_FAILURE);
    }
    else {
        GetFileSizeEx(hFile, &structLargeInt);
        //only the QuadPart member of the LARGE_INTEGER structure concerns us
        printf("Size: %d bytes.\n", structLargeInt.QuadPart);
    }

    return 0;
}

Diving into win32 Processes

A process from the Windows perspective is a kernel object that characterizes a certain address space within which threads execute. As with any other kernel object, a process has a reference count indicating how many open handles there are to itself. A process is created via a call to the CreateProcess() function, and remains in existence until all open handles to the process have been closed, at which point the reference count for the process object is zero, and there are no more active threads in the process.

The GetCurrentProcess() function returns a pseudo handle to the current process. A handle is typically an integer value, although technically speaking we ought to consider a handle as opaque, meaning that we only really need to focus on how to us it, not what it is, as such. A pseudo handle is a special integral value, which again we should consider opaque, which is why we acquire it via the GetCurrentProcess() function, rather than hard-coding it as a constant value.

Once we have a handle to the process, there are a number of functions we can call to provide us with information about the process. The GetProcessId() function takes the handle to the process and returns the ID of the process.

#include "stdafx.h"
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    HANDLE hCurrProc;

    hCurrProc = GetCurrentProcess();

    //pseudo handle will be (HANDLE)-1
    _tprintf(_T("Handle literal value %d\n"), (int)hCurrProc);

    //process ID
    _tprintf(_T("Process ID of current process %d\n"), GetCurrentProcessId());
    //returns the same data, but this call can be used with any process 
    _tprintf(_T("Ditto %d\n"), GetProcessId(hCurrProc));


    return 0;
}

An IO_COUNTERS structure is used to store I/O information for a process or a job object. The win32 GetProcessIoCounters() function enables us to get a pointer to an IO_COUNTERS structure that contains the I/O accounting information for the process.

#include "stdafx.h"
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    IO_COUNTERS structIOCount;
    HANDLE hCurrProcess = GetCurrentProcess();
    //second argument needs to be the address of an IO_COUNTERS
    //structure
    if (GetProcessIoCounters(hCurrProcess, &structIOCount)){
        _tprintf(_T("Got I/O Information for Process %d\n"), GetProcessId(hCurrProcess));
        _tprintf(_T("The number of read operations performed: %d\n"), structIOCount.ReadOperationCount);
        _tprintf(_T("The number of write operations performed: %d\n"), structIOCount.WriteOperationCount);
        _tprintf(_T("The number of bytes read: %d\n"), structIOCount.ReadTransferCount);
        _tprintf(_T("THe number of bytes written: %d\n"), structIOCount.WriteTransferCount);
    }


    return 0;
}

The CreateProcess() function returns a handle to a new process object that will manage the lifetime of the indicated running program. The CreateProcess() function takes ten arguments

The first parameter for CreateProcess() is a pointer to the null-terminated name of the program to run; if this parameter is NULL, the program indicated in the second argument will be executed. This second argument is likewise a null-terminated string; it indicates the program to execute, along with any command-line arguments. The third argument is a pointer to a SECURITY_ATTRIBUTES structure; if this argument is NULL, the default security attributes are used, and the process handle returned by the function is not inheritable. The fourth argument is likewise a pointer to a SECURITY_ATTRIBUTES structure; if NULL, the default attributes are used, and the thread handle is not inheritable. The fifth argument is a Boolean value indicating whether or not the new process should inherit the inheritable handles of the calling process. The sixth argument is a DWORD value that indicates the creation flags, which can be OR-ed together to form combinations. This argument can also be used to control the priority of the new process. The seventh argument is a pointer to an environment block for the new process. If we put NULL here than the process will be passed a copy of the current environment. The eight argument is a string that specifies the initial default drive and directory of the process. The ninth argument is a pointer to a STARTUPINFO structure, and the tenth argument is a pointer to a PROCESS_INFORMATION structure that will be filled in with details about the newly created process.

#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    STARTUPINFO structSI;
    PROCESS_INFORMATION structPI;
    BOOL bRValue;
    ZeroMemory(&structSI, sizeof(structSI));
    //important!
    structSI.cb = sizeof(STARTUPINFO);

    bRValue = CreateProcess(
        NULL, //lpApplicationName
        "notepad.exe", //lpCommandLine
        NULL, //lpProcessAttributes
        NULL, //lpThreadAttributes
        TRUE, //bInheritHandles
        0, //dwCreationFlags
        NULL, //lpEnvironment
        NULL, //lpCurrentDirectory
        &structSI, //lpStartupInfo
        &structPI //lpProcessInformation
        );

    if (bRValue){
        printf("Process created.\n");
    }

    return 0;
}

The PROCESS_INFORMATION structure has four members. The hProcess member is the handle to the newly created process. The calling program should use the CloseHandle() function to release its reference to the newly created process kernel object. The hThread member is the handle to the primary thread of the new process. The dwProcessId member stores the system-wide unique identifier to the new process. The dwThreadId member stores the system-wide unique identifier to the primary thread of the new process.

#include "stdafx.h"
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    STARTUPINFO structSI;
    PROCESS_INFORMATION structPI;

    ZeroMemory(&structSI, sizeof(structSI));
    structSI.cb = sizeof(structSI);

    //createprocess returns Boolean value
    if (CreateProcess(NULL, "notepad.exe", NULL, NULL, FALSE,
        0, NULL, NULL, &structSI, &structPI)){
        //display the information stored in structPI
        printf("Notepad.exe created as process %d and thread %d\n",
            structPI.dwProcessId, structPI.dwThreadId);
    }

    //let's close the newly created process and thread handles
    CloseHandle(structPI.hProcess);
    CloseHandle(structPI.hThread);

}

Each process has a handle and a system-wide unique identifier. From the calling program, we can access these values via the PROCESS_INFORMATION data structure. From within the process itself, an application can determine its own pseudo-handle via the GetCurrentProcess() call. Note that a pseudo-handle is only valid in the context of the calling process, as it is not a real handle.

The TerminateProcess() function can be used to terminate another process. Generally speaking, this function should not be called unless in an emergency.

Any process can get the exit status of a process it has a handle to by using the GetExitCodeProcess() function. The GetExitCodeProcess() function returns a Boolean value and accepts two parameters, the first being the handle to the process and the second being a pointer to a DWORD value to store the exit code.

#include "stdafx.h"
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    STARTUPINFO structSInfo;
    PROCESS_INFORMATION structPInfo;
    DWORD dwExitCode;
    BOOLEAN boolRValue;

    ZeroMemory(&structSInfo, sizeof(structSInfo));

    //start notepad
    boolRValue = CreateProcess(
        NULL, //application name
        "notepad.exe", //command line
        NULL, //Inherit security attributes
        NULL, //process attributes
        TRUE, //inherit handles
        0, //no creation flags
        NULL, //environment
        NULL, //current directory
        &structSInfo,
        &structPInfo
        );

    if (boolRValue){
        printf("Notepad.exe created as process %d, thread %d.\n", structPInfo.dwProcessId, structPInfo.dwThreadId);
    }
    else {
        printf("Error creating new process.");
        exit(EXIT_FAILURE);
    }

    //wait for a few seconds
    Sleep(3000);

    //terminate notepad.exe
    TerminateProcess(structPInfo.hProcess, 0);

    //get exit code
    boolRValue = GetExitCodeProcess(structPInfo.hProcess, &dwExitCode);

    if (boolRValue){
        printf("notepad.exe's exit code is %d\n", dwExitCode);
    }

    CloseHandle(structPInfo.hProcess);
    CloseHandle(structPInfo.hThread);

    return 0;

}

One last item to cover is waiting for a process to exit – this is the simplest, and perhaps most limiting, way to synchronize processes. To wait for a process to exit, we simply call  WaitForSingleObject(). The WaitForSingleObject() function takes two arguments, the first is the handle to the process, and the second specifies how long we want the process to wait for the other process to end.

#include "stdafx.h"
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    STARTUPINFO structSI;
    PROCESS_INFORMATION structPI;
    DWORD dwWaitRValue, dwExitCode;

    ZeroMemory(&structSI, sizeof(structSI));
    structSI.cb = sizeof(structSI);

    CreateProcess(
        NULL,
        "powershell.exe",
        NULL,
        NULL,
        FALSE,
        CREATE_NEW_CONSOLE, //creation flag
        NULL,
        NULL,
        &structSI,
        &structPI
        );

    //print some information
    printf("Powershell.exe was created as process ID %d.\n", structPI.dwProcessId);

    //wait for process to exit
    while (true){
        dwWaitRValue = WaitForSingleObject(structPI.hProcess, 1000);
        if (dwWaitRValue == WAIT_OBJECT_0){
            printf("\nProcess ID %d exited.\n", structPI.dwProcessId);
            break;
        }
        if (dwWaitRValue == WAIT_TIMEOUT){
            printf("Powershell.exe (Process %d) has not exited yet.\n", structPI.dwProcessId);
        }
    }

    //get exit code
    if (GetExitCodeProcess(structPI.hProcess, &dwExitCode)){
        printf("Powershell.exe exited with code %d.\n", dwExitCode);
    }

    CloseHandle(structPI.hProcess);
    CloseHandle(structPI.hThread);

    return 0;

}

This covers most of what we need to know about the basics of creating and managing processes in Windows.