您的位置:首页 > 编程语言

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

2015-04-27 16:30 441 查看
4.1Windows两种类型的程序:

  CUI程序,比如CMD.EXE等等。MicrosoftVisualC++连接开关为/SUBSYSTEM:CONDOLE(程序启动时不能创建GUI程序)。

  GUI程序,图形用户程序,比如Notepad,Word等等。MicrosoftVisualC++连接开关为/SUBSYSTEM:WINDOWS(程序启动时不能创建CUI程序)。

注意:俩者的概念其实是很模糊的,CUI可以创建GUI图形界面,反之GUI程序可能用CUI程序。
Windows入口点函数(区分在于CUI和GUI程序,ANSI码和UNICODE码)

intWINAPIWinMain(
HINSTANCEhinstExe,
HINSTANCE,
PSTRpszCmdLine,
intnCmdShow);
intWINAPIwWinMain(
HINSTANCEhinstExe,
HINSTANCE,
PWSTRpszCmdLine,
intnCmdShow);
int__cdeclmain(
intargc,
char*argv[],
char*envp[]);
int__cdeclwmain(
intargc,
wchar_t*argv[],
wchar_t*envp[]);
其实Windows程序启动时最开始并不调用自己写的入口函数,而是调用系统的几个入口函数,以便可以调用malloc和free之类的函数,初始化全局和静态C++对象等。

应用程序类型进入点嵌入可执行文件的启动函数

ANSI码GUI应用程序WinMainWinMainCRTStattup
UNICODE码GUI应用程序wWinMainwWinMainCRTStattup
ANSI码CUI应用程序mainmainCRTStattup
UNICODE码CUI应用程序wmainwmainCRTStattup

注意:应用程序会根据SUBSYSTEM开关来查找嵌入可执行启动函数,如果进入点函数和启动函数不匹配则显示链接错误。可以删除SUBSYSTEM(VSProjectSettings)开关,这样应用程序会自动需找匹配的函数。

所有的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函数,并将返回值传递给他,关闭进程,并设置它的退出代码

intWINAPIWinMain(
HINSTANCE
hinstExe,HINSTANCE
hPrevInstance,PSTRpszCmdLine,intnCmdShow);

