windows10 记事本进程 键盘消息钩子 dll注入

windows10 记事本进程 键盘消息钩子 dll注入看了很多文档,垮了很多坎,终于完成了这个demo;有很多个人理解,可能不完全正确,见谅;先上实现的图片:如图,我通过SetWindowsHookEx()函数向记事本进程中当前窗口线程注入了自己写的dll,dll中设置的回调函数使,当键盘按了1,那么就会触发一个MessageBox。工具:VS2015,PCHunter(用于查看是否成功注入了dll,其实看能否实现功能就信…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

看了很多文档,垮了很多坎,终于完成了这个demo;

有很多个人理解,可能不完全正确,见谅;

先上实现的图片:

windows10 记事本进程 键盘消息钩子 dll注入

windows10 记事本进程 键盘消息钩子 dll注入

如图,我通过SetWindowsHookEx()函数向记事本进程中当前窗口线程注入了自己写的dll,dll中设置的回调函数使,当键盘按了1,那么就会触发一个MessageBox。

工具:VS 2015, PCHunter(用于查看是否成功注入了dll,其实看能否实现功能就信,非必须的)

思路:先写一个dll(就是要被注入的dll),再写一个windows控制台程序(用于将dll注入到我们想要注入的进程)

接下来我们一步步实现看看:

一、DLL编写

1、打开VS新建一个名为DLL的Win32 项目:

windows10 记事本进程 键盘消息钩子 dll注入

2、在应用程序向导中选中DLL、空项目(空项目比较干净,没有多余的东西):

windows10 记事本进程 键盘消息钩子 dll注入

3、创完了项目,先别急着写代码,还有很多必要的动西要改,右键点击项目->属性。将MFC的使用改为“在共享DLL中使用MFC”,原因是dll中会用到CString类型,要加入#include <afx.h>这个头文件,如果不设置MFC的话,之后编译会报错;将字符集改为“使用多字节字符集”,及ANSI,原因是在ANSI和Unicode下,CSting的存储结构是不同的,前者是char *,后者是wchar_t *,而且字符集不同,有些函数的参数也会跟着变,这个后面会说。

windows10 记事本进程 键盘消息钩子 dll注入

4、如图点击配置管理器:

windows10 记事本进程 键盘消息钩子 dll注入

5、将Debug配置的平台改为64位,原因是:我的windows是64位的,记事本软件也是64位的(虽然它的执行文件在System32文件夹下,但是用PCHunter可以看到它是64位的程序),而我们最重要的注入函数SetWindowsHookEx()的官网文档说了,这个函数只能用于64位程序将64位dll注入64位程序,或32位程序将32位dll注入32位程序,如果我们编写的dll是32位的,那么到时候注入时程序就会卡死(别问我为什么知道),也就是注入失败了,再给个官方文档地址点击打开链接

windows10 记事本进程 键盘消息钩子 dll注入

6、在源文件目录下新建一个名为DLL的cpp文件:

windows10 记事本进程 键盘消息钩子 dll注入

7、现在我们可以写代码了:

#include <afx.h> //CString的头文件
#include "stdio.h"
#include "windows.h" //要调用的很多windows api函数的头文件

HHOOK g_hHook = NULL; //HHOOK是钩子句柄,如果想搭建钩子链,也可把下一个需要传给的钩子句柄放在这。

CString IsNumber(WPARAM wParam)
{
	CString message;
	switch (wParam) {
	case 0x30: message.Format("按了0"); break;
	case 0x31: message.Format("按了1"); break;
	case 0x32: message.Format("按了2"); break;
	case 0x33: message.Format("按了3"); break;
	case 0x34: message.Format("按了4"); break;
	case 0x35: message.Format("按了5"); break;
	case 0x36: message.Format("按了6"); break;
	case 0x37: message.Format("按了7"); break;
	case 0x38: message.Format("按了8"); break;
	case 0x39: message.Format("按了9"); break;
	default: message.Format("未定义的按键"); break;
	}
	return message;
}
//获取到的wparam是16位的int(也可能是long,这个无所谓),用于标识键盘截取到的消息是哪个键,我简单的
//识别了键盘上的数组键(不是小键盘的数组键),返回CString对象。

LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
//这是一个键盘钩子消息的回调函数,当设置钩子成功,dll被注入到目标线程,该回调函数会在每次有键盘消息
//传递给目标线程时被调用,第二个参数在这个类型的钩子中放回的是虚拟键盘的信息,其他两个参数我不太清楚
{
	MessageBox(NULL, IsNumber(wParam), _T("Message"), 0);
	return CallNextHookEx(g_hHook, nCode, wParam, lParam);
	//我理解这句代码意思是,如果g_hHook非空,就把消息传给这个句柄,否则就传给应用程序。
}

KeyboardProc,官方文档有解释:点击打开链接,关于KeyboardProc中wparam参数返回的信息:点击打开链接

我可能也有很多地方没理解对,有能力尽量看官方文档。

8、在源文件目录下新建一个名为DLL的def文件:

windows10 记事本进程 键盘消息钩子 dll注入

9、添加如下代码,可以将KeyboardProc函数导出:

LIBRARY DLL

EXPORTS
KeyboardProc

 整个项目下只有“源文件”下的两个文件:

windows10 记事本进程 键盘消息钩子 dll注入

10、点击最上方的生成->生成解决方案,成功的话,找到DLL->x64->Debug这个文件夹,看下有没有DLL.dll这个文件,注意:不是DLL->DLL->x64->Debug这个文件夹,不要问我为什么会知道。

至此第一部分就算完成了,我们得到了DLL.dll这个文件。

二、CPP编写

1、打开VS新建一个名为CPP的Win 32控制台应用程序:

windows10 记事本进程 键盘消息钩子 dll注入

2、之后的设置都是默认的(之前写dll选了空项目,写cpp就不用了)。

3、4、5、全部参考第一部分。

6、在源文件目录下的CPP.cpp文件添加代码:

#include "stdafx.h"
#include "windows.h"
#include "Psapi.h" //连接了库后引用头文件,EnumProcesses及GetModuleFileNameEx都需要引入这个头文件
#pragma comment(lib,"Psapi.lib") //预编译指令,连接psapi.lib库

DWORD FindProcessByEnumProcess(CString TargetProcessName)
//参数是目标程序名,如notepad.exe
//返回值类型DWORD,是32位的long型,值是找到的目标进程的进程id, 如果打开了多个同名程序,找到的是最后打开的那个进程的进程id
{
	DWORD TargetProcessId = 0; //目标进程初始值是0,没找到时就返回0
	DWORD ProcessesId[1024] = { 0 }; //进程id数组,在之后EnumProcesses函数调用会将当前所有进程id放入数组
	DWORD NeededProcessesId = 0; //在之后EnumProcesses函数调用后会将实际需要的进程数组的大小赋值给它
	LPSTR ProcessName = (LPSTR)malloc((sizeof(char)) * 1024);
	//LPSTR定义是typedef LPSTR char * ,LPSTR被定义成是一个指向以NULL(‘
#include "stdafx.h"
#include "windows.h"
#include "Psapi.h" //连接了库后引用头文件,EnumProcesses及GetModuleFileNameEx都需要引入这个头文件
#pragma comment(lib,"Psapi.lib") //预编译指令,连接psapi.lib库
DWORD FindProcessByEnumProcess(CString TargetProcessName)
//参数是目标程序名,如notepad.exe
//返回值类型DWORD,是32位的long型,值是找到的目标进程的进程id, 如果打开了多个同名程序,找到的是最后打开的那个进程的进程id
{
DWORD TargetProcessId = 0; //目标进程初始值是0,没找到时就返回0
DWORD ProcessesId[1024] = { 0 }; //进程id数组,在之后EnumProcesses函数调用会将当前所有进程id放入数组
DWORD NeededProcessesId = 0; //在之后EnumProcesses函数调用后会将实际需要的进程数组的大小赋值给它
LPSTR ProcessName = (LPSTR)malloc((sizeof(char)) * 1024);
//LPSTR定义是typedef LPSTR char * ,LPSTR被定义成是一个指向以NULL(‘\0’)结尾的32位ANSI字符数组指针
//用于存储返回到的进程名
EnumProcesses(ProcessesId, sizeof(ProcessesId), &NeededProcessesId);
//查询所有当前进程
//第一个参数是输出参数,返回进程数组存储到ProcessesId[1024]中
//第二个参数的输入参数,输入需要返回的进程数组的存储大小
//第三个参数的输出参数,返回实际需要的进程数组的存储大小
DWORD ProcessNumber = NeededProcessesId / sizeof(DWORD);
//得到进程个数,用于遍历
for (unsigned int i = 0; i < ProcessNumber; i++)
{
if (ProcessesId[i] != 0)
{
//对每个进程id执行下面操作
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, ProcessesId[i]);
//HANDLE是进程句柄类型,hProcess存储的就是进程句柄了
//OpenProcess函数通过进程id获取进程句柄
//第一个参数是输入参数,标识需要获取的权限,这里我们获取PROCESS_QUERY_INFORMATION和PROCESS_VM_READ权限
//因为下面的GetModuleFileNameEx函数指定要这两个权限
//第二个参数是输入参数,用来标识该句柄是否希望被子进程继承,不过不考虑子进程的继承权限则直接赋值为FALSE
//第三个参数是输入参数,输入需要打开进程的进程id
//返回值就是得到的句柄了
if (hProcess != NULL)
{
GetModuleFileNameEx(hProcess, NULL, ProcessName, 1024);
//根据进程句柄获取到进程完整的名称,如C:\Windows\System32\notepad.exe
//第一个参数是输入参数,输入需要获取进程名的进程句柄
//第二个参数是输入参数,输入需要获取的模块的模块句柄,为NULL表示获取进程主模块
//第三个参数是输出参数,输出进程模块完整的名称
//第四个参数是输入参数,表明ProcessName的存储大小
CString ProcessFullPathName = (CString)ProcessName;
//把LPSTR类型转为CString类型,便于进行字符处理
//CString在ANSI字符集下以存储char数组,在Unicode字符集下以存储wchar_t数组,后者的长度是前者的两倍
//CString a,则a可作为指向存储的char数组的头部的指针,和LPSTR类型是一样的,所以我用了强转
//我百度到的转化方法是这么写的:CString ProcessFullPathName(ProcessName); 也可以
CString ProcessBaseName = ProcessFullPathName.Right(ProcessFullPathName.GetLength() - ProcessFullPathName.ReverseFind('\\') - 1);
//把路径去掉,留下一个基础名称及C:\Windows\System32\notepad.exe转为notepad.exe
if (ProcessBaseName == TargetProcessName)
//如果该进程名与目标进程名相同,那么该进程id就是目标进程id
{
TargetProcessId = ProcessesId[i];
}
CloseHandle(hProcess);//关闭句柄
hProcess = NULL;
}
}
}
return TargetProcessId;
}
void DoInject(DWORD TargetWindowThreadId)
{
HMODULE hDll = LoadLibrary(_T("DLL.dll"));
//HMODULE是模块句柄类型
//LoadLibrary可以显示加载dll
//这里我没有加路径,所有执行前要将dll放到exe文件同目录下
if (hDll == NULL) {
printf("将dll加载到自身进程失败\n");
exit(0);
}
else {
printf("将dll加载到自身进程成功\n");
}
FARPROC KeyboardProc = (FARPROC)GetProcAddress(hDll, "KeyboardProc");
//通过GetProcAddress函数获取到hDll句柄中的KeyboardProc函数的地址
if (KeyboardProc == NULL) {
printf("获取到回调函数地址失败\n");
exit(0);
}
else {
printf("获取到回调函数地址成功\n");
}
HHOOK g_hHook = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)KeyboardProc, hDll, TargetWindowThreadId);
//将dll注入目标线程,设置函数指针指向写好的键盘消息回调函数
//第一个参数输入钩子类型
//第二个参数根据不同的钩子类型,要输入不同类型的回调函数地址
//第三个参数输入dll句柄
//第四个参数输入目标线程id
if (g_hHook) {
printf("向目标线程添加钩子并注入dll成功\n");
}
printf("输入q卸载钩子:");
while (getchar() != 'q');
if (g_hHook)
{
UnhookWindowsHookEx(g_hHook);
g_hHook = NULL;
}
//卸载钩子
FreeLibrary(hDll);//释放dll
}
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
//EnumWindows设置的回调函数,系统每发现一个窗口都会调用该回调函数
//HWND是窗口句柄类型
//第一个参数返回的是当前窗口句柄,第二个参数类型可以自己定,我传入的是目标进程id
{
DWORD CurrentWindowProcessId; //当前窗口进程id
DWORD CurrentWindowThreadId; //当前窗口线程id
CurrentWindowThreadId = GetWindowThreadProcessId(hwnd, &CurrentWindowProcessId);
//GetWindowThreadProcessId()可以通过窗口句柄,获取该窗口的所在的进程及线程
//第一个参数是输入参数,输入目标窗口句柄
//第二个参数是输出参数,类型是LPDWORD,及指向DWORD的指针,所以要取地址,函数执行成功后CurrentWindowProcessId值就是返回的当前窗口进程id
//返回值是值传递的,DWORD类型,直接赋值给DWORD类型就行了,值是当前窗口线程id
if (CurrentWindowProcessId == lParam) {
//如果当前窗口进程id等于目标进程的进程id
//则得到的当前窗口线程id就是目标窗口线程id
DoInject(CurrentWindowThreadId);
//得到了线程id后就可以注入了
return false; //当找到后就返回false,这样才会终止遍历
}
return true; //不是当前窗口,返回true,继续遍历
}
int main()
{
CString TargetProcessName(_T("notepad.exe"));
DWORD TargetProcessId = FindProcessByEnumProcess(TargetProcessName); //先找到目标进程id
EnumWindows(EnumWindowsProc, TargetProcessId); //根据目标进程id进行遍历,找到目标线程,并注入dll
}
’)结尾的32位ANSI字符数组指针 //用于存储返回到的进程名 EnumProcesses(ProcessesId, sizeof(ProcessesId), &NeededProcessesId); //查询所有当前进程 //第一个参数是输出参数,返回进程数组存储到ProcessesId[1024]中 //第二个参数的输入参数,输入需要返回的进程数组的存储大小 //第三个参数的输出参数,返回实际需要的进程数组的存储大小 DWORD ProcessNumber = NeededProcessesId / sizeof(DWORD); //得到进程个数,用于遍历 for (unsigned int i = 0; i < ProcessNumber; i++) { if (ProcessesId[i] != 0) { //对每个进程id执行下面操作 HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, ProcessesId[i]); //HANDLE是进程句柄类型,hProcess存储的就是进程句柄了 //OpenProcess函数通过进程id获取进程句柄 //第一个参数是输入参数,标识需要获取的权限,这里我们获取PROCESS_QUERY_INFORMATION和PROCESS_VM_READ权限 //因为下面的GetModuleFileNameEx函数指定要这两个权限 //第二个参数是输入参数,用来标识该句柄是否希望被子进程继承,不过不考虑子进程的继承权限则直接赋值为FALSE //第三个参数是输入参数,输入需要打开进程的进程id //返回值就是得到的句柄了 if (hProcess != NULL) { GetModuleFileNameEx(hProcess, NULL, ProcessName, 1024); //根据进程句柄获取到进程完整的名称,如C:\Windows\System32\notepad.exe //第一个参数是输入参数,输入需要获取进程名的进程句柄 //第二个参数是输入参数,输入需要获取的模块的模块句柄,为NULL表示获取进程主模块 //第三个参数是输出参数,输出进程模块完整的名称 //第四个参数是输入参数,表明ProcessName的存储大小 CString ProcessFullPathName = (CString)ProcessName; //把LPSTR类型转为CString类型,便于进行字符处理 //CString在ANSI字符集下以存储char数组,在Unicode字符集下以存储wchar_t数组,后者的长度是前者的两倍 //CString a,则a可作为指向存储的char数组的头部的指针,和LPSTR类型是一样的,所以我用了强转 //我百度到的转化方法是这么写的:CString ProcessFullPathName(ProcessName); 也可以 CString ProcessBaseName = ProcessFullPathName.Right(ProcessFullPathName.GetLength() - ProcessFullPathName.ReverseFind('\\') - 1); //把路径去掉,留下一个基础名称及C:\Windows\System32\notepad.exe转为notepad.exe if (ProcessBaseName == TargetProcessName) //如果该进程名与目标进程名相同,那么该进程id就是目标进程id { TargetProcessId = ProcessesId[i]; } CloseHandle(hProcess);//关闭句柄 hProcess = NULL; } } } return TargetProcessId; } void DoInject(DWORD TargetWindowThreadId) { HMODULE hDll = LoadLibrary(_T("DLL.dll")); //HMODULE是模块句柄类型 //LoadLibrary可以显示加载dll //这里我没有加路径,所有执行前要将dll放到exe文件同目录下 if (hDll == NULL) { printf("将dll加载到自身进程失败\n"); exit(0); } else { printf("将dll加载到自身进程成功\n"); } FARPROC KeyboardProc = (FARPROC)GetProcAddress(hDll, "KeyboardProc"); //通过GetProcAddress函数获取到hDll句柄中的KeyboardProc函数的地址 if (KeyboardProc == NULL) { printf("获取到回调函数地址失败\n"); exit(0); } else { printf("获取到回调函数地址成功\n"); } HHOOK g_hHook = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)KeyboardProc, hDll, TargetWindowThreadId); //将dll注入目标线程,设置函数指针指向写好的键盘消息回调函数 //第一个参数输入钩子类型 //第二个参数根据不同的钩子类型,要输入不同类型的回调函数地址 //第三个参数输入dll句柄 //第四个参数输入目标线程id if (g_hHook) { printf("向目标线程添加钩子并注入dll成功\n"); } printf("输入q卸载钩子:"); while (getchar() != 'q'); if (g_hHook) { UnhookWindowsHookEx(g_hHook); g_hHook = NULL; } //卸载钩子 FreeLibrary(hDll);//释放dll } BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) //EnumWindows设置的回调函数,系统每发现一个窗口都会调用该回调函数 //HWND是窗口句柄类型 //第一个参数返回的是当前窗口句柄,第二个参数类型可以自己定,我传入的是目标进程id { DWORD CurrentWindowProcessId; //当前窗口进程id DWORD CurrentWindowThreadId; //当前窗口线程id CurrentWindowThreadId = GetWindowThreadProcessId(hwnd, &CurrentWindowProcessId); //GetWindowThreadProcessId()可以通过窗口句柄,获取该窗口的所在的进程及线程 //第一个参数是输入参数,输入目标窗口句柄 //第二个参数是输出参数,类型是LPDWORD,及指向DWORD的指针,所以要取地址,函数执行成功后CurrentWindowProcessId值就是返回的当前窗口进程id //返回值是值传递的,DWORD类型,直接赋值给DWORD类型就行了,值是当前窗口线程id if (CurrentWindowProcessId == lParam) { //如果当前窗口进程id等于目标进程的进程id //则得到的当前窗口线程id就是目标窗口线程id DoInject(CurrentWindowThreadId); //得到了线程id后就可以注入了 return false; //当找到后就返回false,这样才会终止遍历 } return true; //不是当前窗口,返回true,继续遍历 } int main() { CString TargetProcessName(_T("notepad.exe")); DWORD TargetProcessId = FindProcessByEnumProcess(TargetProcessName); //先找到目标进程id EnumWindows(EnumWindowsProc, TargetProcessId); //根据目标进程id进行遍历,找到目标线程,并注入dll }

我把cpp的框架写出来

FindProcessByEnumProcess()函数是输入进程名,返回进程id

DoInject()函数是执行注入的过程,需要知道被注入的线程的id

EnumWindowsProc()函数是回调函数,对于每个已存在的窗口,判断其进程id是否与目标进程id相同,如果是,就锁定了目标线程id,再调用DoInject()函数执行注入的过程

int main()

{

    1、得到目标进程id

    2、设置回调函数,等待其执行

}

再来说说我的思路:我们目标是要找到计算本程序线程id,因为注入函数SetWindowsHookEx的最后一个参数是目标线程id,进程id是 不行的,其实有两种实现方法:

思路1:找到记事本进程id,根据进程id找到其所有的线程id,但是一个记事本进程有很多子线程,我不知道是否都要注入还是只要注入一个,而且列出所有子线程那个方法我没弄懂,于是没这么做;

思路2:找到找到记事本进程id,枚举当前所有窗口参看窗口的进程id以及线程id,对比记事本进程id,相同的话就锁定了记事本窗口所在线程id;

思路3:其实最开始我们的源头就是记事本的进程名notepad.exe,我们有没有办法绕过进程id,找到线程id呢,FindWindow()这个函数可以通过窗口名找到窗口句柄,再GetWindowThreadProcessId()根据窗口句柄找到窗口线程id不就行了吗,但是可惜的是这个窗口名并不是notepad.exe,而是“新建文本文档.txt – 记事本”,根本不好锁定,所以此路不通。

