Windows核心编程之核心总结(第四章 进程(二))(2018.6.17)

学习目标

上一节我们了解了进程、入口函数和进程实例句柄等内容,在进入进程的命令行学习前,有一个全局变量初始化问题需要测试一波。本节的学习目标如下:
1.测试C/C++运行库启动函数初始化哪些全局变量
2.进程的命令行
3.进程的环境变量
4.进程的当前驱动器和目录
5.判断系统版本
6.创建进程(CreateProcess函数详解)

测试启动函数初始化哪些全局变量

我们知道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函数测试情况相同。

2.可以看出,下面的测试结果和在main函数测试情况也相同。

(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);

下面举个例子:

可以看出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系统里,一直有环境变量这一说,我们都还知道环境变量可在Windows界面里的高级系统设置里的环境变量中获取或设置。但其实,环境变量真正是存储在注册表里的,每个Windows系统的注册表编辑器都在C:\Windows\regedit.exe,我们知道在可视化界面下(高级系统设置中打开)有两种环境变量,分别是系统变量和用户变量。而系统变量和用户变量分别在注册表编辑器下的两个路径(系统变量路径:HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Session Manager\Environment;用户变量路径:HKEY_CURRENT_USER\Environment)。下面放个注册表编辑器的示意图:

好了,言归正传。其实每个进程被创建后都会有一个与它关联的环境块,也就是在进程地址空间内分配的一块内存,内存块包含的字符串大概长这样:

=::=::\ ...
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;
}

运行结果如下:

(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;
}

运行结果如下:

(2)下面是SetEnvironmentVariable函数的使用案例:
这里先放上SetEnvironmentVariable函数签名,后面使用案例有几个注意点需要重视。
1.SetEnvironmentVariable函数用于设置指定的环境变量:

BOOL WINAPI SetEnvironmentVariable(
  __in      LPCTSTR lpName, //环境变量名,当该值不存在且lpValue不为NULL时,将创建一个新的环境变量
  __in_opt  LPCTSTR lpValue //环境变量值
);
返回值:
成功时,返回非零值;
失败时,返回零值,调用GetLastError()查看具体的错误信息。
该函数对系统环境变量以及其他进程的环境变量不起作用!

在写测试程序前,我先在我的电脑->属性->高级系统设置->环境变量->用户变量处添加一个自定义的环境变量MyPath,环境变量值为woaini。呃。。。值不是重点,大概长下面那样。

好了,准备工作做好了,现在重点要关闭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;
}

如果执行步骤没错,那么运行结果是下面这样的:

好了,注意点有以下几点:
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;
}

运行结果如下:

(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函数的调用需要提供路径,例如: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;
}

运行结果如下:

通过上面的测试,我们可以得出以下几点:
1.GetCurrentDirectory函数是获取进程的当前目录,而函数参数1一般用MAX_PATH(宏定义为260)就很安全了,因为这是目录名称或文件名称得最大字符数了。
2.SetCurrentDirectory函数设置进程的当前目录,而如果该函数参数指定的路径在本电脑中不存在,那么就设置无效,还是采用前一个有效的当前进程目录。
3.GetFullPathName函数只是将进程当前路径(szPath)粘贴到你给的文件名(wxf1)上,其他什么也没有做,不做检查 。
4.为了更好理解GetCurrentDirectory函数获取进程的当前目录这一功能,你可以将上面代码生成的可执行文件放到桌面,再运行,那么进程的当前目录就改变啦

判断系统版本

......明天补充

原文地址:http://blog.51cto.com/12731497/2130213

时间: 2024-10-30 07:07:52

Windows核心编程之核心总结(第四章 进程(二))(2018.6.17)的相关文章

Windows核心编程之核心总结(第四章 进程(一))(2018.6.8)

学习目标 第四章进程的学习可谓是任重而道远,虽然不难,但知识量很多,也比较零散,需要多总结,脑海里才有进程的框架.所以,我把本章分为几个小节来讲完.我还是一如既往的添加辅助性内容,希望对于小白有所帮助.而比我流弊的大有人在,大神们可以跳过辅助性内容.本小节的学习目标如下:1.C/C++程序编译过程2.C/C++命令行参数的使用3.什么是进程4.Windows的入口点函数5.进程实例句柄(可执行文件实例句柄或者DLL文件实例句柄) C/C++程序编译过程 C/C++的编译.链接过程要把我们编写的一

Python核心编程(第二版) 第二章习题答案 未完待续

2-2.程序输出.阅读下面的Python脚本.#!/usr/bin/env python1 + 2 * 4(a)你认为这段脚本是用来做什么的?(b)你认为这段脚本会输出什么?(c)输入以上代码,并保存为脚本,然后运行它,它所做的与你的预期一样吗?为什么一样/不一样?(d)这段代码单独执行和在交互解释器中执行有何不同?试一下,然后写出结果.(e)如何改进这个脚本,以便它能和你想象的一样工作?答:(a)这段脚本是用来计算表达式的值(b)脚本会输出9(c)保存为脚本,运行后没有输出.和自己预期不一样.