4.1.1
进程实例句柄
进程实例句柄代表可执行文件的实例,是(w)WinMain函数的第一个参数hInstanceExe,实际是内存基地址,也就是可执行文件所加载到的内存位置。
Visual
Studio链接器使用的默认基地址是0x00400000,使用Microsoft链接器的/BASE:address开关可以更改要将应用程序加载到哪个基地址。
使用HMODULEGetModuleHandle(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版本。例:

intnNumArgs;
PWSTR*ppArgv=CommandLineToArgvW(GetCommandLineW(),&nNumArgs);
if(*ppArgv[1]==L'x'){...}
HeapFree(GetProcessHeap(),0,ppArgv);//Freethememoryblock


4.1.4进程的环境变量
每个进程都有一个与他关联的环境块,这是在进程地址空间内分配的一块内存。其中包含的字符串类似于:
=::=::\...
VarName1=VarVarlue1\0

VarName2=VarVarlue2\0

VarName3=VarVarlue3\0...

VarNameX=VarVarlueX\0

\0

注意:

‘=’号不能是变量名的一部分。
等号左右两边的空格将被算做名称或者值。
最后必须加个’\0’表示结束。
子进程和父进程不共用环境块,修改不会影响父/子进程。

访问环境块的两种方式:
1.调用DWORD
GetEnvironmentVariable(PCTSTRpszName,PTSTRpszValue,DWORDcchValue)函数。例:

voidDumpEnvStrings(){
PTSTRpEnvBlock=GetEnvironmentStrings();
//解析块格式如下:
//=::=::\
//=...
//var=value\0
//...
//var=value\0\0
//请注意,其他一些字符串的开始处可能会是这样的'='.
//下面是当应用程序是从网络共享开始的一个例子.
//[0]=::=::\
//[1]=C:=C:\Windows\System32
//[2]=ExitCode=00000000
//
TCHARszName[MAX_PATH];
TCHARszValue[MAX_PATH];
PTSTRpszCurrent=pEnvBlock;
HRESULThr=S_OK;
PCTSTRpszPos=NULL;
intcurrent=0;
while(pszCurrent!=NULL){
//跳过毫无意义的字符串:
//"=::=::\"
if(*pszCurrent!=TEXT('=')){
//查询'='分离器.
pszPos=_tcschr(pszCurrent,TEXT('='));//
从一个字符串中查找字符。
//现在点的值的第一个字符.
pszPos++;
//复制的变量名.
size_tcbNameLength=//没有'='
(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(PTSTRpEnvBlock[]){
intcurrent=0;
PTSTR*pElement=(PTSTR*)pEnvBlock;
PTSTRpCurrent=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函数来判断一个环境变量是否存在。

函数声明:

DWORDGetEnvironmentVariable(
PCTSTRpszName,//指向预期变量名称。
PTSTRpszValue,//指向保存变量值的缓冲区
DWORDcchValue//缓冲区大小
);


变量名找到返回复制到缓冲区的字符数。

如果在环境中没找到变量名,就返回0.

函数需要调用两次,第一次向cchValue参数传入0,以返回所需字符数量,其中也包括了结束符NULL。

voidPrintEnvironmentVariable(PCTSTRpszVariableName){
PTSTRpszValue=NULL;
//获取足够的,所需要的存储缓冲区的大小值
//第一次调用GetEnvironmentVariable函数,最后一个参数传入0以获取足够缓冲区大小值。
DWORDdwResult=GetEnvironmentVariable(pszVariableName,pszValue,0);
if(dwResult!=0){
//分配好缓冲区以存储环境变量的值
DWORDsize=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'=<unknownvalue>\n"),pszVariableName);
}
}


关于2个%%之间,是可替换字符串,类似于
%USERPROFILE%\Documents转换后为C:\Users\jrichter\Documents.用ExpandEnvironmentStrings函数处理:

DWORDExpandEnvironmentStrings(
PTCSTRpszSrc,//参数是"可替换环境变量的字符串"的一个字符串地址
PTSTRpszDst,//用于接收扩展字符串的一个缓冲区地址
DWORDchSize//缓冲区所需的大小
);


其中chSize不能小于缓冲区大小,不然%%变量就不会被扩展,会被替换为空字符串。所以,通常我们需要
调用两次ExpandEnvironmentStrings函数

DWORDchValue=//第一次调用ExpandEnvironmentStrings函数..其中最后参数为0自动获取大小。
ExpandEnvironmentStrings(TEXT("PATH='%PATH%'"),NULL,0);
//分配内存空间
PTSTRpszBuffer=newTCHAR[chValue];
//第二次调用ExpandEnvironmentStrings函数.并且获取到可替换的字符串内容。
chValue=ExpandEnvironmentStrings(TEXT("PATH='%PATH%'"),pszBuffer,chValue);
_tprintf(TEXT("%s\r\n"),pszBuffer);
delete[]pszBuffer;


可以使用SetEnvironmentVariable来添加、删除、修改一个变量的值

BOOLSetEnvironmentVariable(
PCTSTRpszName,//为变量设为psazValue参数所标识的值。
PCTSTRpszValue//这个参数如果设置为NULL,就是表面要将环境块中删除变量
);


当pszName与pszValue参数一样时,要么就添加会修改一个变量。pszValue
设为NULL则删除变量

4.1.5进程的关联性

用于多cpu,进程中线程可以在主机的任何CPU上执行。然而,也可以强迫线程在可用CPU的一个子集上运行,这称为“处理器关联性”。子进程继承了父进程的关联性

4.1.6进程的错误模式

每个进程都关联了一组标志,这些标志的作用是让系统知道进程如何响应严重错误。进程可以调用SetErrorMode设置如何处理一些错误。

UINTSetErrorMode(UINTfuErrorMode);


各个模式用OR连接

标志说明

SEM_FAILCRITICALERRORS系统不显示关键错误句柄消息框,并将错误返回给调用进程

SEM_NOGOFAULTERRORBOX系统不显示一般保护故障消息框。本标志只应该由采用异常情况处理程序来处理一般保护(GP)故障的调式应用程式来设定

SEM_NOOPENFILEERRORBOX当系统找不到文件时,它不显示消息框。

SEM_NOALIGNMENTFAULTEXCEPT系统自动排除内存没有对其的故障,并使应用程序看不到这些故障。本标志对X86处理器不起作用。

子进程会继承父进程的错误模式,如果不想让子进程继承父进程的错误模式的话,可以在调用CreateProcess时设定。

4.1.7进程当前所在的驱动器和目录

默认情况下不提供全路径的话,系统就会在当前驱动器和目录中查找文件,比如CreateFile,因为驱动器和目录是每个进程来维护的,所以某个线程改变了目录和驱动器会改变整个进程的目录和驱动器。

下面两个函数读取和设置:

DWORDGetCurrentDirectory(DWORDcchCurDir,PTSTRpszCurDir);
BOOLSetCurrentDirectory(PCTSTRpszCurDir);


如果缓冲区不够大,GetCurrentDirectory将返回所需字符数,且不会复制任何内容。调用成功会返回字符串的长度。

注:WinDef.h中定义为260的常量MAX_PATH是目录名称或文件名称的最大字符数。所以传入MAX_PATH个TCHAR元素构成的一个缓冲区是非常安全的。

4.1.8进程的当前目录

系统跟踪记录着进程的当前驱动器和目录,但它没有记录每个驱动器的当前目录。解释:

例如,一个进程可以有如下所示的两个环境变量:

=C:=C:\Utility\Bin

=D:=D:\programFile

假定进程的当前目录是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系统版本

DWORDGetVersion();此函数存在高地位的混论BUG,所以尽量不要使用。

BOOLGetVersionEx(POSVERSIONINFOEXpVersionInfomation);
typedefstruct_OSVERSIONINFOEXA{
DWORDdwOSVersionInfoSize;//在调用GetVersionEx函数之前,必必须置为sizeof(OSVERSIONINFOEX)
DWORDdwMajorVersion;//主系统的主要版本号
DWORDdwMinorVersion;//主系统的次要版本号
DWORDdwBuildNumber;//当前系统的构建号
DWORDdwPlatformId;//识别当前系统的平台。可以使VER_PLATFORM_WIN32(WIN32),VER_PLATFORM_WIN32_WINDOWS(WINDOWS95/WINDOWS98),VER_PLATFORM_WIN32_NT(WINDOWSNT/WINDOWS2000)或VER_PLATFORM_WIN32_CEHH(WINDOWSCE)
CHARszCSDVersion[128];//MaintenancestringforPSSusage本域包含了附加文本,用于提供关于已经安装的操作系统的详细信息
WORDwServicePackMajor;//最新安装的服务程序包的主要版本号
WORDwServicePackMinor;//最新安装的服务程序包的次要版本号
WORDwSuiteMask;//用于标识系统上存在那个程序组(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)
BYTEwProductType;//用于标识安装了下面的哪个操作系统:VER_NT_WORKSTATION,VER_NT_SERVER或VER_NT_DOMAIN_CONTROLLER
BYTEwReserved;//留作将来使用
}OSVERSIONINFOEXA,*POSVERSIONINFOEXA,*LPOSVERSIONINFOEXA;


这个是扩展版本。
推荐使用VerifyVersionInfo来进行版本判断,官方解释是因爲一般只要判断是否windows的版本大于某个特定的版本号。

BOOLVerifyVersionInfo(
LPOSVERSIONINFOEXlpVersionInfo,//versioninfo
DWORDdwTypeMask,//attributes
DWORDLONGdwlConditionMask//comparisontype
);


参数1:OSVERSIONINFOEX这个结构里保存用户提供的系统版本信息,例如majorversion,minorversion等,这些会用作和系统实际信息进行比较。

参数2:dwTypeMask类型掩码,是由一些宏进行或操作之后的结果,例如VER_MAJORVERSION|VER_MINORVERSION告诉函数majorversion和minorversion需要进行比较,如果只定义VER_MAJORVERSION那么就只会判断majorversion字段。

参数3:dwlConditionMask条件掩码,目的是向用户提供丰富的判断条件设置,各个字段都有相应的判断条件设置,通过VER_SET_CONDITION宏进行条件设置

OSVERSIONINFOEXpVersionInformation;
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("Yoursystemfullyansweredourrequirements"),__TEXT("Message"),MB_ICONINFORMATION);
}
else{
MessageBox(0,__TEXT("ThisprogramrequireWindowsXPwithSP2orhigher"),__TEXT("Error"),MB_ICONSTOP);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: