Creating Threads Using the win32 API

The CreateThread() API call can be used to create and run a thread. A thread needs to have code to execute, and we will provide this via a ThreadProc callback function. A ThreadProc function takes a single argument of type LPVOID and returns a DWORD value.

The CreateThread() function returns a thread handle that is valid only within our process. The handle refers to the kernel thread object managing the new thread. Most of the thread API class use this thread handle.

#include <Windows.h>
#include <stdlib.h>
#include <stdio.h>

DWORD WINAPI threadFunc(LPVOID lPtr);

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

	HANDLE hThread;


	printf("In main.\n");

	hThread = CreateThread(
		NULL, //default security
		0, //default stack size
		threadFunc, //LPTHREAD_START_ROUTINE
		0, //argument to threadFunc
		0, //creation flags
		0
		);

	if(hThread!=NULL){
		printf("Thread created successfully.\n");
	} else {
		printf("Error creating thread.\n");
	}

	//give called thread time to finish
	Sleep(1500);

	return 0;
}

DWORD WINAPI threadFunc(LPVOID lPtr){
	printf("In the thread.\n");

	return 0;
}

Note that the CreateThread() function returns immediately, so that the calling routine and the new thread execute simultaneously. If the primary thread dies before the thread functions have finished executing, then all threads will be forcibly terminated, and will not have a chance to finish.

The CreateThread() function takes a number of parameters, most of which we should provide with a NULL or 0. The first parameter is a pointer to a SECURITY_ATTRIBUTES structure, which is used to govern who can access the new thread. We can set this parameter to NULL. The second parameter indicates the amount of stack space to be allocated for the new thread. Here we should specify 0, as this causes the stack size to default to the default size of the executable.

The third and fourth parameters are most important, as these specify the address of the ThreadProc function, and a pointer to any arguments we want to supply to it. These arguments are passed via a single void* pointer, so we must ensure that the data is cast back into the right types in the new thread.

The fifth parameter lets us supply flag values which determine how the thread behaves when it is created. Here we will pass 0.

#include <Windows.h>
#include <stdlib.h> 
#include <stdio.h>

DWORD WINAPI TPFunction(LPVOID lPtr);

int _tmain(int argc, _TCHAR* argv[])
{
	int num = 73;
	void *vPtr = &num;

	HANDLE hThreadHandle = CreateThread(
		NULL, //default security
		0, //default stack size
		TPFunction, //function
		vPtr, //argument
		0,
		0
		);

	for(int i = 0; i < 10; i++){
		printf("In main %d \t", i+1);
		Sleep(100);
	}

	return 0;
}

DWORD WINAPI TPFunction(LPVOID lPtr){
        //cast the void pointer to an int pointer
       //then dereference it
	printf("...Received %d in the thread.\n", *((int*)lPtr));

	return 0;
}

The final parameter is a pointer to a DWORD value that can be used to store the thread ID. The thread ID is a unique global value that any or thread or process can use to refer to our thread.

#include <Windows.h>
#include <stdlib.h>
#include <stdio.h>

DWORD WINAPI theFunction(LPVOID lPtr);

int _tmain(int argc, _TCHAR* argv[])
{
	DWORD dwIDOne, dwIDTwo;
	HANDLE hThreadOne, hThreadTwo;
	int iValOne = 100;
	int iValTwo = iValOne * 2;

	hThreadOne = CreateThread(
		NULL,
		0,
		theFunction,
		(void*)&iValOne,
		0,
		&dwIDOne
		);
	
	if(hThreadOne!=NULL){
		printf("Thread %d created.\n", dwIDOne);
	}

	hThreadTwo = CreateThread(
		NULL,
		0,
		theFunction,
		(void*)&iValTwo,
		0,
		&dwIDTwo
		);

	if(hThreadTwo!=NULL){
		printf("Thread %d created.\n", dwIDTwo);
	}

	for(int i = 0; i < 10; i++){
		printf("In main...\n");
		Sleep(10 * i);
	}

	

	return 0;
}

DWORD WINAPI theFunction(LPVOID lPtr){
	printf("In the new thread, ID %d.\n", GetCurrentThreadId());
	Sleep(100 + *((int*)lPtr));
	printf("Finishing thread %d.\n", GetCurrentThreadId());

	return 0;
}

Note the use of the GetCurrentThreadId() function to get the current ID for the thread.

The GetExitCodeThread() function gets the exit code from the ThreadProc function associated with the handle. The GetExitCodeThread() function has two paramters, the HANDLE of the thread we want the exit code of, and a pointer to a DWORD value to store the exit code.

#include <Windows.h>
#include <stdlib.h>
#include <stdio.h>

DWORD WINAPI theFunc(LPVOID lPtr);

int _tmain(int argc, _TCHAR* argv[])
{
	DWORD dwExitCode;
	DWORD dwThreadID;

	HANDLE hT = CreateThread(
		NULL,
		0,
		theFunc,
		0,
		0,
		&dwThreadID
		);
	
	//wait for the thread to exit
	while(TRUE){
		GetExitCodeThread(hT, &dwExitCode);
		if(dwExitCode == STILL_ACTIVE){
			printf("Thread %d is still running.\n", dwThreadID);
			Sleep(20);
			continue;
		}
		printf("Thread exit code was %d.\n", dwExitCode);
		break;
	}

	return 0;
}

DWORD WINAPI theFunc(LPVOID lPtr){
	for(int i = 0; i < 10; i++){
		Sleep(20 * i);
	}

	return 0;
}

Note that the GetExitCodeThread() function itself returns a Boolean value.

Directory Management in win32

Creating and deleting directories involves a pair of thankfully simple functions, CreateDirectory() and RemoveDirectory(). The CreateDirectory() function takes two arguments, the first is the pathname to the directory to create, and the second is a pointer to a SECURITY_ATTRIBUTES structure. For the second argument, we can simply put NULL.  The RemoveDirectory() function has only one parameter, the name of the directory to be removed. Both functions return a Boolean value stating the success or failure of the function call.

#include <Windows.h>
#include <stdlib.h>
#include <stdio.h>

int _tmain(int argc, char argv[]){

	if((CreateDirectory("exampleDir", NULL))==FALSE){
		printf("Error creating directory.\n");
		exit(EXIT_FAILURE);
	} else {
		printf("Directory created.\n");
	}

	if((RemoveDirectory("exampleDir"))==FALSE){
		printf("Error removing directory.\n");
		exit(EXIT_FAILURE);
	} else {
		printf("Directory deleted.\n");
	}

	return 0;

}

A process has a working directory. We can both get and set the working directory. The GetCurrentDirectory() function takes two arguments, the size of the buffer in characters, and the buffer to write the full pathname of the directory to. The SetCurrentDirectory() function has one parameter, the name of the directory we wish to set as the current working directory.

#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>

int _tmain(int argc, char argv[]){

	TCHAR szBuffer[MAX_PATH + 1];
	TCHAR szNewDir[] = _T("testdir");
	DWORD dwRValue = 0;
	CreateDirectory(szNewDir, NULL);

	dwRValue = GetCurrentDirectory(MAX_PATH, szBuffer);

	if(dwRValue != 0){
		_tprintf(_T("Current directory is %s\n"), szBuffer);
	} else {
		printf("Error getting current directory.\n");
		exit(EXIT_FAILURE);
	}

	if(!(SetCurrentDirectory(szNewDir))){
		printf("Could not change directory.\n");
		exit(EXIT_FAILURE);
	} else {
		printf("Directory changed.\n");
	}

	if(!(GetCurrentDirectory(MAX_PATH, szBuffer))){
		printf("Could not get working directory.\n");
		exit(EXIT_FAILURE);
	} else {
		_tprintf(_T("Current directory is %s\n"), szBuffer);
	}

	return 0;

}

The ReadConsole() and WriteConsole() functions are used to read and write from the console. Both the ReadConsole() and WriteConsole() functions take five arguments; the first argument is a HANDLE to standard input or standard output.  We get a standard handle using the GetStdHandle() function, which takes a single parameter that can be one of three DWORD values, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, and STD_ERROR_HANDLE .

