Windows核心編程之核心總結(第四章 進程(二))(2018.6.17)

學習目標

上一節我們瞭解了進程、入口函數和進程實例句柄等內容,在進入進程的命令行學習前,有一個全局變量初始化問題需要測試一波。本節的學習目標如下:
1.測試C/C++運行庫啓動函數初始化哪些全局變量
2.進程的命令行
3.進程的環境變量
4.進程的當前驅動器和目錄
5.判斷系統版本

測試啓動函數初始化哪些全局變量

我們知道C/C++運行庫的啓動函數會做一些事後再調用我們的入口函數,而入口函數的參數都是在調用前就初始化好了的。那麼我就產生了一個疑問,全局變量隨入口函數的不同(四種入口函數,分別是main、wmain、wWinMain、WinMain)都分別初始化了哪些全局變量?我做出了下面的測試:
(1)在CUI程序下測試Unicode字符集和多字節字符集兩種情況的全局變量的初始化:

#include<windows.h>
#include<tchar.h>
#include<iostream>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
    //測試兩次:第一次是在Unicode環境下,第二次是在多字節字符集環境下,注意輸出的不同。
    //測試_environ有無被初始化成有用的值
    char** p1 = _environ;
    if (p1 != NULL)
    {
        while (*p1)
        {
            cout << *p1 << endl;
            p1++;
        }
        cout << "---------------------------上面的是_environ輸出的值---------------------------------" << endl;
    }
    //測試_wenviron有無被初始化成有用的值
    wchar_t** p2 = _wenviron;
    if (p2 != NULL)
    {
        while (*p2)
        {
            wcout << *p2 << endl;
            p2++;
        }
        cout << "--------------------------上面的是_wenviron輸出的值--------------------------" << endl;
    }
    //測試__argv有無被初始化成有用的值
    char** p3= __argv;
    if (p3 != NULL)
    {
        while (*p3)
        {
            cout << *p3 << endl;
            p3++;
        }
        cout << "-------------------------上面的是__argv輸出的值----------------------------" << endl;
    }
    //測試__wargv有無被初始化成有用的值
    wchar_t** p4 = __wargv;
    if (p4 != NULL)
    {
        while (*p4)
        {
            wcout << *p4 << endl;
            p4++;
        }
        cout << "-------------------------上面的是__wargv輸出的值----------------------------" << endl;
    }
    system("pause");
    return 0;
}

測試結果:輸出結果太長不好截圖,這裏只給出總結,運行結果可以自己運行查看。如果你寫的主函數是_tmain,那麼其中_environ和_wenviron全局變量,在Unicode環境下,_environ和_wenviron全局變量都被初始化成有用的值了。而在多字節字符集下,_environ全局變量被初始化成有用的值,_wenviron全局變量才被置NULL。明顯,和書中P69頁表格的描述有差異
(2)在GUI程序下測試Unicode字符集和多字節字符集兩種情況的全局變量的初始化:
1.很明顯,下面的測試結果和在wmain函數測試情況相同。
Windows核心編程之核心總結(第四章 進程(二))(2018.6.17)
2.可以看出,下面的測試結果和在main函數測試情況也相同。
Windows核心編程之核心總結(第四章 進程(二))(2018.6.17)
(3)大總結:
對於書中P69頁的全局變量初始化表的描述我產生了質疑。如果你寫的主函數是_tmain,那麼其中_environ和_wenviron全局變量,在Unicode環境下,_environ和_wenviron全局變量都被初始化成有用的值了。而在多字節字符集下,_environ全局變量被初始化成有用的值,_wenviron全局變量才被置NULL。簡單來說就是無論_UNICODE有無被定義,_environ都會被初始化成有用的值,而_wenviron就受字符集影響,跟書產生了歧義。而wargv和argv就是符合書本的情況定義。如果你寫的主函數是_tWinMain,那麼其中_environ和_wenviron全局變量,在Unicode環境下,_environ和_wenviron全局變量都被初始化成有用的值了。而在多字節字符集下,_environ全局變量被初始化成有用的值,_wenviron全局變量才被置NULL。而wargv和argv就是符合書本的情況定義。注意,如果Windows編程,不使用_tmain和_tWinMain函數,而是使用main或wmain,那麼上述的總結不一定成立,但由於兼顧兩種字符集,建議以後寫的入口函數就寫_tmain和_tWinMain函數。

