Skip to main content

Win32 System Programming - Part 4

Proseslerin komut satiri argumanlari (Command line arguments of a process)

  • Programlari komut satirindan calistirirken program isminin yanina yazilan yazilara komut satiri argumanlari denir. Windows sistemlerinde komut satiri argumanlari createProcess fonksiyonunda parametre olarak girilmektedir.
  • windows da komut satiri argumanlari createProcess in ikinci parametresi olarak girilir. ve tek bir yazi seklindedir.
  • Processin komut satiri argumanlari GetCommandLine api fonksiyonu ile elde dilebilir.
  • Derleyilerin baslangic kodlari bu api yi kullanarak komut satirini elde eder. Sonra onu boskuk karakterlerinden parse ederek ayirir ve main fonksiyonuna gecirir.
  • UNIX / Linux sistemlerin komut satiri argumanlari tek tek exec fonksiyonlarinda verilmektedir.
  • komut satiri argumanlari process kontrol blogunda saklanmaktadir.

GNU Stili komut satiri argumanlari

Klasik GNU sisteminde komut satiri argumanalari 3 bicimde olabilir.

1.seceneksiz arguman 2.yalnizca secenek 3.secenekli arguman

  • secenek bir tire ve tek bir karakterden olusur. (orn, ls -l -a)
  • secenekler birlestirilebilir. (orn. ls -la)
  • Eger secenek birden fazla karakterden olusuyor ise secenek karakteri (–) ile belirtilir.(ornegin –login gibi )

Unix/ Lionux sistemlerinde komut satiri argumanlarini parse etmek icin getopt ve getopt_long isimli fonksiyonlar vardir. http://www.csystem.org/makaleler/programlar%C4%B1n-komut-sat%C4%B1r%C4%B1-arg%C3%BCmanlar%C4%B1 makalesini oku.

/*----------------------------------------------------------
    Proseslerin komut satırı argümanları 
    (Parse işlemi düzeltilmiş versiyon)
------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <tchar.h>
#include <Windows.h>
#include <Psapi.h>

/* Symbolic Constants */

#define MAX_CMD_LINE                1024
#define MAX_CMD_PARAMS                32

/* Type Declarations */

typedef struct tagCMD {
    const char *name;
    void (*Proc)(void);
} CMD;

/* Function Prototypes */

void ExitSys(LPCTSTR lpszMsg, int status);
void PrintSysErr(void);
void Parse(void);
void DirProc(void);
void ClsProc(void);
void ChDirProc(void);
void GetEnvProc(void);
void SetEnvProc(void);
void RunProcess(void);
void TrimCmd(char *trimmed);
void ExitProc(void);

/* Global Data Definitions */

char g_cmd[MAX_CMD_LINE];
char *g_params[MAX_CMD_PARAMS];
int g_nparams;
CMD g_cmds[] = {
    {"dir", DirProc},
    {"cls", ClsProc},
    {"cd", ChDirProc},
    {"getenv", GetEnvProc},
    {"setenv", SetEnvProc},
    {"exit", ExitProc},
    {NULL, NULL},
};

/* Function Definitions */

int main(void)
{
    char cwd[1024];
    int i;

    for (;;) {
        if (GetCurrentDirectory(1024, cwd) == 0)
            ExitSys("GetCurrentDirectory", EXIT_FAILURE);
        printf("%s>", cwd);
        gets(g_cmd);
        Parse();
        if (g_nparams == 0)
            continue;
        for (i = 0; g_cmds[i].name != NULL; ++i)
            if (!strcmp(g_cmds[i].name, g_params[0])) {
                g_cmds[i].Proc();
                break;
            }
        if (g_cmds[i].name == NULL)
            RunProcess();

        if (!strcmp(g_cmd, "quit"))
            break;
    }
        
    return 0;
}

