win32 API编程学习笔记

win32的变量类型

https://www.cnblogs.com/happycat1988/archive/2013/04/14/3020820.html

win32中的宽字符

视频主要讲了MessageBox()这个API函数吧

#include<stdio.h>
#include<windows.h>
int main()
{
    /*    int MessageBox(
        HWND hWND,   //handle to owner window   句柄 
        LPCSTR IpText,  //text in message box  内容 
        LPCSTR IpCption, //message box title  窗口的标题 
        UINT uType       //message box style  窗口的格式 
        )
      */

//    CHAR message[]="hellow world";
//    CHAR title[]="messagebox";
//    MessageBoxA(NULL,message,title,MB_OK);   /*ASCALL   单位为一个字节*/ 


//    WCHAR message[]=L"hellow world";
//    WCHAR title[]=L"messagebox";
//    MessageBoxW(NULL,message,title,MB_OK);   /*unicode  一个单位为两个字节*/

//    TCHAR message[]=TEXT("hellow world");
//    TCHAR title[]=TEXT("messagebox");
//    MessageBox(NULL,message,title,MB_OK);    /*可以根据项目的要求来决定是什么编码方式*/

    return 0;
}

进程的创建过程

1.什么是进程?

进程提供程序所需的资源,如:数据,代码(dll文件这些)。需要注意的是只提供资源,而使用这些资源是线程做的事情,任何进程都是别的进程创建的,使用了CreateProcess()这个函数,然后第一个进程是内核创建的。

2.进程内存空间的地址划分

可以在调试器(od,xdbg)里面大致查看,下面是分区。

空指针赋值区    0x00000000--0x0000FFFF   //就是全是空的,一般不会使用这一部分
用户模式区      0x00010000--0x7FFEFFFF   //我们使用的地方,加载exe,和dll的,这个过程应该发生了基地址重定位。
64kb禁入区      0x7FFF0000--0X7FFFFFFF   //也是空的,我们和系统都不会用的地方
内核            0x80000000--0xFFFFFFFF   //内核系统占用的地方,这些内核是不会变的

3.进程的创建过程

1.映射EXE文件
2.创建内核对象EPROCESS
3.映射系统Dll(ntdll.dll)  //映射的第一个dll
4.创建线程内核对象ETHREAD
5.系统启动线程
    映射DLL(ntdll.LdrlnitializeThunk)  //应该是加载其他的dll这些,然后有些dll也会加载它们需要的dll,这也是为什么进程里面有很多dll

    线程开始执行


如果熟悉这些过程,就可以较好的理解dll注入这些技术手段。

创建进程

主要就是CreateProcess()这个API

BOOL CreateProcess
(
    LPCTSTR lpApplicationName,  //指向一个NULL结尾的、用来指定可执行模块的字符串。
    LPTSTR lpCommandLine,  //指向一个以NULL结尾的字符串,该字符串指定要执行的命令行。
    LPSECURITY_ATTRIBUTES lpProcessAttributes,
    LPSECURITY_ATTRIBUTES lpThreadAttributes,
    BOOL bInheritHandles,
    DWORD dwCreationFlags,
    LPVOID lpEnvironment,
    LPCTSTR lpCurrentDirectory,
    LPSTARTUPINFO lpStartupInfo,  //STARTUPINFO,指定一个STARTUPINFO结构,其中包含了创建进程时使用的附加信息
    LPPROCESS_INFORMATIONlpProcessInformation   //PROCESS_INFORMATION,该结构用于容纳新进程的进程和线程标识符。大多数情况下,一旦这个函数返回,父应用程序都会关闭两个句柄。
);

然后就是两个结构体

  typedef struct _PROCESS_INFORMATION {
    HANDLE hProcess;  //两个句柄
    HANDLE hThread;
    DWORD dwProcessId;   //两个id
    DWORD dwThreadId;
  } PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION;

  typedef struct _STARTUPINFOA {
    DWORD cb;   //第一个是用来储存这个结构体的size的。
    LPSTR lpReserved;
    LPSTR lpDesktop;
    LPSTR lpTitle;
    DWORD dwX;
    DWORD dwY;
    DWORD dwXSize;
    DWORD dwYSize;
    DWORD dwXCountChars;
    DWORD dwYCountChars;
    DWORD dwFillAttribute;
    DWORD dwFlags;
    WORD wShowWindow;
    WORD cbReserved2;
    LPBYTE lpReserved2;
    HANDLE hStdInput;
    HANDLE hStdOutput;
    HANDLE hStdError;
  } STARTUPINFOA, *LPSTARTUPINFOA;

例子

#include<stdio.h>
#include<windows.h>
BOOL CreateChildProcess(PTCHAR szChildname,PTCHAR szCommandLine)
{
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    ZeroMemory(&pi,sizeof(pi));//用来将指定的内存块清零。
    ZeroMemory(&si,sizeof(si));
    si.cb =sizeof(si);

    if(!CreateProcess(
        szChildname,
        szCommandLine,
        NULL,  //不继承进程句柄
        NULL,  //不继承线程句柄
        FALSE, //不继承句柄 
        0,     //没有创建标志,常用的有一个CREATE_SUSPENDED,立即挂起新进程。除非调用了ResumeThread函数函数,否则它不会恢复运行。可以用来干坏事。
        NULL,
        NULL,
        &si,
        &pi)
    )
    {
        printf("CreateChildProcess Error%d",GetLastError());   //用来返回大概错误信息
        return FALSE;
    }
    CloseHandle(pi.hProcess);   //关闭句柄
    CloseHandle(pi.hThread);
    return TRUE;
}

int main(int argc,char*argv[])
{
    TCHAR lpApplicationName[]=TEXT("C:/Program Files/Mozilla Firefox/firefox.exe");
    TCHAR lpCommandLine[]=TEXT(" https://the_itach1.gitee.io/");

    CreateChildProcess(lpApplicationName,lpCommandLine);

    getchar();
    return 0;
}