進程的命令行

(1)如果是運行CUI應用程序,在C/C++運行庫啓動函數執行時,就已經初始化好全局變量(包括命令行參數argc、argv或wargv。如果在Unicode字符集下,初始化了argc、argv;如果在多字符集下,初始化了argc、__wargv。)然後調用入口點函數_tmain,將參數argc、argv或wargv傳入_tmain函數。
現在對_tmain函數的參數進行測試:

#include<windows.h>
#include<tchar.h>
#include<iostream>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
    /*
    有兩種方式可以輸入命令行參數:
    1.屬性->配置屬性->調試->命令參數:例如:wo ai ni
    2.在可執行文件目錄下打開命令行窗口(cmd),輸入文件名+命令行參數:例如:ConsoleApplication9 wo ai ni
    但有一點需要注意,就是字符集問題,當項目字符集是Unicode字符集,那麼在C++利用wcout輸出命令行。當項目字符集是多字節字符集,那麼在C++利用cout輸出命令行。
    注意,不論通過以上兩種方式輸入的命令行參數都會在C/C++運行庫啓動函數中被初始化全局變量argc、__argv、__wargv。
    所以傳入_tmain函數的argv參數也是對應字符集編碼的字符串。例如:如果在Unicode下,argv數組內的元素就是寬字符串,如果在多字節字符集下,argv數組內的元素就是ANSI字符串。
    注意第一種方式和第二種方式在輸出上的區別,第一種輸出的第一個文件名字符串,這個字符串也包括路徑。而第二種輸出只有命令行參數,因爲就算沒有填寫命令行參數也會輸出文件名,那個文件名
    只是起到運行這個程序的象徵。
    */
    for (int i = 0; i < argc; i++)
    {
        //cout只能輸出ANSI字符和字符串,要想輸出寬字符可以使用wcout。
        wcout << argv[i] << endl;
    }
    system("pause");
    return 0;
}

(2)如果是運行CUI應用程序,在C/C++運行庫啓動函數執行時,會調用Windows函數GetCommandLine來獲取進程的完整命令行(文件名+命令行參數,其中文件名也就是絕對路徑)然後啓動函數進行忽略可執行文件的名稱,包括路徑,接着將指向命令行剩餘部分的一個指針傳給WinMain的pszCmdLine參數。下面給出函數的簽名:

LPTSTR WINAPI GetCommandLine(void);

下面舉個例子:
Windows核心編程之核心總結(第四章 進程(二))(2018.6.17)
可以看出cmdLine包含絕對路徑的文件名稱和命令行參數。而pszCmdLine參數只有命令行參數,因爲在啓動函數處理中已經忽略了文件名了。
(3)我們也可以利用CommandLinetoArgvW函數將GetCommandLineW函數獲取的完整命令行分解成單獨的標記。
該函數原型如下:

LPWSTR* CommandLinetoArgvW(LPCWSTR,int*);
參數1是指向一個命令行字符串,通常利用GetCommandLineW獲取。
參數2是獲取命令行實參的個數。
返回的字符串數組所使用的內存,用LocalFree來釋放!

以下是MSDN的示例代碼:是在CUI程序下CommandLinetoArgvW函數的使用

#include<windows.h>
#include<tchar.h>
#include<iostream>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
    LPWSTR *szArglist;//用於
    int nArgs;
    int i;
    /*
    CommandLineToArgvW函數只有Unicode版本的,所以參數1也必須使用Unicode版本的GetCommandLineW來獲取完整的命令行
    參數2是存儲完整命令行中一共有多少個命令行參數,包括文件名參數。
    CommandLineToArgvW函數返回的是一個Unicode字符串指針數組的地址。
    這個函數將參數1完整命令行分解成單獨的標記。
    */
    LPTSTR cmdLine;
    cmdLine = GetCommandLine();
    printf("%ws\n", cmdLine);//這個是輸出完整命令行
    szArglist = CommandLineToArgvW(GetCommandLineW(), &nArgs);
    if (NULL == szArglist)
    {
        wprintf(L"CommandLineToArgvW failed\n");
        return 0;
    }
    else for (i = 0; i<nArgs; i++) printf("%d: %ws\n", i, szArglist[i]);//這個是輸出分解後放到字符串數組中的內容
    LocalFree(szArglist);
    system("pause");
    return 0;
}

在CUI程序下,入口點函數的argv函數就已經分解好了命令行參數,其實這個函數更大的用處是在GUI程序中,例如下面代碼的使用:

#include<windows.h>
#include<tchar.h>
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE, PTSTR pszCmdLine, int nCmdShow)
{
    LPWSTR *szArglist;//用於
    int nArgs;
    LPTSTR cmdLine;
    cmdLine = GetCommandLineW();
    szArglist = CommandLineToArgvW(GetCommandLineW(), &nArgs);
    LocalFree(szArglist);
    return 0;
}

測試結果如下:
Windows核心編程之核心總結(第四章 進程(二))(2018.6.17)

進程的環境變量

在我們熟知的Windows系統裏,一直有環境變量這一說,我們都還知道環境變量可在Windows界面裏的高級系統設置裏的環境變量中獲取或設置。但其實,環境變量真正是存儲在註冊表裏的,每個Windows系統的註冊表編輯器都在C:\Windows\regedit.exe,我們知道在可視化界面下(高級系統設置中打開)有兩種環境變量,分別是系統變量和用戶變量。而系統變量和用戶變量分別在註冊表編輯器下的兩個路徑(系統變量路徑:HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Session Manager\Environment;用戶變量路徑:HKEY_CURRENT_USER\Environment)。下面放個註冊表編輯器的示意圖:
Windows核心編程之核心總結(第四章 進程(二))(2018.6.17)
好了,言歸正傳。其實每個進程被創建後都會有一個與它關聯的環境塊,也就是在進程地址空間內分配的一塊內存,內存塊包含的字符串大概長這樣:

=::=::\ ...
VarName1=VarValue1\0
VarName2=VarValue2\0
VarName3=VarValue3\0
VarNameX=VarValueX\0
\0

我們要注意的是等號左邊的VarName1、VarName2等都是環境變量的名稱,而等號右邊的VarValue1、VarValue2等都是環境變量的值。還有一個更重要的一點就是每行環境變量的賦值最後都有個‘\0’,這是字符串結束符,後邊GetEnvironmentStrings函數遍歷完整的環境變量字符串時有用。
我們有兩種方式來獲取完整的環境塊,第一種方式是調用GetEnvironmentStrings函數獲取完整的環境變量(還有GetEnvironmentVariable函數獲取的是單個指定環境變量名的值,下面會有使用案例)得到的完整環境塊的格式和前面描述的一樣;第二種方式是CUI程序專用的,就是通過入口函數所接收的TCHAR *envp[]參數來實現。不同於GetEnvironmentStrings返回的值,GetEnvironmentStrings返回的是完整的環境塊,而envp是一個字符串指針數組,每個指針都指向一個不同的環境變量(其定義採用常規的“名稱=值”的格式),在數組最後一個元素是一個NULL指針,代表這是數組的末尾,那麼我們就可以通過這個NULL指針作爲遍歷的終止處,我們需要注意的是以等號開頭的那些無效字符串在我們接收到envp之前就已經被移除了,所以不必進行處理只要獲取數組元素即可。
(1)下面先講GetEnvironmentStrings函數的使用案例:
這裏先放上等會要用到的兩個函數的函數簽名。
1.GetEnvironmentStrings函數用於獲取所有環境變量字符串:

LPTCH WINAPI GetEnvironmentStrings(void);
返回值:成功時,返回指向保存環境變量的緩衝區;失敗時,返回值爲NULL。

2.FreeEnvironmentStrings函數用來釋放由GetEnvironmentStrings返回的內存塊:

BOOL WINAPI FreeEnvironmentStrings(
  __in  LPTCH lpszEnvironmentBlock
);
返回值:成功時,返回非零值;失敗時,返回零值,可調用GetLastError()查看進一步錯誤消息。
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include<strsafe.h>
int _tmain()
{
    LPTSTR lpszVariable;
    LPTCH lpvEnv;//LPTCH就是WCHAR *數據類型,指向寬字符的指針變量
    size_t iTarget;
    //調用GetEnvironmentStrings函數獲取完整的環境變量內存塊,並讓lpvEnv指向這個內存塊
    lpvEnv = GetEnvironmentStrings();
    //如果獲取的環境塊爲空,則該函數調用失敗,並獲取錯誤代碼
    if (lpvEnv == NULL)
    {
        _tprintf(TEXT("GetEnvironmentStrings failed(%d)\n"), GetLastError());
        return 0;
    }
    //lpvEnv指向的環境變量字符串是以NULL分隔的,即'\0'分隔,可以回去看前面我展示的環境字符串的大概格式。而字符串最後是以NULL結尾的
    lpszVariable = (LPTSTR)lpvEnv;
    while (*lpszVariable)
    {
        _tprintf(TEXT("%s\n"), lpszVariable);
        StringCchLength(lpszVariable, 1000, &iTarget);//PATH的值太長,我設1000爲最大允許字符數
        lpszVariable += iTarget + 1;//移動指針,訪問下一環境變量的值
    }
    //如果GetEnvironmentStrings函數返回的內存塊不用了,記得要釋放掉
    FreeEnvironmentStrings(lpvEnv);
    system("pause");
    return 1;
}

運行結果如下:
Windows核心編程之核心總結(第四章 進程(二))(2018.6.17)
(2)下面是GetEnvironmentVariable函數的使用案例:
這裏先放上GetEnvironmentVariable函數簽名。
1.GetEnvironmentVariable函數用於獲取指定的環境變量:

DWORD WINAPI GetEnvironmentVariable(
  __in_opt   LPCTSTR lpName, //環境變量名
  __out_opt  LPTSTR lpBuffer, //指向保存環境變量值的緩衝區
  __in       DWORD nSize //緩衝區大小(字符數)
);
返回值:成功時,返回真實的環境變量值大小,不包括null結束符;如果lpBuffer大小不足,則返回值是實際所需的字符數大小,lpBuffer內容就未被賦值;失敗時,返回0;如果指定的環境變量找不到,GetLastError()返回ERROR_ENVVAR_NOT_FOUND。
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include<strsafe.h>
int _tmain()
{
    TCHAR szBuffer[1000];
    DWORD dwResult = GetEnvironmentVariable(TEXT("PATH"), szBuffer, 1000);
    if (dwResult != 0)
    {
        _tprintf(TEXT("PATH=%s"), szBuffer);
    }
    else
    {
        _tprintf(TEXT("function call falid!"));
    }
    system("pause");
    return 1;
}

運行結果如下:
Windows核心編程之核心總結(第四章 進程(二))(2018.6.17)
(2)下面是SetEnvironmentVariable函數的使用案例:
這裏先放上SetEnvironmentVariable函數簽名,後面使用案例有幾個注意點需要重視。
1.SetEnvironmentVariable函數用於設置指定的環境變量:

BOOL WINAPI SetEnvironmentVariable(
  __in      LPCTSTR lpName, //環境變量名,當該值不存在且lpValue不爲NULL時,將創建一個新的環境變量
  __in_opt  LPCTSTR lpValue //環境變量值
);
返回值:
成功時,返回非零值;
失敗時,返回零值,調用GetLastError()查看具體的錯誤信息。
該函數對系統環境變量以及其他進程的環境變量不起作用!

在寫測試程序前,我先在我的電腦->屬性->高級系統設置->環境變量->用戶變量處添加一個自定義的環境變量MyPath,環境變量值爲woaini。呃。。。值不是重點,大概長下面那樣。
Windows核心編程之核心總結(第四章 進程(二))(2018.6.17)
好了,準備工作做好了,現在重點要關閉VS,重新開VS再運行測試代碼(先思考爲什麼,如果不重開VS會有什麼現象,後面講注意點有解釋重開VS的原因),現在放測試代碼:

#include <windows.h>
#include <tchar.h>
#include <stdio.h>
int _tmain(int argc,TCHAR *argv[],TCHAR *envp[])
{
    TCHAR szBuffer[1000];//用於存儲獲取的環境變量的值
    DWORD dwResult1 = GetEnvironmentVariable(TEXT("MyPath"), szBuffer, 1000);//先獲取我們前面已經設置好的MyPath環境變量的值,如果沒錯應該是woaini,但如果你測試時獲取不到,該函數返回0,那麼就要看看後面我講的注意點了哦。
    if (dwResult1 != 0)
    {
        _tprintf(TEXT("MyPath=%s\n"), szBuffer);
    }
    else
    {
        _tprintf(TEXT("function call falid!\n"));
    }
    SetEnvironmentVariable(TEXT("MyPath"), TEXT("I love you"));//這裏爲我新建的MyPath環境變量重新修改值爲I love you,注意,其實這只是修改當前進程的環境塊,而未影響系統或用戶的環境塊
    DWORD dwResult2 = GetEnvironmentVariable(TEXT("MyPath"), szBuffer, 1000);//這裏重新獲取以下修改後的MyPath環境變量的值
    if (dwResult2 != 0)
    {
        _tprintf(TEXT("MyPath=%s\n"), szBuffer);
    }
    else
    {
        _tprintf(TEXT("function call falid!\n"));
    }
    system("pause");
    return 0;
}

如果執行步驟沒錯,那麼運行結果是下面這樣的:
Windows核心編程之核心總結(第四章 進程(二))(2018.6.17)
好了,注意點有以下幾點:
1.爲什麼要重開VS,GetEnvironmentVariable函數才能正確獲取前面我們新建的環境變量MyPath?這是因爲我們之前講過每個進程在創建時就被分配了一個環境塊,而這個環境塊就是Windows系統賦予的,那麼我們可以猜測,當運行VS,就已經在內部存好了我們將要分配的環境塊內容,而我們是VS運行後再新建環境變量MyPath,那麼VS保存的這塊內容還沒更新呢,所以函數當然獲取不到,我們只能重開VS了。這也只是我的猜測,是爲了更好理解GetEnvironmentVariable函數,如有其他看法的,可以留言探究哦。
2.GetEnvironmentVariable函數對系統環境變量以及其他進程的環境變量不起作用,因爲創建了一個進程,就已經爲進程分配好環境塊了,我們通過GetEnvironmentVariable函數添加、修改或刪除環境塊內容,也只是添加、修改或刪除進程的環境塊,而非Windows系統或用戶的環境塊。

(3)下面是CUI程序入口函數TCHAR *envp[]參數的使用案例:
這裏就不自己寫代碼了,直接放上書本P76頁的示例代碼(修改過)。

#include <windows.h>
#include <tchar.h>
#include <stdio.h>
int _tmain(int argc,TCHAR *argv[],TCHAR *envp[])
{
    int current = 0;//用於環境變量計數
    PTSTR *pElement = (PTSTR *)envp;//創建新的指針指向CUI程序的envp數組
    PTSTR pCurrent = NULL;//用於遍歷envp數組元素的指針
    while (pElement != NULL)
    {
        //取數組的元素
        pCurrent = (PTSTR)(*pElement);
        //前面說過數組末尾是NULL指針,所以當遍歷到NULL則將pElement置NULL,接着就跳出循環了
        if (pCurrent == NULL)
        {
            pElement = NULL;
        }
        else
        {
            //打印遍歷到的環境變量
            _tprintf(TEXT("[%u] %s\r\n"), current, pCurrent);
            current++;//計數+1
            pElement++;//指向下一個數組元素
        }
    }
    system("pause");
    return 0;
}

運行結果如下:
Windows核心編程之核心總結(第四章 進程(二))(2018.6.17)
(4)下面是ExpandEnvironmentStrings函數的使用案例:
通過前面註冊表的瞭解,我們可以細心發現,有些環境變量的值含有兩個百分號(%)之間的字符串,這種字符串叫做可替換字符串,顧名思義,我們可以通過函數ExpandEnvironmentStrings函數替換掉可替換字符串。也可以發現,這種可替換字符串只有在註冊表才能看到,而在我的電腦->屬性->高級系統設置->環境變量或通過其他方式獲取整個完整的環境變量都看不到可替換字符串這種形式。下面,我先放上ExpandEnvirnmentStrings函數的函數簽名:

DWORD WINAPI ExpandEnvironmentStrings(
  _In_      LPCTSTR lpSrc,
  _Out_opt_ LPTSTR  lpDst,
  _In_      DWORD   nSize
);
參數1:一個包含可替換字符串的字符串地址(也叫擴展字符串),例如:TEXT("PATH=%PATH%")
參數2:用於接收擴展字符串的一個緩衝區的地址
參數3:這個緩衝區的最大大小,用字符數來表示。
返回值:保存擴展字符串所需的緩衝區的大小,用字符數表示,若參數3小於這個返回值,%%變量就不會擴展,而是被替換爲空字符串,所以一般要調用兩次ExpandEnvironmentStrings函數。
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
int _tmain(int argc,TCHAR *argv[],TCHAR *envp[])
{
    //第一次調用ExpandEnvironmentStrings是爲了獲取保存擴展字符串所需的緩衝區大小,所以函數參數2可以爲NULL,參數3爲0
    DWORD chValue = ExpandEnvironmentStrings(TEXT("USERPROFILE='%USERPROFILE%'"), NULL, 0);
    PTSTR pszBuffer = new TCHAR[chValue];//動態創建chValue大小的緩衝區,最後記得釋放掉動態創建的空間
    chValue = ExpandEnvironmentStrings(TEXT("USERPROFILE='%USERPROFILE%'"), pszBuffer, chValue);//這次調用纔是真正獲取替換後的字符串
    _tprintf(TEXT("%s\r\n%d"), pszBuffer,chValue);//打印擴展字符串的緩衝區和字符數目
    delete[]pszBuffer;//釋放動態創建的空間
    system("pause");
    return 0;
}

運行結果如下:
Windows核心編程之核心總結(第四章 進程(二))(2018.6.17)

進程的當前驅動器和目錄

有一些Windows函數的調用需要提供路徑,例如:CreateFile函數打開一個文件(未指定完整路徑名,只有一個文件名),那麼該函數就會在當前驅動器(例如:C、D、E磁盤)的當前目錄查找文件和目錄。系統在內部跟蹤記錄着一個進程的當前驅動器和目錄,我們可以獲取進程的當前驅動器和目錄,也可以修改進程的當前驅動器和目錄。
下面給出分別獲取和設置當前驅動器和目錄的函數簽名:
1.GetCurrentDirectory函數獲取進程當前目錄:

DWORD WINAPI GetCurrentDirectory(
  _In_  DWORD  nBufferLength,
  _Out_ LPTSTR lpBuffer
);
nBufferLength:lpBuffer指針指向內存塊的大小(單位TCHAR);
lpBuffer:接收當前路徑的內存塊。

2.SetCurrentDirectory函數設置進程當前目錄

BOOL WINAPI SetCurrentDirectory(
  _In_ LPCTSTR lpPathName
);
lpPathName:需要被設置的目錄路徑

3.GetFullPathName函數獲取指定文件的當前路徑:

DWORD WINAPI GetFullPathName(
  __in   LPCTSTR lpFileName,
  __in   DWORD nBufferLength,
  __out  LPTSTR lpBuffer,
  __out  LPTSTR *lpFilePart
);
lpFileName:文件名
nBufferLength:獲取全路徑的內存大小(TCHAR)
lpBuffer:內存指針
lpFilePart:文件名最後一個元素,在lpBuffer中的位置。
注意:這個函數,只是將當前路徑,粘貼到你給的文件上,其他什麼也沒有做。

下面,我給出使用案例來領會這些函數的使用:

#include <windows.h>
#include <tchar.h>
#include <stdio.h>
int _tmain(int argc,TCHAR *argv[],TCHAR *envp[])
{
    TCHAR szPath[MAX_PATH];
    GetCurrentDirectory(MAX_PATH, szPath);//獲取進程當前路徑  
    _tprintf(L"%s\n", szPath);
    TCHAR *str = L"D:\\360Downloads\\";//設置的當前路徑  
    SetCurrentDirectory(str);   //設置文件的當前路徑,如果指定的str參數在電腦中存在這個路徑,那麼就設置成功,否則設置無效,還是採用前一個有效的當前進程目錄
    GetCurrentDirectory(MAX_PATH, szPath);
    _tprintf(L"%s\n", szPath);
    TCHAR *str1 = L"D:\\ddsdf\\";//設置的當前路徑  
    SetCurrentDirectory(str1);   //設置文件的當前路徑,如果指定的str參數在電腦中存在這個路徑,那麼就設置成功,否則設置無效,還是採用前一個有效的當前進程目錄
    GetCurrentDirectory(MAX_PATH, szPath);
    _tprintf(L"%s\n", szPath);//因爲"D:\\ddsdf\\"路徑在我電腦裏不存在,所以SetCurrentDirectory函數設置失敗了
    GetFullPathName(L"wxf1", MAX_PATH, szPath, NULL);
    //這個函數只是將進程當前路徑(szPath)粘貼到你給的文件名(wxf1)上,其他什麼也沒有做,不做檢查  
    _tprintf(L"%s\n", szPath);
    system("pause");
    return 0;
}

運行結果如下:
Windows核心編程之核心總結(第四章 進程(二))(2018.6.17)
通過上面的測試,我們可以得出以下幾點:
1.GetCurrentDirectory函數是獲取進程的當前目錄,而函數參數1一般用MAX_PATH(宏定義爲260)就很安全了,因爲這是目錄名稱或文件名稱得最大字符數了。
2.SetCurrentDirectory函數設置進程的當前目錄,而如果該函數參數指定的路徑在本電腦中不存在,那麼就設置無效,還是採用前一個有效的當前進程目錄。
3.GetFullPathName函數只是將進程當前路徑(szPath)粘貼到你給的文件名(wxf1)上,其他什麼也沒有做,不做檢查 。
4.爲了更好理解GetCurrentDirectory函數獲取進程的當前目錄這一功能,你可以將上面代碼生成的可執行文件放到桌面,再運行,那麼進程的當前目錄就改變啦

判斷系統版本

在實際應用中,我們所開發的應用程序需要判斷用戶所運行的Windows系統的版本。簡單來說就是在應用程序運行前先判斷系統版本,若用戶系統不支持我們的應用程序則提示用戶系統版本不支持,接着退出程序。Windows提供了兩個判斷系統版本的函數,分別是GetVersion函數和GetVersionEx函數,由於GetVersion函數在獲取的版本信息,其中Windows版本號的順序顛倒了。明知有錯,那麼我們就只用GetVersionEx函數好了,而且這個函數更好使用。我們還可以通過VerifyVersionInfo函數確認當前系統版本是否滿足應用程序版本需求。
現在放上GetVersionEx函數的函數簽名:
1.GetVersionEx函數獲取當前系統版本信息

BOOL WINAPI GetVersionEx(
  __inout  LPOSVERSIONINFO lpVersionInfo
);
LpVersionInfo:這個參數執行OSVERSIONINFO或者OSVERSIONINFOEX這個結構體。
OSVERSIONIINFOEX結構體是OSVERSIONINFO結構體的擴展,所以這個函數可以傳遞這兩個參數的任意一個,前提條件是,必須設置結構體的大小,讓GetVersionEx函數能夠知道你傳遞的是那個結構體。

2.OSVERSIONINFOEX結構體

typedef struct _OSVERSIONINFOEX {
  DWORD dwOSVersionInfoSize;//結構體大小
  DWORD dwMajorVersion;//主機系統的主版本號
  DWORD dwMinorVersion;//主機系統的次版本號
  DWORD dwBuildNumber;//當前系統的構建版本號
  DWORD dwPlatformId;//平臺ID
  TCHAR szCSDVersion[128];//額外的文本
  WORD  wServicePackMajor;//ServicePack的主版本號
  WORD  wServicePackMinor;//ServicePack的次版本號
  WORD  wSuiteMask;//suite掩碼
  BYTE  wProductType;//產品類型
  BYTE  wReserved;//保留字段
} OSVERSIONINFOEX, *POSVERSIONINFOEX, *LPOSVERSIONINFOEX;

3.OSVERSIONINFO結構體

typedef struct _OSVERSIONINFO {
  DWORD dwOSVersionInfoSize;//結構體大小
  DWORD dwMajorVersion;//主機系統的主版本號
  DWORD dwMinorVersion;//主機系統的次版本號
  DWORD dwBuildNumber;//當前系統的構建版本號
  DWORD dwPlatformId;//平臺ID
  TCHAR szCSDVersion[128];//額外的文本
} OSVERSIONINFO;

下面對GetVersionEx和OSVERSIONINFO結構體的測試案例:

#include <windows.h>
#include <stdio.h>
void main()
{
    //OSVERSIONINFOEX osvi;//如果設置的結構體是OSVERSIONINFOEX,那麼下面設置結構體大小要修改爲sizeof(OSVERSIONINFOEX),還有ZeroMemory的函數參數也要相應修改
    OSVERSIONINFO osvi;
    BOOL bIsWindowsXPorLater;
        //ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
    ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
         //osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    //GetVersionEx((LPOSVERSIONINFO)&osvi);
    GetVersionEx(&osvi);
    bIsWindowsXPorLater = 
       ( (osvi.dwMajorVersion > 5) ||
       ( (osvi.dwMajorVersion == 5) && (osvi.dwMinorVersion >= 1) ));
    if(bIsWindowsXPorLater)
        printf("The system meets the requirements.\n");
    else 
        printf("The system does not meet the requirements.\n");
    system("pause");
}

至於所有系統版本的信息,我們可以通過在MSDN上,搜索OSVERSIONINFOEX結構體下方有系統版本號總結,官網鏈接爲:https://msdn.microsoft.com/en-us/library/windows/desktop/ms724832(v=vs.85).aspx
就像下面的那樣:
Windows核心編程之核心總結(第四章 進程(二))(2018.6.17)
需要注意的是:在windows8.1或windows10上的應用程序通過函數GetVersionEx獲取的版本信息,沒有對應windows8.1或windows10的版本信息,那麼將返回windows8的版本值(6.2)。
4.VerifyVersionInfo函數測試主機系統的版本符不符合應用程序要求的版本
VerifyVersionInfo函數的函數簽名:

BOOL WINAPI VerifyVersionInfo(
  __in  LPOSVERSIONINFOEX lpVersionInfo,
  __in  DWORD dwTypeMask,
  __in  DWORDLONG dwlConditionMask
);
參數1是應用程序自己定義的版本要求
參數2是應用程序的比較測試項
參數3是測試條件,這個測試條件是DWORDLONG類型的數據,由VER_SET_CONDITION宏來爲這個DWORDLONG類型的數據添加測試條件

5.VER_SET_CONDITION宏是爲了設置測試條件,例如比較什麼,怎麼比較。每比較一個成員就要調用一次VER_SET_CONDITION宏。

ULONGLONG VER_SET_CONDITION(
   ULONGLONG dwlConditionMask,
   DWORD     dwTypeBitMask,
   BYTE      dwConditionMask
);
參數1是測試條件,DWORDLONG類型的變量,DWORDLONG類型是DWORDLONG類型的重命名。
參數2是主機系統的比較測試項
參數3是比較方式

下面對VerifyVersionInfo函數的測試案例:

#include <windows.h>
#include <tchar.h>  
#pragma warning(disable: 4996)
int _tmain(int argc, TCHAR *argv[])
{
    //創建等會需要比較的版本信息,本應用程序要求的版本信息
    OSVERSIONINFOEX infoEx;
    ZeroMemory(&infoEx, sizeof(OSVERSIONINFOEX));//清零
    infoEx.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);//設置結構體大小
    infoEx.dwMajorVersion = 6;//設置待會比較的主版本信息
    infoEx.dwMinorVersion = 2;//設置待會比較的次版本信息
    infoEx.wServicePackMajor = 0;
    infoEx.wServicePackMinor = 0;
    DWORDLONG dwlConditionMask = 0;//
    VER_SET_CONDITION(dwlConditionMask, VER_MAJORVERSION, VER_GREATER_EQUAL);//用等號的方式比較VER_MAJORVERSION,並放入dwlConditionMask中,下面類似
    VER_SET_CONDITION(dwlConditionMask, VER_MINORVERSION, VER_GREATER_EQUAL);
    VER_SET_CONDITION(dwlConditionMask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
    VER_SET_CONDITION(dwlConditionMask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL);
    DWORD dwTypeMaask = VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR;
    if (VerifyVersionInfo(&infoEx, dwTypeMaask, dwlConditionMask))
    {
        _tprintf(L"this system is sufficient!\n");
    }
    else
    {
        if (ERROR_OLD_WIN_VERSION == GetLastError())
        {
            _tprintf(L"this system is old system!\n");
        }
        else
        {
            _tprintf(L"this function is error!\n");
        }
    }
    system("pause");
    return 0;
}

在我Win10的系統下運行此程序,運行結果如下,很明顯我的主機系統符合應用程序的要求的系統版本:
Windows核心編程之核心總結(第四章 進程(二))(2018.6.17)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章