开启掘金成长之旅!这是我参与「掘金日新计划 · 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对象大致分为三大类
- USER对象:窗口、控件、图标、菜单、光标。user32.dll
- GDI对象(绘图用):画刷、子图、画笔 GDI32.dll
- 内核对象:文件、进程、线程。* Kernel32.dll*
内核对象的创建方式:
- 创建内核对象CreateXXX.
(eg. CreateProcess 创建进程。CreateThread创建线程。CreateFile创建文件。CreateSemaphore创建信号量)
- 打开内核对象获取句柄:OpenXXX
- 通过API操作内核对象。
- 关闭句柄CloseHandle
内核对象特性:
所有的内核对象都属于操作系统内核,可以在不同的进程间访问到(* 内核对象是跨进程的 *)
很多时候我们都需要在不同的进程中访问同一个内核对象,比如进程间的同步,进程间共享数据等。
通常,我们使用命名的方式在不同进程间使用内核对象。
每一个内核对象结构体都有一个* 引用计数属性 *,当有一个进程创建或者打开了此内核对象,那么内核对象的引用计数就会+1,进程终止,或者关闭,引用计数-1,当引用计数为0,则对象销毁。
内核对象都一个安全描述符。这个描述符说明了谁拥有此内核对象,哪些用户组合用户允许访问此对象,以及哪些用户和组拒绝访问此对象(权限)
在创建一个内核对象的时候,需要我们传递一个安全描述符(可以据此判断创建的对象是不是内核对象)* 安全描述符是一个结构体*
Typedef stuct _SECURITY_ATTRIBUTES
{
DWORD nLength;结构体大小
LPOVID lpSecurityDescriptor;安全描述符
BOOL bInheritHandle;能否被新创建的进程继承。
}
一般情况下,在创建进程内核对象的时候可以给安全描述符一个null,这意味着此内核对象将使用与当前令牌相关的默认安全属性。
在Windows操作系统中,使用对象就要使用句柄,对于内核对象来说,也是这样。内核对象的句柄和进程相关的,同一个对象在不同进程中,其句柄值是不同的。这点和GDI对象不同,GDI对象的句柄值,全局有效。由此可见不同的类型的对象其管理方式也不同。
句柄表: 在每一个进程对象中,都有一个句柄表,用于记录本进程中所有打开的内核对象,可以简单的将句柄表理解为一个一维数组,句柄值可以理解为数组索引。
句柄表中的每一项,描述了使用此句柄访问对象的权限,以及此句柄是否可以被子进程继承。
内核对象的跨进程访问
由于内核对象是操作系统全局的,因此我们可以在多个程序中访问同一个内核对象。
通常有3中方式实现跨进程访问内核对象。
- 由父进程继承给子进程
- 使用名称或者ID作为标识,打开一个内核对象
- 使用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
信号量