其中一些函数的解释

ZeroMemory()

ZeroMemory只是将指定的内存块清零。
使用结构前清零,而不让结构的成员数值具有不确定性,是一个好的编程习惯。

void ZeroMemory( PVOID Destination,SIZE_T Length );

Destination :指向一块准备用0来填充的内存区域的开始地址。
Length :准备用0来填充的内存区域的大小,按字节来计算。

句柄与id

内核对象

像进程,线程,文件,互斥体,事件等在内核都有一个对应的结构体,这些结构体由内核负责管理,我们管这样的对象叫做内核对象。

句柄表

每个进程里面都有一个句柄表,根据索引可以找到像对应的内核对象(结构体),比如说File,Process,Thread,Event….。句柄应该就指向对应的内核对象。

特别的是操作系统有一个特殊的句柄,叫做全局句柄表。

ID

ID可以理解为就是每一个进程的编号,是不会变的。

进程相关API

SuspendThread()

SuspendThread,暂停指定的线程。

DWORD WINAPI SuspendThread(
  _In_HANDLE hThread
  );

ResumeThread()

ResumeThread,启动指定的线程。

DWORD WINAPI ResumeThread(
_In_ HANDLE hThread
);

TerminateProcess()
终止指定进程及其所有的线程。

BOOL TerminateProcess(
    HANDLE hProcess,  //进程句柄
    UINT uExitCode    //进程终止码
);

OpenProcess()

OpenProcess 函数用来打开一个已存在的进程对象,并返回进程的句柄。

HANDLE OpenProcess(
    DWORD dwDesiredAccess, //渴望得到的访问权限(标志),有很多种。
    BOOL bInheritHandle, // 是否继承句柄
    DWORD dwProcessId// 进程标示符
);

例子

#include<stdio.h>
#include<windows.h>

int main()
{
    HANDLE hProcess;
//    hProcess=(HANDLE)0x212;

    hProcess=OpenProcess(PROCESS_ALL_ACCESS,FALSE,19220);
    if(!TerminateProcess(hProcess,1))
    {
        printf("无法终止进程"); 
    }

 } 

GetModuleFileName()

获取当前进程已加载模块的文件的完整路径,该模块必须由当前进程加载。

DWORDGetModuleFileName(
HMODULE  hModule,//装载一个程序实例的句柄。如果该参数为NULL,该函数返回该当前应用程序全路径。
LPTSTR  lpFilename,
DWORD  nSize
);

例子

TCHAR szPath[MAX_PATH];
if( !GetModuleFileName( NULL, szPath, MAX_PATH ) )
{
    printf("GetModuleFileName failed (%d)\n", GetLastError());
    return FALSE;
}
printf("%s",szPath);

GetCurrentDirectory()

是一个系统参数,在一个缓冲区中装载当前目录,Long,装载到lpBuffer的字节数。如nBufferLength的长度不够,不足以容纳目录,则返回值是必要的缓冲区长度(要求至少这个长度),其中包括空中止字符。零表示失败。会设置GetLastError。

DWORD GetCurrentDirectory(
DWORD nBufferLength,     //sizeofdirectorybuffer  缓冲区的长度
LPTSTR lpBuffer          //directorybuffer  指定一个预定义字串,用于装载当前目录
);

例子

TCHAR scPath[MAX_PATH];
if( !GetCurrentDirectory( MAX_PATH,scPath ) )
{
    printf("GetCurrentDirectory failed (%d)\n", GetLastError());
    return FALSE;
}
printf("%s",scPath); 

GetCurrentProcessId()

获取当前进程一个唯一的标识符(PID)。返回值 Long,当前进程的PID

例子

long PID;
PID=GetCurrentProcessId(); 
printf("%d",PID);

GetCurrentProcess()

获取当前进程的一个伪句柄,返回值 Long,当前进程的伪句柄

例子

HANDLE FAKEhProcess;
FAKEhProcess=GetCurrentProcess();
printf("%d",FAKEhProcess);

CreateToolhelp32Snapshot()

CreateToolhelp32Snapshot可以通过获取进程信息为指定的进程、进程使用的堆[HEAP]、模块[MODULE]、线程建立一个快照。

HANDLE WINAPI CreateToolhelp32Snapshot(
    DWORD dwFlags, //用来指定“快照”中需要返回的对象,可以是TH32CS_SNAPPROCESS等
    DWORD th32ProcessID //一个进程ID号,用来指定要获取哪一个进程的快照,当获取系统进程列表或获取 当前进程快照时可以设为0
);

TH32CS_SNAPPROCESS(0x00000002) - 在快照中包含系统中所有的进程。
调用成功,返回快照的句柄,调用失败,返回INVALID_HANDLE_VALUE 。

例子,查找一个进程的PID

#include "windows.h"
#include "tlhelp32.h"
#include "tchar.h"

DWORD FindProcessID(LPCTSTR szProcessName)
{
    DWORD dwPID = 0xFFFFFFFF;
    HANDLE hSnapShot = INVALID_HANDLE_VALUE;   //定义了一个快照的句柄。
    PROCESSENTRY32 pe;  //tagPROCESSENTRY32结构体,下面会介绍。

    // Get the snapshot of the system
    pe.dwSize = sizeof(PROCESSENTRY32);
    hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, NULL);  //获取快照句柄

    // find process
    Process32First(hSnapShot, &pe);  //获得第一个进程的句柄。
    do
    {
        if (!_tcsicmp(szProcessName, (LPCTSTR) pe.szExeFile))//比较当前进程和我们需要的进程的名称是否一样。
        {
            dwPID = pe.th32ProcessID;
            break;
        }
    } while (Process32Next(hSnapShot, &pe)); //获得下一个进程的句柄。

    CloseHandle(hSnapShot);  //关闭快照句柄

    return dwPID;
}

int _tmain(int argc, TCHAR* argv[])
{
    DWORD dwPID = 0xFFFFFFFF;

    // find process
    dwPID = FindProcessID(argv[1]);
    if (dwPID == 0xFFFFFFFF)
    {
        _tprintf(L"There is no <%s> process!\n", argv[1]);
        return 1;
    }

    _tprintf(L"PID of \"%s\" is %d\n", argv[1], dwPID);

}

tagPROCESSENTRY32结构体,快照的进程信息会保存在这个结构体内。

typedef struct tagPROCESSENTRY32 {
    DWORD dwSize; // 结构大小;
    DWORD cntUsage; // 此进程的引用计数;
    DWORD th32ProcessID; // 进程ID;
    DWORD th32DefaultHeapID; // 进程默认堆ID;
    DWORD th32ModuleID; // 进程模块ID;
    DWORD cntThreads; // 此进程开启的线程计数;
    DWORD th32ParentProcessID;// 父进程ID;
    LONG pcPriClassBase; // 线程优先权;
    DWORD dwFlags; // 保留;
    WCHAR szExeFile[MAX_PATH]; // 进程全名;
} PROCESSENTRY32;

创建线程

1.什么是线程?

(1)线程是附属在进程上的执行实体,是代码的执行流程。

(2)一个进程可以包含多个线程,但一个进程至少要包含一个线程。

CreateThread()

HANDLE CreateThread(
    LPSECURITY_ATTRIBUTES lpThreadAttributes,//SD
    SIZE_T dwStackSize,//initialstacksize   初始堆栈大小
    LPTHREAD_START_ROUTINE lpStartAddress,//threadfunction 
    LPVOID lpParameter,//threadargument   线程参数
    DWORD dwCreationFlags,//creationoption   创建选项
    LPDWORD lpThreadId//threadidentifier   线程标识符
)

第一个参数 lpThreadAttributes 表示线程内核对象的安全属性,一般传入NULL表示使用默认设置。

第二个参数 dwStackSize 表示线程栈空间大小。传入0表示使用默认大小(1MB)。

第三个参数 lpStartAddress 表示新线程所执行的线程函数地址,多个线程可以使用同一个函数地址。

第四个参数 lpParameter 是传给线程函数的参数。

第五个参数 dwCreationFlags 指定额外的标志来控制线程的创建,为0表示线程创建之后立即就可以进行调度,如果为CREATE_SUSPENDED则表示线程创建后暂停运行,这样它就无法调度,直到调用ResumeThread()。

第六个参数 lpThreadId 将返回线程的ID号,传入NULL表示不需要返回该线程ID号。

ThreadProc()

DWORD WINAPI ThreadProc(
LPVOID lpParameter
);

参数:
lpParameter
接收线程传递给函数使用的CreateThread函数lpParameter参数数据。

返回值:
函数应该返回一个值,表示线程函数返回退出码,一般使用0作为返回值。
线程创建成功,返回非零值,否则为0。

例子

#include<stdio.h>
#include<windows.h>

DWORD WINAPI ThreadProc(LPVOID lpParam)
{
    printf("%d",*(int*)lpParam);
    int i;
    for(i=0;i<=100;i++)
    {
        Sleep(500);
        printf("+++++++++++%d\n",i);
    }

    return 0;
}

int main()
{
    int i;
    int b=10;
    HANDLE hthread;
    LPVOID lpParam=&b; 

    hthread=CreateThread(NULL,0,ThreadProc,lpParam,0,NULL);
    for(i=0;i<=100;i++)
    {
        Sleep(500);
        printf("------------%d\n",i);
    }
}

和线程相关的函数

Sleep()

Sleep函数可以使计算机程序(进程,任务或线程)进入休眠,使其在一段时间内处于非活动状态。当函数设定的计时器到期,或者接收到信号、程序发生中断都会导致程序继续执行。

Sleep(3000)//休眠3s

SuspendThread()

SuspendThread,暂停指定的线程。

DWORD WINAPI SuspendThread(
  _In_HANDLE hThread  //进程句柄
);

ResumeThread()

使线程的挂起时间计数减一。创建一个挂起的线程或者手动挂起一个线程后调用。调用该函数后线程不一定会立刻执行,而是由操作系统继续调度,直到计数为0,系统为其分配资源时才开始执行。

DWORD WINAPI ResumeThread(
    _In_ HANDLE hThread  //进程句柄
);

WaitForSingleObject()

DWORD WINAPI WaitForSingleObject(
    __in HANDLE hHandle,
    __in DWORD dwMilliseconds
);
hHandle[in]:

对象句柄。可以指定一系列的对象,如Event、Job、Memory resource notification、Mutex、Process、Semaphore、Thread、Waitable timer等。

dwMilliseconds[in]:
定时时间间隔,单位为milliseconds(毫秒).如果指定一个非零值,函数处于等待状态直到hHandle标记的对象被触发,或者时间到了。如果dwMilliseconds为0,对象没有被触发信号,函数不会进入一个等待状态,它总是立即返回。如果dwMilliseconds为INFINITE,对象被触发信号后,函数才会返回。

WaitForMultipleObjects()

WaitForMultipleObjects是Windows中的一个功能非常强大的函数,几乎可以等待Windows中的所有的内核对象

DWORD WaitForMultipleObjects(
    DWORD nCount,    //数组中的对象句柄数
    const HANDLE* lpHandles,   //[in]一组对象句柄。该数组可以包含不同类型对象的句柄。
    BOOL bWaitAll,   //[in] 如果此参数为TRUE,则在lpHandles数组中的所有对象的状态发出信号时,该函数返回。如果为FALSE,则当任何一个对象的状态设置为信号时,该函数返回。在后一种情况下,返回值表示其状态导致函数返回的对象。
    DWORD dwMilliseconds  //INFINITE
);

GetExitCodeThread()

GetExitCodeThread,计算机用语,用于获取一个已中止线程的退出代码。

BOOL GetExitCodeThread(
     HANDLE hThread,  //in,获取退出代码的一个线程的句柄
     LPDWORD lpExitCode   //out,存储线程结束代码,也就是线程的返回值
);

例子

#include<stdio.h>
#include<windows.h>

DWORD WINAPI ThreadProc(LPVOID lpParam)
{
    printf("this is a thread\n");
    return 5;
}

int main()
{
    HANDLE arrhtherd[2];
    DWORD  ExitCode1,ExitCode2;
    arrhtherd[0]=CreateThread(NULL,0,ThreadProc,NULL,0,NULL);
    //将线程1挂起2次,并睡眠5秒,然后恢复线程1 
    SuspendThread(arrhtherd[0]);
    SuspendThread(arrhtherd[0]);
    Sleep(5000);
    ResumeThread(arrhtherd[0]);
    ResumeThread(arrhtherd[0]);

    arrhtherd[1]=CreateThread(NULL,0,ThreadProc,NULL,0,NULL);
    //等待两线程都完成。 
    WaitForMultipleObjects(2,arrhtherd,TRUE,INFINITE);
    //获得线程1的返回值ExitCode1,然后输出 
    GetExitCodeThread(arrhtherd[0],&ExitCode1);
    printf("%d",ExitCode1);

    CloseHandle(arrhtherd[0]);
    CloseHandle(arrhtherd[1]);
    getchar();
}

临界区-多线程调用全局变量产生的问题

当多个个线程同时调用一个全局变量时候会有线程安全问题,因为线程的进行时间的不定性,可能导致线程1执行到一半,然后又去执行线程2,又因为使用的是同一个全局变量,就会产生问题。

#include<stdio.h>
#include<windows.h>

int number=10; 
DWORD WINAPI ThreadProc1(LPVOID lpParam)
{
    while(number>0)
    {
        printf("当前number为%d\n",number);
        number--;
        printf(" 减后number为%d\n",number);
    }
    return 0;
}

DWORD WINAPI ThreadProc2(LPVOID lpParam)
{
    while(number>0)
    {
        printf("当前number为%d\n",number);
        number--;
        printf(" 减后number为%d\n",number);
    }
    return 0;
}

int main()
{
    HANDLE arrhtherd[2];

    arrhtherd[0]=CreateThread(NULL,0,ThreadProc1,NULL,0,NULL);
    arrhtherd[1]=CreateThread(NULL,0,ThreadProc2,NULL,0,NULL);

    WaitForMultipleObjects(2,arrhtherd,TRUE,INFINITE);

    CloseHandle(arrhtherd[0]);
    CloseHandle(arrhtherd[1]);
    getchar();
}

执行会看到结果如下,存在一些问题

所以这时候我们需要设置临界区,也就是线程锁,让线程1在执行的时候,不会跳到线程2。但是设置临界区要注意保证线程中和全局变量相关的逻辑代码都要报包含在临界区内

#include<stdio.h>
#include<windows.h>

int number=10; 
//创建全局变量 
CRITICAL_SECTION cs;

DWORD WINAPI ThreadProc1(LPVOID lpParam)
{
    //实现临界区 
    EnterCriticalSection(&cs);
    while(number>0)
    {
        printf("当前number为%d\n",number);
        number--;
        printf(" 减后number为%d\n",number);
    }
    LeaveCriticalSection(&cs);
    return 0;
}

DWORD WINAPI ThreadProc2(LPVOID lpParam)
{
    EnterCriticalSection(&cs);
    while(number>0)
    {
        printf("当前number为%d\n",number);
        number--;
        printf(" 减后number为%d\n",number);
    }
    LeaveCriticalSection(&cs);
    return 0;
}

int main()
{
    HANDLE arrhtherd[2];
    //初始化全局变量 
    InitializeCriticalSection(&cs);

    arrhtherd[0]=CreateThread(NULL,0,ThreadProc1,NULL,0,NULL);
    arrhtherd[1]=CreateThread(NULL,0,ThreadProc2,NULL,0,NULL);

    WaitForMultipleObjects(2,arrhtherd,TRUE,INFINITE);

    CloseHandle(arrhtherd[0]);
    CloseHandle(arrhtherd[1]);
    getchar();
} 

执行后会发现没有问题,就不贴图了。

互斥体

CreateMutex

CreateMutex是一个计算机函数,作用是找出当前系统是否已经存在指定进程的实例。如果没有则创建一个互斥体。

HANDLE CreateMutex(
    LPSECURITY_ATTRIBUTES lpMutexAttributes, // 指向安全属性的指针,一般设置为NULL
    BOOL bInitialOwner, // 初始化互斥对象的所有者,如创建进程希望立即拥有互斥体,则设为TRUE。一个互斥体同时只能由一个线程拥有。
    LPCTSTR lpName // 指向互斥对象名的指针
);

BOOL bInitialOwner TRUE代表就是当前线程的,FALSE代表这个互斥体不是当前线程只是将他创建出来

例子,自己过改TRUE和FLASE来理解。

进程1

#include<stdio.h>
#include<windows.h>

int main()
{
    int i;
    //创建一个互斥体 
    HANDLE g_hMutex=CreateMutex(NULL,TRUE,"name");
    //获取令牌:有信号 或者线程的拥有者(TRUE)
    WaitForSingleObject(g_hMutex,INFINITE);
    for(i=0;i<10;i++)
    {
        Sleep(1000);
        printf("hellow world\n");
    }
    //释放令牌 
    ReleaseMutex(g_hMutex);
    getchar(); 
    return 0;
 } 

进程2

#include<stdio.h>
#include<windows.h>

int main()
{
    int i;
    //创建一个互斥体 
    HANDLE g_hMutex=CreateMutex(NULL,TRUE,"name");
    //获取令牌:有信号 或者线程的拥有者(TRUE)
    WaitForSingleObject(g_hMutex,INFINITE);
    for(i=0;i<10;i++)
    {
        Sleep(1000);
        printf("hellow world\n");
    }
    //释放令牌 
    ReleaseMutex(g_hMutex);
    getchar(); 
    return 0;
 } 

我自己得出的结论就是:互斥体只会存在于其所在的线程,如果这个互斥体被其拥有者线程占用了,那么其线程将无法使用该互斥体。

例子

#include<stdio.h>
#include<windows.h>
HANDLE g_hSet1;

DWORD WINAPI ThreadProc1(LPVOID lpParam)
{
    WaitForSingleObject(g_hSet1,INFINITE);
    printf("++++++++++++\n");
    ReleaseMutex(g_hSet1);
    return 0;
}

DWORD WINAPI ThreadProc2(LPVOID lpParam)
{

    WaitForSingleObject(g_hSet1,INFINITE);
    printf("-----------\n");
    SetEvent(g_hSet1);
    return 0;
}

int main()
{
    HANDLE arrhtherd[2];

    g_hSet1=CreateMutex(NULL,TRUE,NULL);
    ReleaseMutex(g_hSet1);//如果将这一句注释掉,那么线程1和线程2都无法执行。 

    arrhtherd[0]=CreateThread(NULL,0,ThreadProc1,NULL,0,NULL);
    arrhtherd[1]=CreateThread(NULL,0,ThreadProc2,NULL,0,NULL);

    WaitForMultipleObjects(2,arrhtherd,TRUE,INFINITE);

    CloseHandle(arrhtherd[0]);
    CloseHandle(arrhtherd[1]);
    getchar();
} 

互斥体和线程锁的区别

  1. 线程锁只能用于单个进程间的线程控制
  2. 互斥体可以设定等待超时,但线程锁不能
  3. 线程意外终结时,Mutex可以避免无限等待
  4. Mutex效率没有线程锁高

防多开

CreateMutex函数的返回值

Long,如执行成功,就返回互斥体对象的句柄;零表示出错。会设置GetLastError。如果返回的是一个有效句柄,但指定的名字已经存在,GetLastError也会设为ERROR_ALREADY_EXISTS,bInitialOwner的值将会被忽略。如果调用者限制了权限,GetLastError将会返回ERROR_ACCESS_DENIED,这个时候应该使用OpenMutex函数。

#include <stdio.h> 
#include <windows.h>
int main(int argc, char* argv[])
{
    //创建令牌
    HANDLE g_mutex = CreateMutex(NULL,FALSE,"防止多开");
    DWORD DwRet = GetLastError();
    //CreateMutex()执行成功 
    if(g_mutex)
    {
        //否是互斥体的名字已经存在 
        if(DwRet == ERROR_ALREADY_EXISTS)
        {
            CloseHandle(g_mutex);
            return 0;
        }       
    }
    //CreateMutex()执行出错了
    else
    {
        printf("创建失败");
        CloseHandle(g_mutex);
        return 0;
    }
    while(1)
    {
        Sleep(1000);
        printf("程序执行中\n");
    }   
    return 0;
}

事件

CreateEvent()

CreateEvent是一个WindowsAPI函数。它用来创建或打开一个命名的或无名的事件对象。

HANDLECreateEvent(
    LPSECURITY_ATTRIBUTES lpEventAttributes,// 安全属性
    BOOL bManualReset,// 复位方式,TRUE为通知类型,FALSE为互斥类型。
    BOOL bInitialState,// 初始状态,FALSE为无信号,TURE为有信号
    LPCTSTR lpName // 对象名称
);
bManualReset:指定将事件对象创建成手动复原还是自动复原。如果是TRUE,那么必须用ResetEvent函数来手工将事件的状态复原到无信号状态。如果设置为FALSE,当一个线程等待到事件信号后系统会自动将事件状态复原为无信号状态。

实现同步,同步=互斥+有序

#include<stdio.h>
#include<windows.h>
HANDLE g_hSet1,g_hSet2;
int b=10;
DWORD WINAPI ThreadProc1(LPVOID lpParam)
{
    int i;
    for(i=0;i<b;i++)
    {
        WaitForSingleObject(g_hSet1,INFINITE);
        printf("++++++++++++\n");
        SetEvent(g_hSet2);
    }
    return 0;
}

DWORD WINAPI ThreadProc2(LPVOID lpParam)
{
    int i;
    for(i=0;i<b;i++)
    {
        WaitForSingleObject(g_hSet2,INFINITE);
        printf("-----------\n");
        SetEvent(g_hSet1);
    }
    return 0;
}

int main()
{
    HANDLE arrhtherd[2];

    g_hSet1=CreateEvent(NULL,FALSE,TRUE,NULL);
    g_hSet2=CreateEvent(NULL,FALSE,FALSE,NULL);

    arrhtherd[0]=CreateThread(NULL,0,ThreadProc1,NULL,0,NULL);
    arrhtherd[1]=CreateThread(NULL,0,ThreadProc2,NULL,0,NULL);

    WaitForMultipleObjects(2,arrhtherd,TRUE,INFINITE);

    CloseHandle(arrhtherd[0]);
    CloseHandle(arrhtherd[1]);
    getchar();
} 

第一个windows程序

WinMain()

WinMain是一个函数,该函数的功能是被系统调用,作为一个32位应用程序的入口点。WinMain函数应初始化应用程序,显示主窗口,进入一个消息接收一发送循环,这个循环是应用程序执行的其余部分的顶级控制结构。

int APIENTRY WinMain(
    HINSTANCE hInstance,  //模块句柄,应用程序当前实例的句柄。
    HINSTANCE hPrevInstance,  //Null
    LPSTR     lpCmdLine,  //指向应用程序命令行的字符串的指针
    int       nCmdShow) //指明窗口如何显示,例如最大化,最小化,具体请查阅。

sprintf()

sprintf指的是字符串格式化命令,函数声明为 int sprintf(char *string, char *format [,argument,…]);,主要功能是把格式化的数据写入某个字符串中,即发送格式化输出到 string 所指向的字符串。

int sprintf(char *string, char *format [,argument,...]);

WNDCLASS

WNDCLASS是一个由系统支持的结构,用来储存某一类窗口的信息,如ClassStyle,消息处理函数,Icon,Cursor,背景Brush等。也就是说,CreateWindow只是将某个WNDCLASS定义的窗体变成实例。

typedef struct _WNDCLASS {
    UINT style;// 窗口类的风格
    WNDPROC lpfnWndProc;//窗口处理函数
    int cbClsExtra;//窗口扩展
    int cbWndExtra;//窗口实例扩展
    HINSTANCE hInstance;//实例句柄
    HICON hIcon;//窗口的最小化图标
    HCURSOR hCursor;//窗口鼠标光标
    HBRUSH hbrBackground;//窗口背景色
    LPCTSTR lpszMenuName;//窗口菜单
    LPCTSTR lpszClassName;// 窗口类名
} WNDCLASS, *LPWNDCLASS;

CreateWindow()

HWND WINAPI CreateWindow(

  _In_opt_  LPCTSTR lpClassName,  // 窗口类名称    
  _In_opt_  LPCTSTR lpWindowName, // 窗口标题    
  _In_      DWORD dwStyle,        // 窗口风格,或称窗口格式    
  _In_      int x,                // 初始 x 坐标    
  _In_      int y,                // 初始 y 坐标    
  _In_      int nWidth,           // 初始 x 方向尺寸    
  _In_      int nHeight,          // 初始 y 方向尺寸
  _In_opt_  HWND hWndParent,      // 父窗口句柄
  _In_opt_  HMENU hMenu,          // 窗口菜单句柄
  _In_opt_  HINSTANCE hInstance,  // 程序实例句柄
  _In_opt_  LPVOID lpParam        // 创建参数
);

WndProc()

Wndproc是Windows操作系统向应用程序发送一系列消息之一,每个窗口会有一个窗口过程的回调函数,分别是窗口句柄、消息ID、WPARAM、LPARAM。
LRESULT CALLBACK WndProc( //WndProc名称可自由定义
HWND hwnd,//窗口句柄
UINT uMsg,//消息ID
WPARAM wParam,//消息参数
LPARAM lParam//消息参数
);
ShowWindow()
该函数设置指定窗口的显示状态。

BOOL ShowWindow(
    HWND hWnd,//窗口句柄
    int nCmdShow//指定窗口如何显示
);

GetMessage()

GetMessage是从调用线程的消息队列里取得一个消息并将其放于指定的结构。

GetMessage(
    LPMSG lpMsg,//指向MSG结构的指针,该结构从线程的消息队列里接收消息信息。
    HWND hWnd,//取得其消息的窗口的句柄。
    UINT wMsgFilterMin,
    UINT wMsgFilterMax
)
lpMsg:指向MSG结构的指针,该结构从线程的消息队列里接收消息信息。
hWnd:取得其消息的窗口的句柄。当其值取NULL时,GetMessage为任何属于调用线程的窗口检索消息,线程消息通过PostThreadMessage寄送给调用线程。
wMsgFilterMin:指定被检索的最小消息值的整数。
wMsgFilterMax:指定被检索的最大消息值的整数。
返回值:如果函数取得WM_QUIT之外的其他消息,返回非零值。如果函数取得WM_QUIT消息,返回值是零。如果出现了错误,返回值是-1。例如,当hWnd是无效的窗口句柄或lpMsg是无效的指针时。若想获得更多的错误信息,请调用GetLastError函数。

MSG

用来储存消息的结构体。MSG是Windows程序中的结构体。在Windows程序中,消息是由MSG结构体来表示的。成员变量含义:第一个成员变量hwnd表示消息所属的窗口;第二个成员变量message指定了消息的标识符。第三、第四个成员变量wParam和lParam,用于指定消息的附加信息。最后两个变量分别表示消息投递到消息队列中的时间和鼠标的当前位置。

typedef struct tagMSG {
    HWND hwnd;//消息所属的窗口
    UINT message;//消息的标识符
    WPARAM wParam;//指定消息的附加信息
    LPARAM lParam;//指定消息的附加信息
    DWORD time;//投递到消息队列中的时间
    POINT pt;//当前鼠标位置
} MSG;

DispatchMessage()

函数功能:该函数分发一个消息给窗口程序。通常消息从GetMessage函数获得或者TranslateMessage函数传递的。消息被分发到回调函数(过程函数),作用是消息传递给操作系统,然后操作系统去调用我们的回调函数,也就是说我们在窗体的过程函数中处理消息。

LONG DispatchMessage(
    CONST MSG*lpmsg//指向含有消息的MSG结构的指针。
);

DefWindowProc()

DefWindowProc函数调用缺省的窗口过程来为应用程序没有处理的任何窗口消息提供缺省的处理。该函数确保每一个消息得到处理。

LRESULT DefWindowProc(
    HWND hWnd,//指向接收消息的窗口过程的句柄。
    UINT Msg,//指定消息类型。
    WPARAM wParam,//指定其余的、消息特定的信息。该参数的内容与Msg参数值有关。
    LPARAM IParam  //指定其余的、消息特定的信息。该参数的内容与Msg参数值有关。
);

例子,在vc6++中编译

// 第一个程序.cpp : Defines the entry point for the application.
//
#include "stdafx.h"