void Parse(void)
{
    char *str, *beg;
    str = g_cmd;
    
    for (g_nparams = 0;; ++g_nparams) {
        while (isspace(*str))
            ++str;
        if (*str == '\0')
            break;
        beg = str;
        while (!isspace(*str) && *str != '\0') {
            if (*str == '\"') {
                ++str;
                while (*str != '\"' && *str != '\0')
                    ++str;
                if (*str == '\"')
                    ++beg;
                if (*str == '\0')
                    break;
                *str = '\0';
            }
            ++str;
        }
        g_params[g_nparams] = beg;
        if (*str == '\0')
            break;
        *str++ = '\0';
    }

    ++g_nparams;
}

void DirProc(void)
{
    printf("dir\n");
}

void ClsProc(void)
{
    if (g_nparams > 1) {
        printf("too many arguments...\n\n");
        return;
    }
    system("cls");
}

void ChDirProc(void)
{
    char cwd[1024];

    if (g_nparams == 1) {
        if (GetCurrentDirectory(1024, cwd) == 0)
            ExitSys("GetCurrentDirectory", EXIT_FAILURE);
        printf("%s\n\n", cwd);
        return;
    }

    if (g_nparams > 2) {
        printf("too many arguments...\n\n");
        return;
    }

    if (!SetCurrentDirectory(g_params[1]))
        PrintSysErr();
}

void GetEnvProc(void)
{
    char buf[1024];
    char *envs;

    if (g_nparams == 1) {
        if ((envs = GetEnvironmentStrings()) == NULL)
            ExitSys("GetEnvironmentStrings", EXIT_FAILURE);

        while (*envs != '\0') {
            puts(envs);
            envs += strlen(envs) + 1;
        }
        printf("\n");
        return;
    }

    if (g_nparams > 2) {
        printf("too many arguments..\n\n");
        return;
    }

    if (!GetEnvironmentVariable(g_params[1], buf, 1024)) {
        PrintSysErr();
        return;
    }

    printf("%s\n\n", buf);
}

void SetEnvProc(void)
{
    if (g_nparams == 1) {
        printf("argument missing...\n\n");
        return;
    }

    if (g_nparams > 3) {
        printf("too many arguments..\n\n");
        return;
    }

    if (!SetEnvironmentVariable(g_params[1], g_nparams == 3 ? g_params[2] : NULL)) {
        PrintSysErr();
        return;
    }
}

void RunProcess(void)
{
    STARTUPINFO si = {sizeof(STARTUPINFO)};
    PROCESS_INFORMATION pi;
    char cmdLine[MAX_CMD_LINE];

    TrimCmd(cmdLine);
    printf(":%s:\n", cmdLine);
    if (!CreateProcess(NULL, cmdLine, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) {
        PrintSysErr();
        return;
    }

    CloseHandle(pi.hProcess);
    printf("\n");
}

void ExitProc(void)
{
    exit(EXIT_SUCCESS);
}

void TrimCmd(char *trimmed)
{
    char *str1 = g_cmd;
    char *str2 = g_cmd + strlen(g_cmd) - 1;

    while (isspace(*str1))
        ++str1;
    if (*str1 == '\0') {
        *trimmed = '\0';
        return;
    }
    
    while (isspace(*str2))
        --str2;

    do {
        *trimmed++ = *str1++;
    } while (str1 <= str2);
    *trimmed = '\0';
}

void ExitSys(LPCTSTR lpszMsg, int status)
{
    DWORD dwLastError = GetLastError();
    LPTSTR lpszErr;
    
    if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwLastError,
                    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpszErr, 0, NULL)) {
        fprintf(stderr, "%s: %s", lpszMsg, lpszErr);
        LocalFree(lpszErr);
    }
    
    exit(status);
}

void PrintSysErr(void)
{
    DWORD dwLastError = GetLastError();
    LPTSTR lpszErr;
    
    if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwLastError,
                    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpszErr, 0, NULL)) {
        fprintf(stderr, "%s\n", lpszErr);
        LocalFree(lpszErr);
    }
}

