CPP Windows编程 (二)

148 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第6天,点击查看活动详情

创建动态链接库

动态链接库dll,是Dynamic Link Libarary的缩写

Dll包含许多公用的代码、数据等,可供其他模块(DLL或exe文件等)使用。

DLL中并不是所有的函数都必须供其他函数使用,只有经过导出(export)后才被允许正常调用。

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"

int my_export1(int a,int b){
    return a+b;
}


BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}
// pch.h: 这是预编译标头文件。
// 下方列出的文件仅编译一次,提高了将来生成的生成性能。
// 这还将影响 IntelliSense 性能,包括代码完成和许多代码浏览功能。
// 但是,如果此处列出的文件中的任何一个在生成之间有更新,它们全部都将被重新编译。
// 请勿在此处添加要频繁更新的文件,这将使得性能优势无效。

#ifndef PCH_H
#define PCH_H

// 添加要在此处预编译的标头
#include "framework.h"
extern "C" _declspec(dllexport) int my_export(int a,int b); 
#endif //PCH_H

导入动态链接库

对于dll项目,编译器会产生两个文件,分别时.DLL和.LIB其中dll时动态链接库,保存了所有代码和数据,lib是导入库,保存了一些符号和地址的对应信息。不能单独使用,必须和dll共存,用于动态链接库的隐式链接。

隐式导入

#pragma once
#pragma comment(lib,"DLL1.lib")
extern "C" int  export_add(int a, int b);
	
#include"main.h"
#include <iostream>

int main()
{
    std::cout << export_add(2, 3) << std::endl;
}

显示导入

#include"main.h"
#include <iostream>
#include<Windows.h>
typedef int  (* export_add)(int a, int b);
int main()
{
    HMODULE hModule = LoadLibraryW(L"DLL1.dll");
    export_add func = (export_add)GetProcAddress(hModule, "export_add");
    std::cout << func(2, 3) << std::endl;
    FreeLibrary(hModule);
}

静态库

静态库又叫对象库,其中的代码在运行链接器进行静态链接时添加到exe程序中,静态链接库最终会与调用程序融为一体。其文件后缀也是.lib。

静态链接库在调用时只能使用隐式链接。静态链接库在程序编译前,在链接过程会被直接添加到目标应用程序中,会加大应用程序的体积。

Windows进程线程

Windows是一个面向对象的操作系统,Windows终端 一个个对象本质上都是结构体变量,windows系统不希望用户直接访问这些结构体变量(这些结构体变量在内核层,即R0中),所以使用时都需要先得到他们的句柄(Handler,不等同于指针)然后再调用相应的API去操作对象

Windows对象大致分为三大类

  1. USER对象:窗口、控件、图标、菜单、光标。user32.dll
  2. GDI对象(绘图用):画刷、子图、画笔 GDI32.dll
  3. 内核对象:文件、进程、线程。* Kernel32.dll*

内核对象的创建方式:

  1. 创建内核对象CreateXXX.

(eg. CreateProcess 创建进程。CreateThread创建线程。CreateFile创建文件。CreateSemaphore创建信号量)

  1. 打开内核对象获取句柄:OpenXXX
  2. 通过API操作内核对象。
  3. 关闭句柄CloseHandle

内核对象特性:

所有的内核对象都属于操作系统内核,可以在不同的进程间访问到(* 内核对象是跨进程的 *)

很多时候我们都需要在不同的进程中访问同一个内核对象,比如进程间的同步,进程间共享数据等。

通常,我们使用命名的方式在不同进程间使用内核对象。

每一个内核对象结构体都有一个* 引用计数属性 *,当有一个进程创建或者打开了此内核对象,那么内核对象的引用计数就会+1,进程终止,或者关闭,引用计数-1,当引用计数为0,则对象销毁。

内核对象都一个安全描述符。这个描述符说明了谁拥有此内核对象,哪些用户组合用户允许访问此对象,以及哪些用户和组拒绝访问此对象(权限)

在创建一个内核对象的时候,需要我们传递一个安全描述符(可以据此判断创建的对象是不是内核对象)* 安全描述符是一个结构体*

Typedef stuct _SECURITY_ATTRIBUTES
{
    DWORD nLength;结构体大小
    LPOVID lpSecurityDescriptor;安全描述符
    BOOL bInheritHandle;能否被新创建的进程继承。
}

一般情况下,在创建进程内核对象的时候可以给安全描述符一个null,这意味着此内核对象将使用与当前令牌相关的默认安全属性。

在Windows操作系统中,使用对象就要使用句柄,对于内核对象来说,也是这样。内核对象的句柄和进程相关的,同一个对象在不同进程中,其句柄值是不同的。这点和GDI对象不同,GDI对象的句柄值,全局有效。由此可见不同的类型的对象其管理方式也不同。

句柄表: 在每一个进程对象中,都有一个句柄表,用于记录本进程中所有打开的内核对象,可以简单的将句柄表理解为一个一维数组,句柄值可以理解为数组索引。

句柄表中的每一项,描述了使用此句柄访问对象的权限,以及此句柄是否可以被子进程继承。

内核对象的跨进程访问

由于内核对象是操作系统全局的,因此我们可以在多个程序中访问同一个内核对象。

通常有3中方式实现跨进程访问内核对象。

  1. 由父进程继承给子进程
  2. 使用名称或者ID作为标识,打开一个内核对象
  3. 使用DuplicateHandle函数,将一个句柄从一个进程传递给另一个进程。

什么是进程

进程可以理解为存放资源的容器。

进程内核对象放在高地址中。

#include<Windows.h>
int main()
{
    CreateProcessW(
        LPCWSTR lpApplicationName, //要打开的程序路径
        LPWSTR  lpCommandLine, //命令行
        LPSECURITY_ATTRIBUTES lpProcessAttributes,//进程安全描述符
        LPSECURITY_ATTRIBUTES lpThreadAttributes,//线程安全描述符
        BOOL bInheritHandles, //是否可被继承
        DWORD dwCreationFlags,//创建标识
        LPVOID lpEnviroment, //指向新进程环境块的指针,往往设置为null
        LPCWSR lpCurrentDirectory,//当前路径
        LPSTARTUPINFOW lpStartupInfo,//进程启动信息
        LPPROCESS_INFOMATION lpProcessInformation//进程信息
    );
    return 0;
}

什么是模块

进程相关操作

#include<Windows.h>
#include<TlHelp32.h>
#include<iostream>
int main() {
	setlocale(LC_ALL, "chs");
	//打开一个进程获取句柄
	HANDLE hprocess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 8360);
	//结束一个句柄
	TerminateProcess(hprocess,0);
	//第二个参数只有在遍历堆或者模块的时候需要,需要指定进程ID
	HANDLE hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	PROCESSENTRY32W processEntry = { sizeof(PROCESSENTRY32W) };
	BOOL bSuccess = Process32FirstW(hSnapShot, &processEntry);
	if (bSuccess) {
		do {
			printf("进程ID:%d,进程名称:%ls \n", processEntry.th32ProcessID, processEntry.szExeFile);
		} while (Process32NextW(hSnapShot, &processEntry));
	}
	return 0;
}

进程间通信

每个进程都由自己独立的4G内存控件,彼此是不能够直接互相访问的。如果需要进行通信需要使用一些方法。

1 COPY_DATA方式

WM_COPYDATA是一个特殊的,专门用于传递数据的消息,这个消息可以携带一个大体积的消息参数,不同于其他只能携带两个固定参数的消息。

在发送WM_COPYDATA消息时,Wparam应该保存有发送此消息的窗口句柄,LParam则应该指向一个名为COPYDATASTRUCT的结构体:

#include<Windows.h>

int main() {
	HWND hWnd = FindWindow(NULL, L"DRAGON");
	COPYDATASTRUCT sendData = { 0 };
	sendData.cbData = sizeof(L"HOLY DRAGON");
	sendData.lpData = (LPVOID)L"HOLY DRAGON";
	SendMessage(hWnd, WM_COPYDATA, 0, (LPARAM)&sendData);
	return 0;
}
LRESULT CALLBACK WinProc(
		HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam
	) {
		static HINSTANCE hInstance = GetModuleHandleW(NULL);
		switch (uMsg) {
		case WM_COPYDATA: {
			PCOPYDATASTRUCT pCopyData = (PCOPYDATASTRUCT)lParam;
			MessageBox(hWnd, (LPCWSTR)pCopyData->lpData, L"提示", MB_OK);
			break;
		}
        }
}

2 邮槽的方式

邮槽是Windows系统中最简单的一种进程通信方式,一个进程可以创建一个邮槽,其他进程可以通过打开此邮槽与该进程通信。

邮槽的通讯是单向的,只有服务端才能从邮槽中读取信息,客户端只能写入,消息被写入后以队列的方式保存(先进先出)

邮槽除了可以在本机内进程通讯外,还可以在主机之间进程通讯(使用UDP协议),想要通过网络进行通讯必须知道服务端的主机名和域名。

#include<Windows.h>