#include <Window.h>
#include <stdlib.h>
#include <stdio.h>

int _tmain(int argc, char argv[]){

	//create a handle to standard output
	HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
	TCHAR *szMsg = "Nice boat!";
	DWORD dwCount;
	BOOLEAN bRValue;

	if(hOut==INVALID_HANDLE_VALUE){
		printf("Could not open a file handle to standard output.\n");
		exit(EXIT_FAILURE);
	}

	WriteConsole(hOut, szMsg, _tcslen(szMsg),  &dwCount, NULL);

	if(dwCount == _tcslen(szMsg)){
		printf("\nThe full message has been printed.\n");
	} else {
		printf("\nError! The full message has not printed.\n");
	}

	CloseHandle(hOut);

	return 0;

}

Note that a process can only have one console at a time.

Working with Disks and Directories in win32

We can find out the maximum size of any drive, along with its available free space, using the GetDiskFreeSpace() function.

The GetDiskFreeSpace() function has five parameters, the first of which is a pointer to a string containing the pathname of the root directory of the disk. If we specify NULL for this argument, the function uses the current disk.

The second, third, fourth, and fifth arguments are all of type DWORD. The second argument is a pointer to a DWORD variable that receives the number of sectors per cluster. The third argument is a pointer to a variable that receives the number of bytes per sector. The fourth argument is a pointer to a variable that stores the total number of clusters that are free and available to the owner of the thread. The fifth and final argument is a pointer to a variable that stores the number of clusters, both free and occupied, available to the user.

#include <Windows.h>
#include <stdlib.h>
#include <stdio.h>

int _tmain(int argc, char argv[]){

	BOOLEAN bSuccess;

	DWORD dwSectorsPerCluster;
	DWORD dwBytesPerSector;
	DWORD dwFreeClusters;
	DWORD dwTotalClusters;

	//get disk space for current drive
	bSuccess = GetDiskFreeSpace(
		NULL, //current drive
		&dwSectorsPerCluster, //sectors per cluster
		&dwBytesPerSector, //bytes per sector
		&dwFreeClusters, //free clusters
		&dwTotalClusters //total clusters
		);

	if(!bSuccess){
		printf("Could not get drive information.\n");
		exit(EXIT_FAILURE);
	}

	printf("Bytes per sector: %d\n", dwBytesPerSector);
	printf("Sectors per cluster: %d\n", dwSectorsPerCluster);
	printf("Free clusters: %d\n", dwFreeClusters);
	printf("Total clusters: %d\n", dwTotalClusters);

	unsigned int uiKBPerCluster = dwBytesPerSector * dwSectorsPerCluster / 1024;

	printf("You have %.2f MB of free storage\n", (double) uiKBPerCluster * dwFreeClusters / 1024);
	printf("You have %.2f MB of total storage\n", (double) uiKBPerCluster * dwTotalClusters / 1024);

	return 0;

}

The GetFreeDiskSpace() function returns data about the number of clusters and free clusters on a disk. Clusters are sets of sectors grouped together as a logical unit. The function also returns the number of sectors per cluster and the number of bytes per sector.

We can query for the drive letters that are currently in use. The GetLogicalDriveStrings() function returns the drive letters in string form, and takes two arguments, the first being the size of the buffer and the second being the buffer itself. The GetLogicalDriveStrings() function returns 0 on failure.

#include <Windows.h>
#include <stdlib.h>
#include <stdio.h>

int _tmain(int argc, char argv[]){

	TCHAR szBuffer[256];
	TCHAR *szPtr;
	DWORD dwBufferLength = _tcslen(szBuffer);

	GetLogicalDriveStrings(dwBufferLength, szBuffer);

	for(szPtr = szBuffer; *szPtr!=''; szPtr++){
		//print out drive letter
		_tprintf(_T("%s\n"), szPtr);
		//move ahead to next entry
		while(*szPtr!=''){szPtr++; }
	}

	return 0;

}