《Python核心编程》第二版第五章答案

5-1.整型.讲讲Python普通整型和长整型的区别. Python的标准整形类型是最通用的数字类型.在大多数32位机器上,标准整形类型的取值范围是-2**32-2**32 - 1. Python的长整型类型能表达的数值仅仅与你的机器支持的(虚拟)内存大小有关,换句话说,Python能轻松表达很大的整数. 长整型类型是标准整形类型的超集,当程序需要使用比标准整形更大的整型时,可以使用长整型类型,在整型值后面添加L,表示这个为长整型,这两种整形类型正在逐渐统一为 一种. 5-2.操作符.(a)写一

《Unix环境高级编程》读书笔记 第7章-进程环境

1. main函数 int main( int argc, char *argv[] ); argc是命令行参数的数目,包括程序名在内 argv是指向参数的各个指针所构成的数组,即指针数组 当内核执行C程序时(使用exec函数),在调用main前先调用一个特殊的启动例程.可执行程序文件将此启动例程指定为程序的起始地址——这是由连接器设置的,而连接器则是由C编译器调用.启动例程从内核取得命令行参数和环境变量值,然后按上述方式调用main函数做好安排. 2. 进程终止 有8种方式使进程终止,其中5种

Windows核心编程之核心总结(第一章 错误处理)(2018.5.26)

前沿 学习Windows核心编程是步入Windows编程殿堂的必经之路,2018年寒假重温了计算机操作系统知识,前阵子又过学习Windows程序设计方面的基础,正所谓打铁要乘热,所以我又入了Windows核心编程的坑啦,哈哈~ 学习目标 每一章的学习都要明确一个目标,就是你学完这一章之后你能做些什么?好的,我们一步步来学习第一章节错误处理.以下是这一章节的学习目标:1.了解Windows函数的错误机制2.了解GetLastError和SetLastError函数的使用3.了解FormatMess

Windows核心编程之核心总结(第三章 内核对象)(2018.6.2)

学习目标 第三章内核对象的概念较为抽象,理解起来着实不易,我不断上网找资料和看视频,才基本理解了内核对象的概念和特性,其实整本书给我的感觉就是完整代码太少了,没有多少实践的代码对内容的实现,而且书本给的源码例子,有太多我们不知道的知识,并且这些知识对本章主要内容来说是多余的,所以我们理解起来也非常困难.为了更好的学习这章,我补充了一些辅助性内容.这一章的学习目标:1.Windows会话和安全机制2.什么是内核对象?3.使用计数和安全描述符4.内核对象句柄表5.创建内核对象6.关闭内核对象7.跨进

Windows核心编程读书笔记-第四章进程

1.进程组成 一个内核对象,操作系统用它来管理进程. 一个地址空间,其中包含所有可执行文件或DLL模块的代码和数据.此外,它还包含动态内存分配,比如线程堆栈和堆的分配. 2.一个进程可以有多个线程,所有线程都在进程的地址空间中"同时"执行代码.每个进程至少要有一个线程来执行进程地址空间包含的代码. 3.用Microsoft Visual Studio来创建一个应用程序项目时,集开发环境会设置各种链接器开关,使链接器将子系统的正确类型嵌入最终生成的可执行文件.对于CUI程序,这个链接器开

Windows核心编程之核心总结(第四章 进程(三))(2018.6.21)

学习目标 本章节将学习以后经常用到的CreateProcess函数,听网上的人说有些面试官喜欢问这个函数的大概功能和参数作用哦,可见这个函数是十分重要滴,那我们来详细了解和测试这个函数的功能吧,有些不足的以后有实际经验再来修改和补充.说实话,我现在也只是一名大学生,到了实际开发也许才会用到这本书的内容,但我现在是作为兴趣来学这本书的,因为这本书给我的feel就是自己掌控Windows系统,这感觉太棒了.不管以后用不用的到,我觉得对我的帮助都很大.好了,闲话说到这吧,现在本章节的学习目标如下:1.

Windows核心编程之核心总结(第二章 字符和字符串处理)(2018.5.27)

学习目标 第二章是学习字符和字符串处理,为了更好理解这一章的内容,我自行添加了其他辅助性内容:存储模式(大端存储和小端存储).字符编码方案(一看就懂).以下是这一章的学习目标:1.大端存储和小端存储2.字符编码方案3.ANSI和Unicode字符.字符串,Windows自定义数据类型(为了兼容ANSI和Unicode)4.Windows的ANSI函数和Unicode函数5.C运行库的ANSI和Unicode函数6.C运行库的安全字符串函数7.C运行库的安全字符串函数(进阶版)8.字符串比较函数9