欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

4. 进程(一) -> Windows核心编程【第五版】

程序员文章站 2022-03-13 23:51:32
...

1. GetModuleHandle

  • HMODULE GetModuleHandle(PCTSTR pszModule);
  • 该函数返回一个执行文件或DLL文件被加载到进程地址空间的位置,是一个句柄/基地址
  • 调用这个函数时,要传递一个以0为终止符的字符串, 它指定了已在主调进程的地址空间中加载的一个可执行文件或DLL文件的名称。
  • 如果找到指定的可执行文件或DLL文件名称GetModuleHandle就会返回可执行文件/DLL文件映像加载到的基地址;
  • 若没有找到,系统将返回NULL
获取主调进程的可执行文件的基地址的方法
  • GetModuleHandle的pszModule参数传入NULL值;
  • 使用链接器提供的伪变量_ImageBase,该值指向当前正在运行的模块的基地址
  • 调用GetModuleHandleEx, 第一个参数为GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,第二个参数为当前函数的地址,最后一个参数为指向HMODULE的指针。GetModuleHandleEx会用传入函数(第二个参数)所在DLL的基地址来填写指针(第三个参数

  • 演示代码
    4. 进程(一) -> Windows核心编程【第五版】

#include <Windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <tchar.h>

extern "C" const IMAGE_DOS_HEADER __ImageBase;

void DumpModel()
{
    HMODULE hModule = GetModuleHandle(NULL);
    _tprintf(TEXT("with GetModuleHandle(NULL) = 0x%x\r\n"), hModule);

    hModule = NULL;
    GetModuleHandleEx(
        GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
        (PCTSTR)DumpModel,
        &hModule);
    _tprintf(TEXT("with GetModuleHandleEx = 0x%x\r\n"), hModule);

    _tprintf(TEXT("with ImageBase = 0x%x\r\n"), (HINSTANCE)&__ImageBase);

}

//int APIENTRY _tWinMain(HINSTANCE hInstanceExe, HINSTANCE, PTSTR pszCmdLine, int nCmdShow)
int _tmain(int argc, TCHAR *arcv[])
{
    DumpModel();
    return(0);
}

2. (w)WinMain的hPreInstance参数

实际上就是历史的参数,为了移植而保留

  • C/C++ Windows的入口点函数(GUI应用程序):
    • 入口点函数的第二个参数(hPrevInstance)总是传递NULL,是因为该参数是用于16位Windows系统,为了方便移植16位Windows应用程序,所以才保留的一个参数,在自己的代码中绝对不要引用该参数, 所以(w)WinMain函数定义可以用如下两种:
      • 第一种,没有指定参数名,所以编译器不会报告一个参数没有被引用到的警告
      • 第二种是利用UNREFERENCED_PARAMETER宏来消除警告
//////////////////////第一种
int WINAPI _tWinMain(
    HINSTANCE hInstanceExe,
    HINSTANCE,
    PSTR pszCmdLine,
    int nCmdShow);

//////////////////////第二种
int APIENTRY _tWinMain(HINSTANCE hInstance,
                       HINSTANCE hPrevInstance,
                       LPTSTR lpCmdLine,
                       int nCmdShow) 
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);
}

PS: WINAPI 和 APIENTRY实际上都是__stdcall的宏定义, 关于 __stdcall的介绍, 见文章 WINAPI和APIENTRY,C/C++函数调用的方式, 函数名字修饰规则

3. 获取环境变量

使用GetEnvironmentStrings函数来获取完整的环境快
  • 如果不再需要GetEnvironmentStrings函数返回的内存块,应调用FreeEnvironmentStrings函数来释放它
#include <Windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <tchar.h>
#include <string.h>
#include <WinNT.h>
#include <StrSafe.h>

void DumpEnvStrings()
{
    PTSTR pEnvBlock = GetEnvironmentStrings();
    // 将环境变量字符串解析成如下格式
    // =::=::\
    // =...
    // var=value\0
    // ...
    // var=value\0\0
    // 有一些字符串以'='开始
    // 解析成如下格式
    // {0} =::=::\
    // {1} =C:=C:\Windows\System32
    // {2} =ExitCode=00000000
    //
    PTSTR pszCurrent = pEnvBlock;
    TCHAR szName[MAX_PATH];
    TCHAR szValue[MAX_PATH];
    size_t current = 0;
    HRESULT hr = S_OK;
    PTSTR pszPos = NULL;

    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*sizeof(TCHAR), pszCurrent, cbNameLength);

            if (FAILED(hr))
            {
                break;
            }

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

int _tmain(int argc, TCHAR *argv[])
{

    DumpEnvStrings();
    return(0);
}
使用main入口点函数所接收的THCAR *env[]
void DumpEnvVariable(PTSTR pEnvBlock[])
{
    int current = 0;
    PTSTR *pElement = (PTSTR *)pEnvBlock;
    PTSTR pCurrent = NULL;
    while (pElement != NULL)
    {
        pCurrent = (PTSTR)(*pElement);
        if (pCurrent == NULL)
        {
            // 没有更多环境变量了
            pElement == NULL;
        }
        else 
        {
            _tprintf(TEXT("{%u} %s\r\n"), current, pCurrent);
            ++current;
            ++pElement;
        }
    }
}

int _tmain(int argc, TCHAR *argv[], TCHAR *env[])
{
    DumpEnvVariable(env);
    return(0);
}
  • 上述结果均如下:
    4. 进程(一) -> Windows核心编程【第五版】

4. 进程的亲缘性

  • 一般来说,进程中的线程可以在主计算机中的任何一个CPU上执行, 但是一个进程的线程可能被强制在可用CPU的子集上运行。这称为进程的亲缘性。
  • 子进程继承了父进程的亲缘性。

5. 进程的错误模式

  • 这是与每个进程相关联的是一组标志,用于告诉系统,进程对严重的错误应该如何作出反映,
    • 包括磁盘介质故障未处理的异常情况文件查找失败和数据没有对齐等。
    • 进程可以告诉系统如何处理每一种错误。方法是调用SetErrorMode函数:
    UINT  SetErrorMode(UINT fuErrorMode);
  • fuErrorMode参数是下表的任何标志按位用OR连接在一起的组合。

    • 默认情况下,子进程继承父进程的错误模式标志。换句话说,如果一个进程的SEM_NOGPFAULTERRORBOX标志已经打开,并且生成了一个子进程,该子进程也拥有这个打开的标志。但是,子进程并没有得到这一情况的通知,它可能尚未编写以便处理GP故障的错误。如果GP故障发生在子进程的某个线程中,该子进程就会终止运行,而不通知用户。父进程可以防止子进程继承它的错误模式, 方法是在调用CreateProcess时设定CREATE_DEFAULT_ERROR_MODE标志
相关标签: Windows