UNIX /Linux Sistemlerinin Tarihsel Gelisimi

Ken Thomson ile roportaj

  • Unix isletim sistemi 1969 1970 yillarindan AT&T bell labs da Ken thomson, Brian Kernigan ve Dennis Ricchi tarafondan dec makinalari icin yazilmistir.
  • Deniss R. bu gruptan bagimsiz olarak unix yaziminda kullanilan Ken Thaomson’in B programlama dilinden hareketle C yi gelistirmistir.
  • Unix 1973 yilinda yeniden C ile yazildi. Bundan sonra Unix in kaynak kodlari pekcok universite ve arastirma kurumlarina dagitildi. Onlar da C ogrenerek UNIX varyantlari olusturmaya basladilar.
  • 1970 li yillarin ortalarinda berkeley deki kaliforniya universitesi Orjinal UNIX kodlarini degistirerek BSD sistemlerini cikartti.
  • Soket sistemi, sanal bellek, tcpip hep ilk olarak bsd de deneniyor.
  • zamanla pekcok unix turevi sistem olusturuldu.
  • 80 li yillarin ortalarinda R Stallman ponerisi ile farkli unix turevi sistemler POSIX ismi ile standardize edilmistir.
  • POSIX standartlari birkac bolumden olusmaktadir. Bolumlerden biri Shell komutlarini aciklari.

Digeri C den kullanabilecegimiz api fonksiyonunu aciklar.

  • Bugun LINUX, BSD, SOLARIS gibi sistemlerin hepsi POSIX uyumludur.
  • PCBSD bsd surumu olarak kullanilabilir.
  • WMS ve openWMS
  • opengroup dernegi
  • 80 li yillarin basindan Sichard S FSF isminde bir xx kurdu. ve Free software akimini kurdu.
  • FSF ayni zamanda GNU projesini baslatti, Bu projenin amaci acik bir isletim sistemi ve gelistirme araclarini yazmakti.
  • Ozgur yazilimi temsil eden lisansn GPL lisansidir. Bu lisansa copyright yerine copyleft denilmektedir.
  • 90 li yillarin basinda Linus torvalds linux u gelistirmeye basladi.
  • Linux sistemleri bugn icin acik kaynak kodlu posix uyumlu unix turevi sistemlerdir.

Standart C Fonksiyonlari, POSIX Fonksiyonlari ve SIstem Fonksiyonlari

  • Standar C fonksiyonlari C derleyicisinin bulundugu her yerde olan fonksiyonlardir. Microcontroller olsa bile bu fonksiyonlar bulunmaktadirlar
  • POSIX Fonksiyonlari POSIX Standartlarina uyan UNIX turevi sistemlerde var olan fonksiyonlardir.
  • Sistem fonksiyonlari ise i isletim sistemine ozgu fonksiyonlardir.
    • Windows dunyasinda standard C fonksiyonlari ve windows api fonksiyonlari vardir.
    • LINUX
      • fopen —> open —> sys_open
      • cdeki fopen fonksiyonu POSIX fonksiyonu olan open fonksiyonunu cagirir. Bu da sistem fonksiyonu olan sys_open fonksiyonunu cagirir.
    • Windows
      • fopen —> CreateFile Windows daki c fonksiyonu olan fopen cagirildigi zaman, API fonsiyonu olan createFile fonksiyonu cagirilir,

Windows Dosya Sistemi

  • DIsk organizasyon tarafi
  • Bellek organizasyon tarafi bulunmaktafir.
  • Bir isletim sisteminin dosya sisteminin iki yonu vardir. Disk tarafi ve bellek tarafi.
  • Sistemin disk tarafi, Yapilan disk organizasyonuna iliskindir. Yani sektorlerin dosyalari tutacak bicimde nasil organize olacagi ile ilgilidir.
  • Bellek tarafi denilince diskteki bu dosyalarin idare edilmedi icin olusturulan veri yapilari akla gelmetedir.
  • isletim sisteminin dosya sistemi diskteki sektorleri bize bagimsiz dosyalarmis gibi gosterir.
  • Biz de dosya islemlerini api fonksiyonleri ile yapariz.
  • BIrcok sistemdi device driver ler dosya olarak tanimlanir ve sistem bu dosyalara okuma yazma yaparak bu driverlari kullanirlar.
  • Windows sistemlerinde temel dosya apileri sunlardir.
    • CreateFile : help dosyası ıcerısınde ıncele
    • ReadFile :
    • WriteFile :
    • SetFilePointer : Dosya kullnici icin bytelerdan olusan brttopluluk. Bir de file pointer diye bir sayi. O anda hagi dosya ile ilgili islem yapilacgini anlatiyor.http://www.kaanaslan.com/resource/article/display_article.php?id=64 makalesini oku
    • CloseHandle :

CreateFile fonksıyonu basarısız olursa invalid handle value isimliozel bir isme geri doner read file fonksiyonunu kullanirken sunlara dikkat etmek gerekmektedir.

  • Dosya gostericisiinin bulundu yerden itibaren kalan byte miktarindan daha fazla miktarda okuma yapmak normak bir durumdur. BU durumda fonksiyon basarisiz olmaz. Okunabilen byte okunur. ve kac byte okundugu bize verilir.
  • ReadFile IO hatasi olustugunda yada gecersiz parametre girildiginde basarisiz olur.
  • EOF durumunda okuma yapmaya calisirsak 0 byte okuruz ancak fonksiyon basarisiz olmaz. UNIX Linux dunyasinda IO hatasinda -1 ve EOF durumunda 0 dinus yapar. WriteFile Fonksiyonu
  • dosyalarin handle degerleri process kontrol blogunda da bulunmaktadir. biz bir disyayi papatmamasak bile process sonlandiginda isletim sistemi butun dosalari bizim icin kapatir. (Terminate process dahil olmak uzere) . Fakat bir dosya ile isimizi bitirmissek onu mumkun oldugunca kapatmak iyi bir tekniktir. Set file pointer — http://www.kaanaslan.com/resource/article/display_article.php?id=64
/*--------------------------------------------------------------------------
    CreateFile, ReadFile, WriteFile örneği
----------------------------------------------------------------------------*/

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

void ExitSys(LPCTSTR lpszMsg, int status);

int main(void)
{
    HANDLE hFile;
    char buf[1024 + 1];
    DWORD dwRead, dwWritten;

    if ((hFile = CreateFile("test.dat", GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_ALWAYS,
                        FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE)
        ExitSys("CreateFile", EXIT_FAILURE);

    if (!ReadFile(hFile, buf, 1024, &dwRead, NULL))
        ExitSys("ReadFile", EXIT_FAILURE);

    buf[dwRead] = '\0';
    puts(buf);

    strcpy(buf, "xxxxx");

    if (!WriteFile(hFile, buf, 5, &dwWritten, NULL))
        ExitSys("WriteFile", EXIT_FAILURE);

    CloseHandle(hFile);

    return 0;
}

void ExitSys(LPCTSTR lpszMsg, int status)
{
    DWORD dwLastError = GetLastError();
    LPTSTR lpszErr;
    
    if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwLastError,
                    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpszErr, 0, NULL)) {
        fprintf(stderr, "%s: %s", lpszMsg, lpszErr);
        LocalFree(lpszErr);
    }
    
    exit(status);
}

Yardimci Dosya fonksiyonlari

read wite yapmayan ancak dosya sistemi ile ilgili islemler yapam fonksiyonlar

  • CopyFile ve CopyFileEx fonksiyonlari dosya kopyalamakta kopyalanir.

