Windows核心编程 进程--编写第一个windows应用程序

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);
4.1.1 进程实例句柄
           进程实例句柄代表可执行文件的实例,是(w)WinMain函数的第一个参数hInstanceExe,实际是内存基地址,也就是可执行文件所加载到的内存位置。
          Visual Studio链接器使用的默认基地址是0x00400000,使用Microsoft链接器的/BASE:address开关可以更改要将应用程序加载到哪个基地址。
         使用HMODULE GetModuleHandle(PCTSTR pszModule)函数返回可执行文件或dll的句柄/基地址  pszModule 文件名称,为NULL时为本进程。 GetModuleHandle函数的两大特征:
1.它只检查主调进程的地址空间 2.传递NULL会进程的地址空间中的可执行文件的基地址。
4.1.2 进程前一个实例的句柄
         (w)WinMain中的 hPrevInstance参数,用于16位的Windows系统中,保留该参数只是为了方便移植,应该传入NULL。
4.1.3 进程的命令行
      系统在创建一个进程时会传一个命令行给它。C运行库的启动代码开始执行一个GUI程序时,会调用Windows函数GetCommandLine来获取进程的完整命令行,忽略可执行文件的名称,然后将指向命令行剩余部分的一个指针传给WinMain的pszCmdLine
      PWSTR* CommandLineToArgvW(PWSTR pszCmdLine, int* pNumArgs);函数可将任何Unicode字符串分解成单独的标记。该函数只有Unicode版本。例:
  
  
int nNumArgs;
PWSTR *ppArgv =CommandLineToArgvW(GetCommandLineW(),&nNumArgs);
if(*ppArgv[1]==L'x'){...}
HeapFree(GetProcessHeap(),0,ppArgv);//Free the memory block
4.1.4 进程的环境变量
    每个进程都有一个与他关联的环境块,这是在进程地址空间内分配的一块内存。 其中包含的字符串类似于:
=::=::\... 
VarName1=VarVarlue1\0

VarName2=VarVarlue2\0

VarName3=VarVarlue3\0 ...

VarNameX=VarVarlueX\0

\0

注意:

  • ‘=’号不能是变量名的一部分。
  • 等号左右两边的空格将被算做名称或者值。
  • 最后必须加个’\0’表示结束。
  • 子进程和父进程不共用环境块,修改不会影响父/子进程。
访问环境块的两种方式:
1. 调用 DWORD GetEnvironmentVariable(PCTSTR pszName, PTSTR pszValue, DWORD cchValue)函数。例:
  
  
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 = _tcschr(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);
}
2. 访问环境变量,它是CUI(控制台)程序专用的,可通过程序main入口点函数的TCHAR *en[]参数来接收。
     env 是一个指针数组, 每个指针都指向一个不同的环境变量,而在最后一个环境变量指针后通常会带有一个NULL指针。 表明已经到达数组末尾了。
另外请注意,等号开头的无效字符串在我们接收到env之前其实就被移除了,所以可以不用处理。
    
    
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++;
}
}
}
用户登录Windows时,系统会创建外壳(shell)进程,并将一组环境字符串与其关联。系统通过检查注册表中的2个注册表项来获取初始的环境字符串。
               当我们通过应用程序或直接修改注册表的环境变量时,为了使其生效用户一般要注销或重启, 然后有些应用程序却可以通过主窗口接收到WM_SETTINGCHANGE消息,来使用新的环境变量。 这时,我们完全可以调用 SendMessage()函数来实现更新。
SendMessage(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM) TEXT("Environment"));
               另外父进程和子进程之间若有继承关系的话... 那么环境变量也可以继承,但他们是相互独立而不是共享使用的。
               我们还可以使用 GetEnvironmentVariable 函数来 判断一个环境变量是否存在。
函数声明:
    
    
DWORD GetEnvironmentVariable(
PCTSTR pszName,// 指向预期变量名称。
PTSTR pszValue,// 指向保存变量值的缓冲区
DWORD cchValue // 缓冲区大小
);
   变量名找到返回复制到缓冲区的字符数。
   如果在环境中没找到变量名,就返回0.
   函数需要调用两次,第一次向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);
}
}
关于2个%%之间,是可替换字符串, 类似于 %USERPROFILE%\Documents 转换后为 C:\Users\jrichter\Documents. 用 ExpandEnvironmentStrings 函数处理:
   
   
DWORD ExpandEnvironmentStrings(
PTCSTR pszSrc,// 参数是 "可替换环境变量的字符串"的一个字符串地址
PTSTR pszDst,// 用于接收扩展字符串的一个缓冲区地址
DWORD chSize // 缓冲区所需的大小
);
其中 chSize 不能小于 缓冲区大小,不然%%变量就不会被扩展,会被替换为空字符串。 所以,通常我们需要 调用两次 ExpandEnvironmentStrings 函数
    
    
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;
可以使用 SetEnvironmentVariable 来添加、删除、修改一个变量的值
    
    
BOOL SetEnvironmentVariable(
PCTSTR pszName,// 为变量设为psazValue参数所标识的值。
PCTSTR pszValue // 这个参数如果设置为NULL,就是表面要将环境块中删除变量
);
     当 pszName 与 pszValue 参数一样时,要么就添加会修改一个变量。 pszValue 设为 NULL则删除变量
4.1.5 进程的关联性
         用于多cpu, 进程中线程可以在主机的任何CPU上执行。然而,也可以强迫线程在可用CPU的一个子集上运行,这称为“处理器关联性”。子进程继承了父进程的关联性
4.1.6 进程的错误模式
    每个进程都关联了一组标志,这些标志的作用是让系统知道进程如何响应严重错误。 进程可以调用SetErrorMode设置如何处理一些错误。
    
    
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);
如果缓冲区不够大, GetCurrentDirectory 将返回所需字符数,且不会复制任何内容。调用成功会返回字符串的长度。
注:WinDef.h中定义为260的常量MAX_PATH是目录名称或文件名称的最大字符数。所以传入MAX_PATH个TCHAR元素构成的一个缓冲区是非常安全的。
4.1.8 进程的当前目录
    系统跟踪记录着进程的当前驱动器和目录,但它没有记录每个驱动器的当前目录。解释:
    例如,一个进程可以有如下所示的两个环境变量:
    =C:=C:\Utility\Bin
    =D:=D:\program File     
假定进程的当前目录是C:\Utility\Bin,而且我们调用CreateFile来打开D:ReadMe.txt,那么系统就会查找系统变量=D:。由于=D:变量是存在的,所以系统尝试从D: \program File  目录打开ReadMe.txt。如果=D:不存在,系统就会试着从D盘的根目录下打开ReadMe.txt。可以通过GetFullPathName来获得当前目录
注:可以使用C运行库函数_chdir函数来代替SetCurrentDirectory。_chdir在内部调用SetCurrentDirectory,而且_chdir还会调用SetEnvironmentVariable来添加或修改环境变量,从而使不同驱动器的当前变量得以保留。
4.1.9 系统版本

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;
这个是扩展版本。
            推荐使用VerifyVersionInfo来进行版本判断,官方解释是因爲一般只要判断是否windows的版本大于某个特定的版本号。
   
   
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);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值