4.1 Windows两种类型的程序:
CUI程序,比如CMD.EXE等等。Microsoft Visual C++连接开关为/SUBSYSTEM:CONDOLE(程序启动时不能创建GUI程序)。
GUI程序,图形用户程序,比如Notepad,Word等等。Microsoft Visual C++连接开关为/SUBSYSTEM:WINDOWS(程序启动时不能创建CUI程序)。
注意:俩者的概念其实是很模糊的,CUI可以创建GUI图形界面,反之GUI程序可能用CUI程序 。
Windows入口点函数(区分在于CUI和GUI程序,ANSI码和UNICODE码)
int WINAPI WinMain(
HINSTANCE hinstExe,
HINSTANCE,
PSTR pszCmdLine,
int nCmdShow);
int WINAPI wWinMain(
HINSTANCE hinstExe,
HINSTANCE,
PWSTR pszCmdLine,
int nCmdShow);
int __cdecl main(
int argc,
char*argv[],
char*envp[]);
int __cdecl wmain(
int argc,
wchar_t*argv[],
wchar_t*envp[]);
其实Windows程序启动时最开始并不调用自己写的入口函数,而是调用系统的几个入口函数,以便可以调用malloc和free之类的函数,初始化全局和静态C++对象等。
应用程序类型 进入点 嵌入可执行文件的启动函数
ANSI码GUI应用程序 WinMain WinMainCRTStattup
UNICODE码GUI应用程序 wWinMain wWinMainCRTStattup
ANSI码CUI应用程序 main mainCRTStattup
UNICODE码CUI应用程序 wmain wmainCRTStattup
注意:应用程序会根据SUBSYSTEM开关来查找嵌入可执行启动函数,如果进入点函数和启动函数不匹配则显示链接错误。可以删除SUBSYSTEM(VS Project Settings)开关,这样应用程序会自动需找匹配的函数。
所有的C\C++运行库启动函数所做的事情基本都是一样的,区别在于它们要处理的是ANSI还是Unicode,以及在初始化运行库之后,他们调用的是哪一个入口点函数。
可在crtexe.c文件中找到4个启动函数的源代码,这些启动函数的用途简单总结如下;- 获取指向新进程的完整命令行的一个指针。
- 获取指向新进程的环境变量的一个指针。
- 对C/C++运行期的全局变量进行初始化。如果包含StdLib.h文件,代码就可以访问这些变量。
- 对C运行期的malloc和callo和其他底层输入/输出例程使用的内存栈进行初始化。
- 为所有全局和静态C++类对象调用构造函数。
入口点函数返回时调用系统的exit函数,将返回值传递给它。exit函数负责下面操作:
- 调用由_onexit函数的调用而注册的任何函数。
- 为所有全局的和静态的C++类对象调用析构函数
- 在DEBUG生成中,如果设置了_CRTDBG_LEAK_CHECK_DK标志,就通过调用_CrtDumpMemoryLeaks函数来生成内存泄漏报告
- 调用操作系统的ExitProcess函数,并将返回值传递给他,关闭进程,并设置它的退出代码
int WINAPI WinMain(
HINSTANCE hinstExe,HINSTANCE hPrevInstance,PSTR pszCmdLine,int nCmdShow);
int nNumArgs;
PWSTR *ppArgv =CommandLineToArgvW(GetCommandLineW(),&nNumArgs);
if(*ppArgv[1]==L'x'){...}
HeapFree(GetProcessHeap(),0,ppArgv);//Free the memory block
VarName2=VarVarlue2\0
VarName3=VarVarlue3\0 ...
VarNameX=VarVarlueX\0
\0
注意:
- ‘=’号不能是变量名的一部分。
- 等号左右两边的空格将被算做名称或者值。
- 最后必须加个’\0’表示结束。
- 子进程和父进程不共用环境块,修改不会影响父/子进程。
voidDumpEnvStrings(){
PTSTR pEnvBlock =GetEnvironmentStrings();
// 解析块格式如下:
// =::=::\
// =...
// var=value\0
// ...
// var=value\0\0
// 请注意,其他一些字符串的开始处可能会是这样的 '='.
// 下面是当应用程序是从网络共享开始的一个例子.
// [0] =::=::\
// [1] =C:=C:\Windows\System32
// [2] =ExitCode=00000000
//
TCHAR szName[MAX_PATH];
TCHAR szValue[MAX_PATH];
PTSTR pszCurrent = pEnvBlock;
HRESULT hr = S_OK;
PCTSTR pszPos = NULL;
int current =0;
while(pszCurrent != NULL){
// 跳过毫无意义的字符串:
// "=::=::\"
if(*pszCurrent != TEXT('=')){
// 查询 '=' 分离器.
// 现在点的值的第一个字符.
pszPos++;
// 复制的变量名.
size_t cbNameLength =// 没有' ='
(size_t)pszPos -(size_t)pszCurrent -sizeof(TCHAR);
// 计算出字符串的长度(以字节为单位)
hr =StringCbCopyN(szName, MAX_PATH, pszCurrent, cbNameLength);
if(FAILED(hr)){// 失败了则直接退出
break;
}
// 复制到最后值要与空字符结尾
// 当长度大于缓冲区时允许使用StringCchCopyN函数截断。
hr =StringCchCopyN(szValue, MAX_PATH, pszPos, _tcslen(pszPos)+1);
if(SUCCEEDED(hr)){// 成功了 则执行下面语句。
_tprintf(TEXT("[%u] %s=%s\r\n"), current, szName, szValue);
}else// 当发生错误时,检查截断.
if(hr == STRSAFE_E_INSUFFICIENT_BUFFER){
_tprintf(TEXT("[%u] %s=%s...\r\n"), current, szName, szValue);
}else{// 这个应该永远不会发生.
_tprintf(
TEXT("[%u] %s=???\r\n"), current, szName
);
break;
}
}else{
_tprintf(TEXT("[%u] %s\r\n"), current, pszCurrent);
}
// 下一个变量.
current++;
// 移动到字符串的末尾.
while(*pszCurrent != TEXT('\0'))
pszCurrent++;
pszCurrent++;
// 检查它是否是最后一个字符串.
if(*pszCurrent == TEXT('\0'))
break;
};
// 不要忘记释放内存.
FreeEnvironmentStrings(pEnvBlock);
}
voidDumpEnvVariables(PTSTR pEnvBlock[]){
int current =0;
PTSTR* pElement =(PTSTR*)pEnvBlock;
PTSTR pCurrent = NULL;
while(pElement != NULL){// 当到达NULL则表明已经到达指针数组末尾了。
pCurrent =(PTSTR)(*pElement);
if(pCurrent == NULL){
//没有更多的环境变量了.
pElement = NULL;
}else{
_tprintf(TEXT("[%u] %s\r\n"), current, pCurrent);
current++;
pElement++;
}
}
}
另外父进程和子进程之间若有继承关系的话... 那么环境变量也可以继承,但他们是相互独立而不是共享使用的。
DWORD GetEnvironmentVariable(
PCTSTR pszName,// 指向预期变量名称。
PTSTR pszValue,// 指向保存变量值的缓冲区
DWORD cchValue // 缓冲区大小
);
函数需要调用两次,第一次向cchValue参数传入0,以返回所需字符数量,其中也包括了结束符NULL。
voidPrintEnvironmentVariable(PCTSTR pszVariableName){
PTSTR pszValue = NULL;
// 获取足够的,所需要的存储缓冲区的大小值
// 第一次调用 GetEnvironmentVariable 函数,最后一个参数传入0以获取足够缓冲区大小值。
DWORD dwResult =GetEnvironmentVariable(pszVariableName, pszValue,0);
if(dwResult !=0){
// 分配好缓冲区以存储环境变量的值
DWORD size = dwResult *sizeof(TCHAR);
pszValue =(PTSTR)malloc(size);
// 给变量分配好一个符号要求的内存空间
// 第2次调用 GetEnvironmentVariable 函数 并获取环境变量。
GetEnvironmentVariable(pszVariableName, pszValue, size);
_tprintf(TEXT("%s=%s\n"), pszVariableName, pszValue);
free(pszValue);
}else{
// 获取失败的提示语句
_tprintf(TEXT("'%s'=<unknown value>\n"), pszVariableName);
}
}
DWORD ExpandEnvironmentStrings(
PTCSTR pszSrc,// 参数是 "可替换环境变量的字符串"的一个字符串地址
PTSTR pszDst,// 用于接收扩展字符串的一个缓冲区地址
DWORD chSize // 缓冲区所需的大小
);
DWORD chValue =// 第一次调用 ExpandEnvironmentStrings 函数..其中最后参数为0自动获取大小。
ExpandEnvironmentStrings(TEXT("PATH='%PATH%'"), NULL,0);
// 分配内存空间
PTSTR pszBuffer =new TCHAR[chValue];
// 第二次调用 ExpandEnvironmentStrings 函数.并且获取到可替换的字符串内容。
chValue =ExpandEnvironmentStrings(TEXT("PATH='%PATH%'"), pszBuffer, chValue);
_tprintf(TEXT("%s\r\n"), pszBuffer);
delete[] pszBuffer;
BOOL SetEnvironmentVariable(
PCTSTR pszName,// 为变量设为psazValue参数所标识的值。
PCTSTR pszValue // 这个参数如果设置为NULL,就是表面要将环境块中删除变量
);
UINT SetErrorMode(UINT fuErrorMode);
各个模式用OR连接
标志 说明
SEM_FAILCRITICALERRORS 系统不显示关键错误句柄消息框,并将错误返回给调用进程
SEM_NOGOFAULTERRORBOX 系统不显示一般保护故障消息框。本标志只应该由采用异常情况处理程序来处理一般保护(GP)故障的调式应用程式来设定
SEM_NOOPENFILEERRORBOX 当系统找不到文件时,它不显示消息框。
SEM_NOALIGNMENTFAULTEXCEPT 系统自动排除内存没有对其的故障,并使应用程序看不到这些故障。本标志对X86处理器不起作用。
子进程会继承父进程的错误模式,如果不想让子进程继承父进程的错误模式的话,可以在调用CreateProcess时设定。
4.1.7 进程当前所在的驱动器和目录
默认情况下不提供全路径的话,系统就会在当前驱动器和目录中查找文件,比如CreateFile,因为驱动器和目录是每个进程来维护的,所以某个线程改变了目录和驱动器会改变整个进程的目录和驱动器。
下面两个函数读取和设置:
DWORD GetCurrentDirectory(DWORD cchCurDir, PTSTR pszCurDir);
BOOL SetCurrentDirectory(PCTSTR pszCurDir);
DWORD GetVersion();此函数存在高地位的混论BUG,所以尽量不要使用。
BOOL GetVersionEx(POSVERSIONINFOEX pVersionInfomation);
typedefstruct _OSVERSIONINFOEXA {
DWORD dwOSVersionInfoSize;// 在调用GetVersionEx函数之前,必必须置为sizeof(OSVERSIONINFOEX)
DWORD dwMajorVersion;// 主系统的主要版本号
DWORD dwMinorVersion;// 主系统的次要版本号
DWORD dwBuildNumber;// 当前系统的构建号
DWORD dwPlatformId;// 识别当前系统的平台。可以使VER_PLATFORM_WIN32(WIN32),VER_PLATFORM_WIN32_WINDOWS(WINDOWS 95/WINDOWS 98),VER_PLATFORM_WIN32_NT(WINDOWS NT/WINDOWS 2000)或VER_PLATFORM_WIN32_CEHH(WINDOWS CE)
CHAR szCSDVersion[128];// Maintenance string for PSS usage 本域包含了附加文本,用于提供关于已经安装的操作系统的详细信息
WORD wServicePackMajor;// 最新安装的服务程序包的主要版本号
WORD wServicePackMinor;// 最新安装的服务程序包的次要版本号
WORD wSuiteMask;// 用于标识系统上存在那个程序组(VER_SUITE_SMALLBUSINESS,VER_SUITE_ENTERPRISE,VER_SUITE_BACKOFFICE,VER_SUITE_COMMUNICATIONS,VER_SUITE_TERMINAL,VER_SUITE_SMALLBUSINESS_RESTRICTED,VER_SUITE_EMBEDDEDNT和VER_SUITE_DATACENTER)
BYTE wProductType;// 用于标识安装了下面的哪个操作系统:VER_NT_WORKSTATION,VER_NT_SERVER或VER_NT_DOMAIN_CONTROLLER
BYTE wReserved;// 留作将来使用
} OSVERSIONINFOEXA,*POSVERSIONINFOEXA,*LPOSVERSIONINFOEXA;
BOOL VerifyVersionInfo(
LPOSVERSIONINFOEX lpVersionInfo,// version info
DWORD dwTypeMask,// attributes
DWORDLONG dwlConditionMask // comparison type
);
参数1:OSVERSIONINFOEX 这个结构里保存用户提供的系统版本信息,例如major version,minor version等,这些会用作和系统实际信息进行比较。
参数2:dwTypeMask 类型掩码,是由一些宏进行或操作之后的结果,例如 VER_MAJORVERSION | VER_MINORVERSION 告诉函数major version和minor version需要进行比较,如果只定义VER_MAJORVERSION那么就只会判断major version字段。
参数3:dwlConditionMask 条件掩码,目的是向用户提供丰富的判断条件设置,各个字段都有相应的判断条件设置,通过VER_SET_CONDITION宏进行条件设置
OSVERSIONINFOEX pVersionInformation;
ZeroMemory(&pVersionInformation,sizeof(OSVERSIONINFOEX));
pVersionInformation.dwOSVersionInfoSize =sizeof(OSVERSIONINFOEX);
pVersionInformation.dwMajorVersion =5;
pVersionInformation.dwMinorVersion =1;
pVersionInformation.wServicePackMajor =2;
VER_SET_CONDITION (dwlConditionMask, VER_MAJORVERSION, VER_EQUAL);
VER_SET_CONDITION (dwlConditionMask, VER_MINORVERSION, VER_EQUAL);
VER_SET_CONDITION (dwlConditionMask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
if(VerifyVersionInfo(&pVersionInformation, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask)){
MessageBox(0, __ TEXT ("Your system fully answered our requirements"), __ TEXT ("Message"), MB_ICONINFORMATION);
}
else{
MessageBox(0, __ TEXT ("This program require Windows XP with SP2 or higher"), __ TEXT ("Error"), MB_ICONSTOP);
}