DeleteFile Fonksiyonu dosya silmekte kullnilir.

  • GetFileAttribule fonksiyonu ile bir dosyanin ozellik bilgisi elde edilebilir.
  • GetFileSize ve GetFileSizeEx fonksiyonu dosya uzunlugunu elde etmekte kullanilir.
  • MoveFile ve MoveFileEx dosyayi tasimakta gullanilir.
  • CreateDirectory ve CreateDirectoryEx fonksiyonlari yeni bir dizin yaratmak icin kullanilir
  • RemoveDirectory Fonksiyonu bos bor klasoru silebilir.
  • Ici dolu kladoru tek hamlede silen bir API fonsiyonu yoktur ancak shell fonksiyonu vardir.

Dizin Iceriginin elde edilmesi

  • Windows da bir dizin icerigini elde etmek icin 3 API fonksiyonu kullanilir.
  • Bunlar FindFirsFile, FindNextFile ve FileClose fonksiyonlaridir.
  • fonksiyonlar su sekilde kullanilirlar.
    • Once bir kes findfirstfile kullanilir. birden fazla dosyanin elde edilmesi hedeflenmisse bu fonksiyon bize ilk dosyanin bilerini verir.
    • Bundan sonra bir dongu icerisinde find next file cagirilarak diger dosyalarin bilgileri elde edilebilir.
    • En sonunda da FindClose cagirilarak islem sonlandirilir.
    • Bı fonksıyonlarda tum dızın ıcerıgını elde ederız. Her dizinin icerisinde nokta ve cift nokta dizinleri bulunur. Bular dizin yaratilirken olusur ve bunlar istense de silinemezler.
    • Dizinler de dosya gibi islem gorurler. Yani biz bu fonksiyonlarla dizinleri de bulabiliriz.
/*-----------------------------------------------------------------------------
    Dosyanın sonuna ekleme yapmak
-------------------------------------------------------------------------------*/

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

void ExitSys(LPCTSTR lpszMsg, int status);

int main(void)
{
    HANDLE hFile;
    DWORD dwWritten;

    if ((hFile = CreateFile("test.dat", GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_ALWAYS,
                        FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE)
        ExitSys("CreateFile", EXIT_FAILURE);

    SetFilePointer(hFile, 0, 0, FILE_END);

    if (!WriteFile(hFile, "ankara", 6, &dwWritten, NULL) )
        ExitSys("WriteFile", EXIT_FAILURE);

    CloseHandle(hFile);

    return 0;
}

void ExitSys(LPCTSTR lpszMsg, int status)
{
    DWORD dwLastError = GetLastError();
    LPTSTR lpszErr;
    
    if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwLastError,
                    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpszErr, 0, NULL)) {
        fprintf(stderr, "%s: %s", lpszMsg, lpszErr);
        LocalFree(lpszErr);
    }
    
    exit(status);
}
Oz yinelemeli olarak dizin agacini dolasasn bir fonksiyon yazilacak\
/*-----------------------------------------------------------------------------------------------------------
    Dizin içerğinin elde edilmesi
-----------------------------------------------------------------------------------------------------------*/

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

void ExitSys(LPCTSTR lpszMsg, int status);

int main(void)
{
    HANDLE hFind;
    WIN32_FIND_DATA fd;
    char path[MAX_PATH];

    printf("Path:");
    gets(path);

    hFind = FindFirstFile(path,  &fd);
    if (hFind == INVALID_HANDLE_VALUE)
        ExitSys("FindFirstFile", EXIT_FAILURE);

    do {
        printf("%-30s %lu\n", fd.cFileName, fd.nFileSizeLow);
    } while (FindNextFile(hFind, &fd));

    FindClose(hFind);

    return 0;
}

void ExitSys(LPCTSTR lpszMsg, int status)
{
    DWORD dwLastError = GetLastError();
    LPTSTR lpszErr;
    
    if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwLastError,
                    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpszErr, 0, NULL)) {
        fprintf(stderr, "%s: %s", lpszMsg, lpszErr);
        LocalFree(lpszErr);
    }
    
    exit(status);
}
/*----------------------------------------------------------------
    Dizin ağacının dolaşılması
------------------------------------------------------------------*/

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