LRESULT CALLBACK WndProc( //WndProc名称可自由定义
    HWND hwnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam
    );

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
     // TODO: Place code here.
    //用这种方式来打印
    /*
    char str[50];
    DWORD dwAddr = (DWORD)hInstance;
    sprintf(str,"模块地址:%x", dwAddr);
    OutputDebugString(str);
    */

    char szOutBuff[50];
    //1.第一步,定义你的窗口是怎么样的。
    TCHAR classname[]=TEXT("the_itach1");
    WNDCLASS wndclass = {0};
    wndclass.hbrBackground=(HBRUSH)COLOR_BACKGROUND;//窗口背景色
    wndclass.lpszClassName=classname;// 窗口类名
    wndclass.hInstance=hInstance;//实例句柄
    wndclass.lpfnWndProc=WndProc;//窗口处理函数
    if(!RegisterClass(&wndclass)) //注册一个窗口类,如果注册失败 发出警告
        {MessageBeep(0); return FALSE;}

    //2.第二步,创建并显示窗口
    HWND hWnd=CreateWindow(
        classname,
        TEXT("THE_ITACH1"),
        WS_OVERLAPPEDWINDOW,
        100,
        100,
        300,
        150,
        NULL,
        NULL,
        hInstance,
        NULL);
    if(hWnd==NULL)//如果错误,返回错误参数。
    {
        sprintf(szOutBuff,"error: %d", GetLastError());
        OutputDebugString(szOutBuff);
    }

    ShowWindow(hWnd,SW_SHOW);//显示这个窗口

    //3.第三步,接收消息并处理
    MSG msg;//定义MSG结构体,用来储存鼠标,键盘或者其他窗口产生的消息

    BOOL bRet;

    while((bRet=GetMessage(&msg,hWnd,0,0))!=0)//用来接收鼠标,键盘或者其他窗口产生的消息,放到msg结构体里面
    {
        if(bRet==-1)
        {
            sprintf(szOutBuff,"error: %d", GetLastError());
            OutputDebugString(szOutBuff);
        }
        else
        {
            TranslateMessage(&msg);//将消息转换,例如键盘码转为字符型。
            DispatchMessage(&msg);//将消息分发给内核,然后内核根据DefWindowProc来将消息传给应用程序进行处理。
        }

    }

    return 0;
}

LRESULT CALLBACK WndProc( //WndProc名称可自由定义
    HWND hwnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam
)
{
    return  DefWindowProc (hwnd, uMsg, wParam, lParam) ;//让系统来处理这些消息,下一节会讲自己来处理某些消息。
}

大致流程图

消息类型

主要就讲键盘类的,也就是MSG那个结构体,下面是一个例子

LRESULT CALLBACK WndProc( //WndProc名称可自由定义
    HWND hwnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam
)
{
    //    sprintf(szOutBuff,"消息: %x", uMsg);
    //    OutputDebugString(szOutBuff);

    switch (uMsg)
    {
    case WM_DESTROY://点击关闭按钮时退出
        {
            PostQuitMessage(0);
            return 0;
        }

    case WM_KEYDOWN://当键盘按下
        {

            return 0;
        }

    case WM_CHAR://记录键盘按下的是什么
        {
            char key[0x80];
            sprintf(key,"消息: %c", wParam);
            OutputDebugString(key);
            return 0;
        }

    }
    return  DefWindowProc (hwnd, uMsg, wParam, lParam) ;
}

子窗口

利用window已有的一些窗口类来创建一些子窗口,例如文本框和按钮等

例子,加在相应位置就行了。

//这3个是子窗口的编号,就相当于身份证一样
# define IDC_EDIT_1 0x100
# define IDC_BUTTON_1 0x101
# define IDC_BUTTON_2 0x102

char szOutBuff[50];
int i=0;
HINSTANCE g_hInstance;//由于hInstance在主函数是一个局部变量,而WndProc需要调用,所以设置为全局变量。

LRESULT CALLBACK WndProc(
    HWND hwnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_DESTROY://点关闭键退出
        {
            exit(0);
            return 0;
        }

    case WM_COMMAND://这个是如果点击了窗口
        {
            switch(LOWORD(wParam))//低16位就代表点击子窗口的编号。
            {
            case IDC_BUTTON_1:
                {
                    MessageBox(NULL,"听说点1314次就会有flag","hint",MB_OK);
                    break;
                }

            case IDC_BUTTON_2:
                {
                    i++;
                    if(i==1314)
                    {
                        sprintf(szOutBuff,"flag{love_you_%d}",i);
                        SetDlgItemText(hwnd,IDC_EDIT_1,szOutBuff);
                    }
                    break;
                }
            }

        }

    case WM_CREATE:
        {
            //创建一个文本框子窗口
            CreateWindow(
                "EDIT",
                "",
                WS_CHILD|WS_VISIBLE|ES_MULTILINE|WS_VSCROLL|WS_HSCROLL,
                0,
                0,
                500,
                360,
                hwnd,
                (HMENU)IDC_EDIT_1,
                g_hInstance,
                NULL);

            //创建两个按钮的子窗口
            CreateWindow(
                "BUTTON",
                "提示",
                WS_CHILD|WS_VISIBLE,
                510,
                200,
                40,
                30,
                hwnd,
                (HMENU)IDC_BUTTON_1,
                g_hInstance,
                NULL);

            CreateWindow(
                "BUTTON",
                "FLAG",
                WS_CHILD|WS_VISIBLE,
                510,
                250,
                40,
                30,
                hwnd,
                (HMENU)IDC_BUTTON_2,
                g_hInstance,
                NULL);
            break;
        }

    }
    return  DefWindowProc (hwnd, uMsg, wParam, lParam) ;
}

虚拟内存与物理内存

https://blog.csdn.net/lvyibin890/article/details/82217193

私有内存的申请与释放

之前我们用malloc申请的堆内存空间实际上是假申请,这些空间本来就已经申请好了的。而实际申请内存的方式有两种。

  1. 通过VitualAlloc/通过VitualAllocEx申请的:Private Memory
  2. 通过CreateFileMapping映射的:Mapped Memory