int main()
{
	HANDLE hMailslot = CreateMailslot(L"\\.\mailslot\dragon", 100, MAILSLOT_WAIT_FOREVER, NULL);
	if (hMailslot == INVALID_HANDLE_VALUE) {
		MessageBox(0, L"打开邮槽失败", L"提示", MB_OK);
		return 0;
	}
	WCHAR buff[50] = { 0 };
	DWORD readSize;
	ReadFile(hMailslot, buff, 100,&readSize, NULL);
	MessageBox(0, buff, L"提示", MB_OK);
	CloseHandle(hMailslot);
	return 0;
}
#include<Windows.h>

int main()
{
	HANDLE hMailslot = CreateFile(L"\\.\mailslot\dragon", GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hMailslot == INVALID_HANDLE_VALUE) {
		MessageBox(0, L"打开邮槽失败", L"提示", MB_OK);
		return 0;
	}
	WCHAR buff[] = L"dr@g0n";
	DWORD sz;
	WriteFile(hMailslot, buff, sizeof(buff), &sz, NULL);
	CloseHandle(hMailslot);
	return 0;
}

线程相关操作

创建线程函数CreateThread.

HANDLE CreateThread(
  [in, optional]  LPSECURITY_ATTRIBUTES   lpThreadAttributes,
  [in]            SIZE_T                  dwStackSize,
  [in]            LPTHREAD_START_ROUTINE  lpStartAddress,
  [in, optional]  __drv_aliasesMem LPVOID lpParameter,
  [in]            DWORD                   dwCreationFlags,
  [out, optional] LPDWORD                 lpThreadId
);

#include<Windows.h>
#include<TlHelp32.h>
#include<iostream>

int main() {
    /*遍历线程*/
	//创建线程快照
	HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
	//创建结构体
	THREADENTRY32 threadEntry = {sizeof(THREADENTRY32)};
	//获取第一个线程
	BOOL FLAG = Thread32First(hSnapshot,&threadEntry);
	if (FLAG) {
		do {
			printf("当前线程ID:%d 当前线程所属进程ID:%d\n", threadEntry.th32ThreadID, threadEntry.th32OwnerProcessID);
    		if(threadEntry.th32OwnerProcessID==7926){
                HANDLE hThreadHandle = OpenThread(THREAD_ALL_ACCESS,NULL,threadEntry.th32ThreadID);
                //SuspendThread(hThreadHandle); 暂停7926进程下的线程
                ResumeThread(hThreadHandle);//恢复7926进程下的线程
                CloseHandle(hThreadHandle;
            }
        } while (Thread32Next(hSnapshot, &threadEntry));
	}
	return 0;
}

GetCurrentThread函数获取当前线程句柄(* 然而此句柄是伪句柄 *)要想将主线程句柄传递给子线程不能通过值传递的方式,而是需要通过DuplicateHandle函数

多线程同步

#include<Windows.h>
#include<iostream>

LONG g_count = 0;

DWORD WINAPI ThreadProc1(LPVOID lpParameter) {
	for (size_t i=0; i < 100000; i++) {
		g_count++;
	}
	return 0;
}

DWORD WINAPI ThreadProc2(LPVOID lpParameter) {
	for (size_t i=0; i < 100000; i++) {
		g_count++;

	}	return 0;
}


int main()
{
	HANDLE hThread1 = CreateThread(
		NULL,
		NULL,
		ThreadProc1,
		0,
		0,
		NULL
	);
	HANDLE hThread2 = CreateThread(
		NULL,
		NULL,
		ThreadProc2,
		0,
		0,
		NULL
	);
	WaitForSingleObject(hThread1, -1);
	WaitForSingleObject(hThread2, -2);
	printf("gcount:%d\n", g_count++);
	CloseHandle(hThread1);
	CloseHandle(hThread2);
	return 0;
}

上述代码每次执行结果不同。

#include<Windows.h>
#include<iostream>

LONG g_count = 0;

DWORD WINAPI ThreadProc1(LPVOID lpParameter) {
	for (size_t i=0; i < 100000; i++) {
		InterlockedIncrement(&g_count);
	}
	return 0;
}

DWORD WINAPI ThreadProc2(LPVOID lpParameter) {
	for (size_t i=0; i < 100000; i++) {
		InterlockedIncrement(&g_count);
	}	return 0;
}


int main()
{
	HANDLE hThread1 = CreateThread(
		NULL,
		NULL,
		ThreadProc1,
		0,
		0,
		NULL
	);
	HANDLE hThread2 = CreateThread(
		NULL,
		NULL,
		ThreadProc2,
		0,
		0,
		NULL
	);
	WaitForSingleObject(hThread1, -1);
	WaitForSingleObject(hThread2, -2);
	printf("gcount:%d\n", g_count++);
	CloseHandle(hThread1);
	CloseHandle(hThread2);
	return 0;
}

要使用InterlockedIncrement,锁住g_count操作

使用临界区给特定代码上锁

#include<Windows.h>
#include<iostream>

LONG g_count = 0;
CRITICAL_SECTION criticalSection = { 0 };
DWORD WINAPI ThreadProc1(LPVOID lpParameter) {
	for (size_t i=0; i < 100000; i++)
		EnterCriticalSection(&criticalSection);
		g_count++;
		LeaveCriticalSection(&criticalSection);
	}
	return 0;
}

DWORD WINAPI ThreadProc2(LPVOID lpParameter) {
	for (size_t i=0; i < 100000; i++) {
		EnterCriticalSection(&criticalSection);
		g_count++;
		LeaveCriticalSection(&criticalSection);
	}	return 0;
}


int main()
{
	InitializeCriticalSection(&criticalSection);
	HANDLE hThread1 = CreateThread(
		NULL,
		NULL,
		ThreadProc1,
		0,
		0,
		NULL
	);
	HANDLE hThread2 = CreateThread(
		NULL,
		NULL,
		ThreadProc2,
		0,
		0,
		NULL
	);
	WaitForSingleObject(hThread1, -1);
	WaitForSingleObject(hThread2, -2);
	printf("gcount:%d\n", g_count++);
	CloseHandle(hThread1);
	CloseHandle(hThread2);
	DeleteCriticalSection(&criticalSection);
	return 0;
}

#include<Windows.h>
#include<iostream>

LONG g_count = 0;
HANDLE g_handle;
DWORD WINAPI ThreadProc1(LPVOID lpParameter) {
	for (size_t i=0; i < 100000; i++) {
		WaitForSingleObject(g_handle,-1);
		g_count++;
		ReleaseMutex(g_handle);
	}
	return 0;
}

DWORD WINAPI ThreadProc2(LPVOID lpParameter) {
	for (size_t i=0; i < 100000; i++) {
		WaitForSingleObject(g_handle, -1);
		g_count++;
		ReleaseMutex(g_handle);
	}	return 0;
}


int main()
{
	HANDLE hThread1 = CreateThread(
		NULL,
		NULL,
		ThreadProc1,
		0,
		0,
		NULL
	);
	HANDLE hThread2 = CreateThread(
		NULL,
		NULL,
		ThreadProc2,
		0,
		0,
		NULL
	);
	g_handle = CreateMutex(NULL, false, L"Dragon");
	WaitForSingleObject(hThread1, -1);
	WaitForSingleObject(hThread2, -1);

	printf("gcount:%d\n", g_count++);
	CloseHandle(hThread1);
	CloseHandle(hThread2);
	return 0;
}

使用事件给特定代码上锁,可以使不同线程按指定顺序执行。

#include<Windows.h>
#include<iostream>

LONG g_count = 0;
HANDLE hEvent1;
HANDLE hEvent2;
HANDLE hEvent3;
DWORD WINAPI ThreadProc1(LPVOID lpParameter) {
		WaitForSingleObject(hEvent1,-1);
		printf("线程1启动\n");
		SetEvent(hEvent2);
	return 0;
}

DWORD WINAPI ThreadProc2(LPVOID lpParameter) {
		WaitForSingleObject(hEvent2, -1);
		printf("线程2启动\n");
		SetEvent(hEvent3);
		return 0;
}
DWORD WINAPI ThreadProc3(LPVOID lpParameter) {
		WaitForSingleObject(hEvent3, -1);
		printf("线程3启动\n");
		return 0;
}

int main()
{
	hEvent1 = CreateEvent(NULL, false, false, L"D1");
	hEvent2 = CreateEvent(NULL, false, false, L"D2");
	hEvent3 = CreateEvent(NULL, false, false, L"D3");
	SetEvent(hEvent1);
	HANDLE hThread1 = CreateThread(
		NULL,
		NULL,
		ThreadProc1,
		0,
		0,
		NULL
	);
	HANDLE hThread2 = CreateThread(
		NULL,
		NULL,
		ThreadProc2,
		0,
		0,
		NULL
	);
	HANDLE hThread3 = CreateThread(
		NULL,
		NULL,
		ThreadProc3,
		0,
		0,
		NULL
	);


	WaitForSingleObject(hThread1, -1);
	WaitForSingleObject(hThread2, -1);
	WaitForSingleObject(hThread3, -1);

	printf("gcount:%d\n", g_count++);
	CloseHandle(hThread1);
	CloseHandle(hThread2);
	CloseHandle(hThread3);

	return 0;
}s

信号量