Note that the final buffer entry is followed by two  characters.

The SearchPath() function searches either the system path or a given directory for a file. The SearchPath() function takes five arguments. The first argument is the path to search, the second argument is the file name, the third argument is the file extension, the fourth argument is the size of the result buffer, the fifth argument is the result buffer, and the sixth argument is a pointer to the file part of the result path.

#include <Windows.h>
#include <stdlib.h>
#include <stdio.h>

int _tmain(int argc, char argv[]){

	TCHAR szFilename[] = _T("notepad.exe");
	TCHAR szDirname[MAX_PATH + 1];
	TCHAR szBuffer[MAX_PATH + 1];
	TCHAR *ptrFile;
	DWORD dwReturnValue;

	dwReturnValue = SearchPath(
		NULL, //search system path
		szFilename,
		0, //extension
		MAX_PATH, //buffer length
		szBuffer,
		&ptrFile
		);

	//if 0 returned, then file not found
	if(dwReturnValue==0){
		_tprintf(_T("File %s not found.\n"), szFilename);
		exit(EXIT_FAILURE);
	} else {
		_tprintf(_T("File %s found.\n"), szFilename);
		_tprintf(_T("%s\n"), szBuffer);
	}

	return 0;

}

Later we will cover methods for searching for filenames with wildcards.

Working with Security Identifiers in win32

The win32 GetUserName() function fetches the name of the user associated with the current thread. The GetUserName() function takes two arguments, a char buffer to hold the username, and a pointer to a DWORD value indicating the size of the buffer. The function returns a Boolean value indicating whether or not it succeeded in acquiring the username.

#include <Windows.h>
#include <stdlib.h>
#include <stdio.h>

int _tmain(int argc, _TCHAR* argv[])
{
	TCHAR tchBuffer[256];
	//_tcslen used in place of strlen
	DWORD dwBufferSize = _tcslen(tchBuffer);

	if(GetUserName(tchBuffer, &dwBufferSize)){
		printf("Username acquired.\n");
	} else {
		printf("Failed to acquire the username.\n");
		exit(EXIT_FAILURE);
	}

	_tprintf(_T("The username is: %s\n"), tchBuffer);

	return 0;
}

The CreateWellKnownSid() function creates a SID for the predefined aliases specified by the WELL_KNOWN_SID_TYPE enumeration. The CreateWellKnownSid() function has four parameters. The first parameter is a value from the WELL_KNOWN_SID_TYPE enumeration. The second parameter is a pointer to a SID to specify what domain to create the SID in; we will put NULL here, to default to the local machine. The third parameter is a pointer to a SID type that will store the new SID, which is a binary value. The fourth value is a pointer to a DWORD value that specifies the amount of storage available in the SID pointed to by the third parameter.

#include <Windows.h>
#include <stdlib.h>
#include <stdio.h>

int _tmain(int argc, _TCHAR* argv[])
{
	BOOL boolReturnVal;
	BYTE bTheSID[SECURITY_MAX_SID_SIZE];
	DWORD dwSIDSize = SECURITY_MAX_SID_SIZE;

	boolReturnVal = CreateWellKnownSid(WinLocalSystemSid, NULL, bTheSID, &dwSIDSize);

	if(boolReturnVal==FALSE){
		printf("Could not create well-known SID.\n");
		printf("Error %d.\n", GetLastError());
		exit(EXIT_FAILURE);
	} else {
		printf("Well-known SID created.\n");
	}

	return 0;
}

The constant SECURITY_MAX_SID_SIZE defines the maximum size of a SID.

Since the SID is binary data, we need to use a function call to convert it into displayable data. The ConvertSidToString() function converts a security identifier to string format.

#include <Windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <sddl.h>

int _tmain(int argc, _TCHAR* argv[])
{
	BOOL boolRVal;
	BYTE bSID[SECURITY_MAX_SID_SIZE];
	LPWSTR pSid = _T("");
	DWORD dwSize = SECURITY_MAX_SID_SIZE;

	boolRVal = CreateWellKnownSid(
		WinLocalSystemSid,
		NULL, //use this system
		bSID,
		&dwSize
		);

	if(!boolRVal){
		printf("Error creating SID: %d\n", GetLastError());
		exit(EXIT_FAILURE);
	}

	boolRVal = ConvertSidToStringSid(bSID, &pSid);

	if(boolRVal){
		printf("The SID is %ls.\n", pSid);
	}

	return 0;
}

Please note the inclusion of the sddl.h header file.

The LookupAccountName() function gets us the security identifier for the account name and system name we provide the function as arguments. A security identifier, usually called a SID, is a pseudo-kernel object that describes a user account.

The LookupAccountName() function takes a whopping seven arguments. The first argument provided to LookupAccountName() is a null-terminated character string that indicates the name of the system; if we enter NULL here, the function uses the system name of the current system. The second argument is also a null-terminated string indicating the user account name.

The third and fourth arguments define a buffer that will store the SID returned from the function. The third argument is the buffer itself, and the fourth argument is the length of that buffer.

The fifth and sixth arguments define a char buffer that will store the domain name.

The seventh and final argument is the type of sid, indicated by the enumerated type SID_NAME_USE.

#include <Windows.h>
#include <stdlib.h>
#include <sddl.h>
#include <stdio.h>

int _tmain(int argc, _TCHAR* argv[])
{
	BYTE bSID[SECURITY_MAX_SID_SIZE];
	BOOL bRVal;
	DWORD dwSidSize = SECURITY_MAX_SID_SIZE;
	
	LPTSTR szSID = NULL;

	TCHAR szUserName[256], szDomainName[256];
	DWORD dwUserSize = _tcslen(szUserName);
	DWORD dwDomainSize = _tcslen(szDomainName);
	
	SID_NAME_USE snu = SidTypeInvalid;

	//find the current user's name
	if(!(GetUserName(szUserName, &dwUserSize))){
		printf("Error getting username.\n");
		exit(EXIT_FAILURE);
	} else {
		printf("Username acquired.\n");
	}

	bRVal = LookupAccountName(
		NULL, //use this system
		szUserName, //the user to look up
		bSID, //the returned SID
		&dwSidSize, //the size of the SID returned
		szDomainName, //the returned domain name
		&dwDomainSize, //the size of the domain name
		&snu //the type of sid
		);

	bRVal = ConvertSidToStringSid(bSID, &szSID);

	//output values
	_tprintf(_T("SID %ls for %s in %s domain.\n"), szSID, szUserName, szDomainName);

	//clean up
	LocalFree(szSID);
	szSID = NULL;

	return 0;
}


The SID we get from the code above is valuable, since it uniquely identifies the account running the process.

File Management with win32

We can delete a file by calling the DeleteFile() function and providing it with the file name. The DeleteFile() function returns a Boolean value indicating whether or not the file was successfully removed. Remember here that all absolute pathnames start with a drive letter or server name.

#include <Windows.h>
#include <stdlib.h>
#include <stdio.h>

int _tmain(int argc, _TCHAR* argv[])
{
	//only works if a file 
	//named 'testfile.txt' isn't in the current directory
	//share mode is zero
	//so no other process can open this file
	HANDLE hFile = CreateFile(_T("testfile.txt"), GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
	//check for INVALID_HANDLE_VALUE to make sure
	//the CreateFile() function worked
	if(hFile==INVALID_HANDLE_VALUE){
		printf("Error creating file.\n");
		exit(EXIT_FAILURE);
	} else {
		printf("File 'testfile.txt' created.\n");
	}

	//close handle so we can delete the file.
	CloseHandle(hFile);

	if(DeleteFile(_T("testfile.txt"))){
		printf("File deleted.\n");
	} else {
		printf("File not deleted.\n");
	}

	exit(EXIT_SUCCESS);

}

The CopyFile() function can copy an entire file. The CopyFile() function takes three arguments, a pointer to the string containing the name of the source file, a pointer to a string containing the name of the new file, and a Boolean value that states whether the function should fail if the file already exists. We should pass FALSE for the third argument if we only want the file to be copied if the target doesn’t exist.

#include <Windows.h>
#include <stdlib.h>
#include <stdio.h>

int _tmain(int argc, _TCHAR* argv[])
{
	TCHAR szFilename[] = _T("thefile.txt");
	TCHAR szTargetName[] = _T("thefilescopy.txt");

	HANDLE hFHandle = CreateFile(szFilename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

	if(hFHandle==INVALID_HANDLE_VALUE){
		printf("Problem creating file handle.\n");
		exit(EXIT_FAILURE);
	} else {
		_tprintf(_T("File %s created.\n"), szFilename);
		CloseHandle(hFHandle);
	}

	//this should work
	if(CopyFile(szFilename, szTargetName, TRUE)){
		_tprintf(_T("File copied to %s\n"), szTargetName) ;
	} else {
		_tprintf(_T("File not copied to %s\n"), szTargetName) ;
	}

	//this shouldn't work
	if(CopyFile(szFilename, szTargetName, TRUE)){
		_tprintf(_T("File copied to %s\n"), szTargetName) ;
	} else {
		_tprintf(_T("File not copied to %s\n"), szTargetName) ;
	}

	//but this should work
	if(CopyFile(szFilename, szTargetName, FALSE)){
		_tprintf(_T("File copied to %s\n"), szTargetName) ;
	} else {
		_tprintf(_T("File not copied to %s\n"), szTargetName) ;
	}

	//let's delete the copied file
	if(DeleteFile(szTargetName)){
		_tprintf(_T("File %s deleted.\n"), szTargetName);
	}

	exit(EXIT_SUCCESS);

}

Note the use of _tprintf().

We have two functions to move a file. The MoveFile() function takes two arguments and moves a file, but fails if the new file already exists. The MoveFileEx() takes an additional third argument of type DWORD and specifies whether or not to replace an existing file and whether or not to use the CopyFile() with the DeleteFile() function in the case of moving a file to another volume.

#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>


int _tmain(int argc, _TCHAR* argv[])
{
	TCHAR szSource[] = _T("source.txt");
	TCHAR szTarget[] = _T("target.txt");

	HANDLE hFH = CreateFile(szSource, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

	if(hFH!=INVALID_HANDLE_VALUE){
		_tprintf(_T("%s created.\n"), szSource);
		CloseHandle(hFH);
	} else {
		exit(EXIT_FAILURE);
	}

	if(MoveFile(szSource, szTarget)){
		_tprintf(_T("%s moved to %s\n"), szSource, szTarget);
	} else {
		_tprintf(_T("%s not moved to %s\n"), szSource, szTarget);
	}

	if(MoveFileEx(szSource, szTarget, MOVEFILE_REPLACE_EXISTING)){
		_tprintf(_T("%s moved to %s\n"), szSource, szTarget);
	} else {
		_tprintf(_T("%s not moved to %s\n"), szSource, szTarget);
	}

	exit(EXIT_SUCCESS);

}

Note that the MOVEFILE_REPLACE_EXISTING option is used to replace an existing file in the MoveFileEx() function.

Interfacing with the Windows System Using win32

The GetSystemInfo() function enables us to get information about the hardware on which our program is running. The GetSystemInfo() function is a void function. It takes one argument, a pointer to a SYSTEM_INFO structure.

The SYSTEM_INFO structure stores information about the current computer system. The structure’s wProcessorArchitecture field is of type WORD and indicates the processor architecture of the installed operating system. If it has the value PROCESSOR_ARCHITECTURE_AMD64 than it is an x64 machine, either AMD or Intel.

#include <stdio.h>
#include <Windows.h>

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

	printf("PROCESSOR_ARCHITECTURE_AMD64 = %d\n", PROCESSOR_ARCHITECTURE_AMD64);
	printf("PROCESSOR_ARCHITECTURE_ARM = %d\n", PROCESSOR_ARCHITECTURE_ARM);
	printf("PROCESSOR_ARCHITECTURE_INTEL = %d\n", PROCESSOR_ARCHITECTURE_INTEL);

	return 0;
}

Note that the dwOemId field is supposed to contain the Original Equipment Manufacturer ID, but this field is deprecated. Likewise, the dwProcessorType field is also obsolete, we should use the wProcessorArchitecture field instead.

#include <stdio.h>
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
	SYSTEM_INFO structSInfo;
	GetSystemInfo(&structSInfo);

	printf("The value for wProcessorArchitecture is %d\n", structSInfo.wProcessorArchitecture);
	
	switch(structSInfo.wProcessorArchitecture){
		case PROCESSOR_ARCHITECTURE_AMD64:
			printf("x64 architecture.\n");
			break;
		case PROCESSOR_ARCHITECTURE_ARM:
			printf("ARM architecture.\n");
			break;
		case PROCESSOR_ARCHITECTURE_INTEL:
			printf("x86 architecture.\n");
			break;
	}

	creturn 0;
}

The lpMinimumApplicationAddress of the SYSTEM_INFO structure is a pointer to the lowest memory address accessible to applications and DLLs. The lpMaximumApplicationAddress is a pointer to the highest address accessible.

#include <Windows.h>
#include <stdio.h>

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

  SYSTEM_INFO structSysInfo; GetSystemInfo(&structSysInfo); 
  printf("Pointer to the lowest memory address: %p\n", structSysInfo.lpMinimumApplicationAddress);
  printf("Pointer to the highest memory address: %p\n", structSysInfo.lpMaximumApplicationAddress);
  printf("Number of processors: %d\n", structSysInfo.dwNumberOfProcessors); 
 
  return 0; 

}

The GetVersionEx() function returns operating system version information via an OSVERSIONINFO structure that contains fields indicating major and minor version and build numbers. The function itself returns a Boolean value of True or False.

#include 
#include 

int _tmain(int argc, _TCHAR* argv[])
{
	OSVERSIONINFO structOSVInfo;
	//don't forget to set this field
	structOSVInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

	if(GetVersionEx(&structOSVInfo)){
		printf("Version information obtained.\n");
 	} else {
		printf("Version information not obtained.\n");
		printf("Last Error: %d\n", GetLastError());
		exit(EXIT_FAILURE);
	}

	printf("Version: %d.%d\n", structOSVInfo.dwMajorVersion, structOSVInfo.dwMinorVersion);

	return 0;
}

Note that we need to initialize the dwOSVersionInfo field of the OSVERSIONINFO structure to be sizeof(OSVERSIONINFO).

The GetLogicalDriveStrings() function fills a buffer with strings that specify valid drives in the system. If the function fails, the return value is 0, if the function succeeds, the return value is the length in characters of the strings copied into the buffer. The function takes two arguments, the first argument is the length of the buffer in characters, and the second argument is the buffer itself.

#include <stdio.h>
#include <string.h>
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    char szBuffer[512];

    if(GetLogicalDriveStrings(strlen(szBuffer),szBuffer)==0){
        printf("Error getting logical drives.\n");
        exit(EXIT_FAILURE);
    } else {
        printf("Logical drives acquired.\n");
    }

    char * szPtr = szBuffer;

    //read through the buffer
    while(*szPtr!=NULL){
        printf("%s\n", szPtr);
        //move the pointer to the next null-terminated
        //string
        szPtr += strlen(szPtr) + 1;
    }

    return 0;
}

Now that we know which drives are available, we can find out what type of device each drive represents by calling the GetDriveType() function. The GetDriveType() function determines the type of the drive from the root directory that is passed to it as a string argument and returns a numeric value indicating the drive type.

#include <stdio.h>
#include <string.h>
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    
    char szDriveNames[256];

    if(GetLogicalDriveStrings(strlen(szDriveNames), szDriveNames)==false){
        printf("Error getting drive names.\n");
        exit(EXIT_FAILURE);
    }

    char *szPtrNames = szDriveNames;
    char szDriveName[5];

    while(*szPtrNames!=NULL){
        printf("Drive %s: \t", szPtrNames);

        switch(GetDriveType(szPtrNames)){
            case DRIVE_FIXED:
                printf("HDD/SSD");
                break;
            case DRIVE_REMOTE:
                printf("Remote");
                break;
            case DRIVE_CDROM:
                printf("CD/DVD");
                break;
        }
        printf("\n");

        szPtrNames += strlen(szPtrNames) + 1;
    }

    return 0;
}

The GetVolumeInformation() function retrieves information about the file system and volume associated with the specified root directory. The function returns a Boolean value indicating its success or failure.

#include <stdio.h>
#include <string.h>
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    char szRoot[] = "C:\\";
    char szVolName[256];
    DWORD dwNameSize = sizeof(szVolName);
    DWORD dwVolID, dwMaxComLength, dwFileSysFlags;
    char szFileSys[256];
    DWORD dwFileSysNameSize = sizeof(szFileSys);
    BOOL returnVal;

    returnVal = GetVolumeInformation(
        szRoot, //root of volume
        szVolName, //volume name buffer
        dwNameSize, //length of volume name buffer
        &dwVolID, //volume serial number
        &dwMaxComLength, //maximum component length
        &dwFileSysFlags, //file system attributes
        szFileSys, //file system name buffer
        dwFileSysNameSize //length of file system name buffer
        );
    
    if(!returnVal){
        printf("Couldn't retrieve volume information.\n");
        exit(EXIT_FAILURE);
    } else {
        printf("Volume information retrieved.\n");
    }

    return 0;
}

Standard Handles and win32

Like *nix, a Windows process has three standard devices for input, output, and error reporting; to access these standard devices, Windows needs a HANDLE that is acquired with the GetStdHandle() function. The GetStdHandle() function accepts a DWORD argument and returns a HANDLE on success and INVALID_HANDLE_VALUE otherwise.

The parameter can be one of three values, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, and STD_ERROR_HANDLE.

#include <Windows.h>
#include <stdio.h>

int _tmain(int argc, _TCHAR* argv[])
{
	printf("%x \n", STD_INPUT_HANDLE);
	printf("%x \n", STD_OUTPUT_HANDLE);
	printf("%x \n", STD_ERROR_HANDLE);

	return 0;
}

The GetStdHandle() function does not create a new or duplicate handle on a standard device. No matter how many times we call GetStdHandle(), the function returns the same handle.

#include 
#include 

int _tmain(int argc, _TCHAR* argv[])
{
	char *szString = "For great justice! ";
	DWORD dwBytesWritten;
	HANDLE hStdin = GetStdHandle(STD_OUTPUT_HANDLE);

	if(hStdin == INVALID_HANDLE_VALUE){
		printf("Error accessing standard input.\n");
	}

	if(WriteFile(hStdin, szString, strlen(szString), &dwBytesWritten, NULL)){
		printf("No problems writing to standard input.\n");
	}

	return 0;
}

The SetStdHandle() function enables us to redirect standard input, standard output, and standard error. The SetStdHandle() function takes two arguments; the first argument is a DWORD value indicating whether we are redirecting standard input, standard output, or standard error, and the second value is a handle to the file we want to redirect to.

#include <Windows.h>
#include <stdio.h>

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

	HANDLE hFile;

	hFile = CreateFile("textfile.txt", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

	if(hFile == INVALID_HANDLE_VALUE){
		printf("Error opening file.");
		return -1;
	}

	if(SetStdHandle(STD_OUTPUT_HANDLE, hFile)==FALSE){
		printf("Error setting standard handle.\n");
		return -2;
	}

	//will print to the file
	//not to the console
	printf("I survived the Kobayashi Maru!\n");
	printf("Photons be free!\n");
	
	return 0;
}

Note that if you call GetStdHandle() after redirecting the standard input, output or error you will get the redirected handle! To recover standard output to the console screen, call CreateFile() with CONOUT$, likewise, a call to CreateFile() with CONIN$ will recover a handle to standard input.