现在主要讲第一种,先来看看相关函数

VirtualAlloc()

该函数的功能是在调用进程的虚地址空间,预定或者提交一部分页。
简单点的意思就是申请内存空间

LPVOID VirtualAlloc{
    LPVOID lpAddress, // 要分配的内存区域的地址
    DWORD dwSize, // 分配的大小
    DWORD flAllocationType, // 分配的类型
    DWORD flProtect // 该内存的初始保护属性
};

VirtualAllocEx()API

指定进程的虚拟空间保留或提交内存区域

LPVOID VirtualAllocEx(
    HANDLE hProcess,
    LPVOID lpAddress,
    SIZE_T dwSize,
    DWORD flAllocationType,
    DWORD flProtect
);
hProcess:申请内存所在的进程句柄。

lpAddress:保留页面的内存地址;一般用NULL自动分配 。

dwSize:欲分配的内存大小,字节单位;注意实际分 配的内存大小是页内存大小的整数倍

flAllocationType
可取下列值:
MEM_COMMIT:为特定的页面区域分配内存中或磁盘的页面文件中的物理存储
MEM_PHYSICAL :分配物理内存(仅用于地址窗口扩展内存)
MEM_RESERVE:保留进程的虚拟地址空间,而不分配任何物理存储。保留页面可通过继续调用VirtualAlloc()而被占用
MEM_RESET :指明在内存中由参数lpAddress和dwSize指定的数据无效
MEM_TOP_DOWN:在尽可能高的地址上分配内存(Windows 98忽略此标志)
MEM_WRITE_WATCH:必须与MEM_RESERVE一起指定,使系统跟踪那些被写入分配区域的页面(仅针对Windows 98)

flProtect
可取下列值:
PAGE_READONLY: 该区域为只读。如果应用程序试图访问区域中的页的时候,将会被拒绝访
PAGE_READWRITE 区域可被应用程序读写
PAGE_EXECUTE: 区域包含可被系统执行的代码。试图读写该区域的操作将被拒绝。
PAGE_EXECUTE_READ :区域包含可执行代码,应用程序可以读该区域。
PAGE_EXECUTE_READWRITE: 区域包含可执行代码,应用程序可以读写该区域。
PAGE_GUARD: 区域第一次被访问时进入一个STATUS_GUARD_PAGE异常,这个标志要和其他保护标志合并使用,表明区域被第一次访问的权限
PAGE_NOACCESS: 任何访问该区域的操作将被拒绝
PAGE_NOCACHE: RAM中的页映射到该区域时将不会被微处理器缓存(cached)
注:PAGE_GUARD和PAGE_NOCHACHE标志可以和其他标志合并使用以进一步指定页的特征。PAGE_GUARD标志指定了一个防护页(guard page),即当一个页被提交时会因第一次被访问而产生一个one-shot异常,接着取得指定的访问权限。PAGE_NOCACHE防止当它映射到虚拟页的时候被微处理器缓存。这个标志方便设备驱动使用直接内存访问方式(DMA)来共享内存块。
返回值:
执行成功就返回分配内存的首地址,不成功就是NULL。

例子可以参考DLL注入文章。

VirtualFree()
VirtualFree该函数的功能是取消或者释放调用进程的虚地址空间页的一个区域。

BOOL VirtualFree(
    LPVOID lpAddress, // 区域地址
    SIZE_T dwSize,// 区域大小,字节
    DWORD dwFreeType // 类型
);

VirtualFreeEx()

VirtualFreeEx即为目标进程的句柄,可在其它进程中释放申请的虚拟内存空间。

BOOL VirtualFreeEx{
HANDLE hProcess, // 要释放内存所在进程的句柄
LPVOID lpAddress, // 区域地址
DWORD dwSize, // 区域大小,字节
DWORD dwFreeType //类型
};
如果 dwFreeType 为 MEM_RELEASE,则 dwSize 必须为0 . 按 VirtualAllocEx申请时的大小全部释放。
如果dwFreeType 为 MEM_DECOMMIT, 则释放从lpAddress 开始的一个或多个字节 ,即 lpAddress +dwSize。

文件系统

文件系统是操作系统用于明确存储设备(常见的是磁盘,也有基于NAND Flash的固态硬盘)或分区上的文件的方法和数据结构;即在存储设备上组织文件的方法。

卷相关API:

GetLogicalDrives()

作用为判断系统中存在哪些逻辑驱动器字母。

DWORD GetLogicalDrives(void);

一般情况下DWORD的数据长度是32位,在这个DWORD中,每一位对应了一个逻辑驱动器是否存在。第二位如果是“1”则表示驱动器“B:”存在,第四位如果是“1”则表示驱动器“D:”存在,以此类推。例如44,换为二进制00101100,就代表有C,D,F盘。
GetLogicalDriveStrings()

GetLogicalDriveStrings,获取一个字串,其中包含了当前所有逻辑驱动器的根驱动器路径。

DWORD GetLogicalDriveStrings(
    DWORD nBufferLength, // size of buffer
    LPTSTR lpBuffer // drive strings buffer
);
就是盘的字符串会存到lpBuffer里面,C:\ D:\ F:\

GetDriveType()

GetDriveType是程序中的一种函数类型,这个GetDriveType函数是用来确定磁盘驱动器是可移动的、固定的、CD-ROM、RAM磁盘还是网络驱动器。

UINT GetDriveType(
    LPCTSTR lpRootPathName //根目录路径的字符串指针
);
返回值,宏,是数字,对应下面的类型
DRIVE_UNKNOWN 未知的磁盘类型
DRIVE_NO_ROOT_DIR 说明lpRootPathName是无效的
DRIVE_REMOVABLE 可移动磁盘
DRIVE_FIXED 固定磁盘
DRIVE_REMOTE 网络磁盘
DRIVE_CDROM 光驱
DRIVE_RAMDISK 为RAM