7、在stdafx.h这个头文件中添加代码:

#include <afx.h> //因为我们cpp建的不是空项目,项目是有结构的,引入头文件一定要放在stdafx.h中

8、点击最上方的生成->生成解决方案,将DLL->x64->Debug目录下的DLL.dll文件复制到CPP->x64->Debug目录下

9、先打开一个记事本程序(如果你打开两个,前面也说到了,只能锁定后打开的窗口),点击上方调试->开始执行,这时再打开回到记事本窗口,试试摁下键盘看看有没有效果。

还可以通过PCHunter查看被注入的dll,方法是右击进程->查看进程模块,如下图被标记为红色的dll:

windows10 记事本进程 键盘消息钩子 dll注入

参考文章:1、点击打开链接(腾讯 游戏安全实验室,这个demo只是其中的一个作业)/2、点击打开链接 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/159477.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • C# 多线程编程

    C# 多线程编程1.如果只是启动一个新线程,不需要传入参数,不需要线程返回结果,可以直接使用ThreadStart(),Thread类接收一个ThreadStart委托或ParameterizedThreadSta

  • RAII_ras raf

    RAII_ras rafhttp://baike.baidu.com/link?url=DUGlzCMqyZ2aInTkdpGWqW0f53fL-LWLu9nD3rGoJClIUb8rssh8oCALOhad7MDAWkqEhdfUzPU72jvQeDx5KK目录1RAII简介2RAII的分类3RAII实际应用4RAII与STL容器1RAII简介RAII[1]

    2022年10月23日
  • 机器学习:代价函数cost function

    机器学习:代价函数cost function本文系转载,咯有修改原博客地址:http://blog.csdn.net/u012162613/article/details/44239919在此,向原作者表达感谢,致敬!1.从方差代价函数说起代价函数经常用方差代价函数(即采用均方误差MSE),比如对于一个神经元(单输入单输出,sigmoid函数),定义其代价函数为:C=(y−a)22C=\frac{(y-a)^2}{2}其中yy是我们期望

  • TP5 where数组查询(模糊查询)(有多个查询条件) when「建议收藏」

    TP5 where数组查询(模糊查询)(有多个查询条件) when「建议收藏」有查询条件就查询,多个查询条件,只要有查询,就增加一个查询条件一、TP5.1版本模糊查询$where[]=[‘title’,’like’,”%”.$sotitle.”%”];$map[]=[‘name’,’like’,’think’];$map[]=[‘status’,’=’,1];//时间查询$wheret2[]=[‘time’,’between’,[…

  • Thread.IsBackground 属性

    Thread.IsBackground 属性net提供了Thread类用于线程的操作。当初始化一个线程,把Thread.IsBackground=true的时候,指示该线程为后台线程。后台线程将会随着主线程的推出而退出。后台线程不妨碍程序的终止,只要所有前台线程都终止后,CLR就会对每一个活在的后台线程调用Abort()来彻底终止应用程序。【注意】当在主线程中创建了一个线程,那么该线程的IsBackground默认是设置为FALS…

  • 什么是波特率,波特率怎么计算[通俗易懂]

    什么是波特率,波特率怎么计算[通俗易懂]✍什么是波特率,波特率怎么计算➹概述:  ☆简而言之,串口传输的波特率即为每秒钟传输二进制的位数。  ☆脱离枯燥乏味的文字描述,我们用波形和数字来看看波特率是什么吧☟。  ☆说明:系统时钟50M,波特率115200。  基础知识:因果系统时钟-50M时钟周期150∗106{{\rm{1}}\over{{\rm{50*1}}{{\rm{0}}^{\rm{6}}}}}50∗1061​假设1个时钟周期可以计数1次(其实FPGA就是这样)50M时钟1s计数5000

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号