void ExitSys(LPCTSTR lpszMsg, int status);
void WalkDir(LPCTSTR path);
void WalkDirRecur(LPCTSTR path);

int main(void)
{
    char path[MAX_PATH];

    printf("Path:");
    gets(path);

    WalkDir(path);


    return 0;
}

void WalkDir(LPCTSTR path)
{
    char cwd[1024];

    if (!GetCurrentDirectory(1024, cwd))
        ExitSys("GetCurrentDirectory", EXIT_FAILURE);

    WalkDirRecur(path);

    if (!SetCurrentDirectory(cwd))
        return;
}

void WalkDirRecur(LPCTSTR path)
{
    HANDLE hFind;
    WIN32_FIND_DATA fd;

    if (!SetCurrentDirectory(path))
        return;
    
    hFind = FindFirstFile("*.*",  &fd);
    if (hFind == INVALID_HANDLE_VALUE)
        return;

    do {
        if (!strcmp(fd.cFileName, ".") || !strcmp(fd.cFileName, ".."))
            continue;
        if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
            WalkDirRecur(fd.cFileName);
            if (!SetCurrentDirectory(".."))
                return;
        }
        else
            printf("%-30s %lu\n", fd.cFileName, fd.nFileSizeLow);
    } while (FindNextFile(hFind, &fd));

    FindClose(hFind);
}

void ExitSys(LPCTSTR lpszMsg, int status)
{
    DWORD dwLastError = GetLastError();
    LPTSTR lpszErr;
    
    if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwLastError,
                    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpszErr, 0, NULL)) {
        fprintf(stderr, "%s: %s", lpszMsg, lpszErr);
        LocalFree(lpszErr);
    }
    
    exit(status);
}

/*------------------------------------------------------------
    Dizin ağacının ağaç biçiminde yazdırılması
-------------------------------------------------------------*/

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

void ExitSys(LPCTSTR lpszMsg, int status);
void WalkDir(LPCTSTR path);
void WalkDirRecur(LPCTSTR path);

int main(void)
{
    char path[MAX_PATH];

    printf("Path:");
    gets(path);
    printf("\n");

    WalkDir(path);

    return 0;
}

void WalkDir(LPCTSTR path)
{
    char cwd[1024];

    if (!GetCurrentDirectory(1024, cwd))
        ExitSys("GetCurrentDirectory", EXIT_FAILURE);
    
    WalkDirRecur(path, 0);

    if (!SetCurrentDirectory(cwd))
        return;
}

void WalkDirRecur(LPCTSTR path, int space)
{
    HANDLE hFind;
    WIN32_FIND_DATA fd;
    int i;

    if (!SetCurrentDirectory(path))
        return;
    
    hFind = FindFirstFile("*.*",  &fd);
    if (hFind == INVALID_HANDLE_VALUE)
        return;

    do {
        if (!strcmp(fd.cFileName, ".") || !strcmp(fd.cFileName, ".."))
            continue;
        if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
            for (i = 0; i < space; ++i)
                putchar(' ');
            printf("%s\n", fd.cFileName);
            space += 4;
            WalkDirRecur(fd.cFileName, space);
            space -= 4;
            if (!SetCurrentDirectory(".."))
                return;
        }

    } while (FindNextFile(hFind, &fd));

    FindClose(hFind);
}

void ExitSys(LPCTSTR lpszMsg, int status)
{
    DWORD dwLastError = GetLastError();
    LPTSTR lpszErr;
    
    if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwLastError,
                    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpszErr, 0, NULL)) {
        fprintf(stderr, "%s: %s", lpszMsg, lpszErr);
        LocalFree(lpszErr);
    }
    
    exit(status);
}