最近闲来无事玩玩游戏,这游戏有个毛病就是不定时掉线,每次掉线都不知道,所以浪费了大把时间。
作为C#初学者这我能忍么,写个软件监控一下掉线,顺便学习历练一下。
搜遍所有代码站点也没有找到符合自己需求的代码,所以经过两天的不断尝试终于解决了自己的问题,现在将经验分享:
思路:通过弹出掉线提示的窗口里面获取信息,来判断是否掉线,然后关闭窗口和对应的进程。
难点:鉴于net框架的设计初衷,没有操作内存类的权限,所以只能使用API来完成。
附加:之所以用了两天时间,是因为大部分时间都浪费在搞懂线程,进程,句柄,窗口,子窗口之间的关系,现分享我的理解:
进程>线程 主窗体>子窗体>控件>控件属性
窗体就像套娃,一个套一个,每个控件或者窗体都有一个的ID,我们叫做指针(API),所以我们想解决问题就要从小往大一步一步的进行
所以我们要解决问题,就要先找到弹窗控件的指针,通过这个指针获取到相关信息进行对比是否符合所有条件,符合的话就通过这个指针对他进行操作,然后找到这个指针的进程ID(PID-ProcessID)或者线程ID(TID-ThreadProcessID),通过ID找到对应的进程,然后干掉他。
废话不多说,直接上代码,至于API的相关详细参数请参考我的上一篇文章。
首先是导入命名空间
using System.Runtime.InteropServices;
然后是导入API函数
/// <summary>
/// 查找所有窗口(只要是在进程里面的)
/// 如果不限制类名或者标题使用null代替
/// </summary>
/// <param name="lpClassName">窗口类名,不限制使用null</param>
/// <param name="lpWindowName">窗口标题,不限制使用null</param>
/// <returns>找到的窗口句柄</returns>
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
// 获取窗口的类名
/// <summary>
/// 获取目标句柄的类名
/// </summary>
/// <param name="hWnd">目标窗口句柄</param>
/// <param name="lpClassName">返回的Class名称</param>
/// <param name="nMaxCount">允许返回的Class名称的字符数量上限</param>
/// <returns>获取到的实际Class字符长度</returns>
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
// 判断窗口是否可见
/// <summary>
/// 判断目标窗口是否可见
/// </summary>
/// <param name="hWnd">窗口句柄</param>
/// <returns>可见true,不可见false</returns>
[DllImport("user32.dll")]
public static extern bool IsWindowVisible(IntPtr hWnd);
// 给窗口发送消息
/// <summary>
/// 给目标句柄发送消息
/// </summary>
/// <param name="hwnd">目标句柄</param>
/// <param name="wMsg">消息内容</param>
/// <param name="wParam">附加自定义参数1</param>
/// <param name="lParam">附加自定义参数2</param>
/// <returns></returns>
[DllImport("user32.dll", EntryPoint = "SendMessageA")]
public static extern int SendMessage(IntPtr hwnd, int wMsg, int wParam, int lParam);
/// <summary>
/// 获取当前活动窗口(当前焦点窗口,键盘可操作的那种)
/// </summary>
/// <returns>返回窗口句柄</returns>
[DllImport("user32.dll")]
public static extern IntPtr GetActiveWindow();
/// <summary>
/// 通过窗口句柄获取线程ID(TID)和进程ID(PID)
/// </summary>
/// <param name="hwnd">窗口句柄</param>
/// <param name="PID">返回进程ID</param>
/// <returns>返回线程ID</returns>
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern int GetWindowThreadProcessId(IntPtr hwnd, out int PID);
你以为这就完了吗?我也是做到这一步了,获取到进程ID后还要判断这个进程ID对应的进程名是不是我们要找到?这个进程的主窗口名字是不是我们要操作的?再看看API函数,没有这类操作了,我在这里卡了小半天,后来突然想到既然有了进程ID我们可以用NET里面的Process类来进行操作判断,到了自己的地盘我想怎么搞就怎么搞
完整代码如下:
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
namespace 游戏插件
{
internal static class ProcCheck
{
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.dll")]
public static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll", EntryPoint = "SendMessageA")]
public static extern int SendMessage(IntPtr hwnd, int wMsg, int wParam, int lParam);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern int GetWindowThreadProcessId(IntPtr hwnd, out int PID);
const int WM_CLOSE = 0x10;
internal static void CheckCloseProcess(bool KillPorcess)
{
//条件值(API函数接口)
string findTitle = "提示信息";//窗口标题
string findClass1 = "#32770";//窗口类名
string findClass2 = "GRootViewClass";//窗口类名
int findClassMaxLen = 2048;//返回类名字符上限
//返回值(API函数接口)
IntPtr hWnd = IntPtr.Zero;//窗口句柄
StringBuilder sbr = new StringBuilder(500);//返回类名字符串数据流
string scr = string.Empty;//返回的类名字符串
int ClassLen = 0;//实际返回类名字符长度
int hwTID = 0;//线程ID
int hwPID = 0;//进程ID
int hwMSR = 0;//关闭窗口返回值
//C#部分变量初始化
string pchkTetris = "Tetris";//游戏窗口进程名
string pchkQQGame1 = "QQGame";//新版游戏大厅进程名
string pchkQQGame2 = "QQGameHall";//旧版游戏大厅进程名
string pchkTitle = "火拼俄罗斯";
string ProcTitleResult = string.Empty;//标题名
Process ProcInfo = null;//实例化进程数据类
string ProcNameResult = string.Empty;//进程名
// 查找标题为Error的窗口
hWnd = FindWindow(null, findTitle);//通过标题找句柄
//判断窗口是否存在
if (hWnd != IntPtr.Zero)
{
ClassLen = GetClassName(hWnd, sbr, findClassMaxLen);//获取类名
if (ClassLen > 0)
{
scr = sbr.ToString();//将返回的类名字符串数据流转换为字符串
if (scr == findClass1 | scr == findClass2)
{
if (IsWindowVisible(hWnd))
{
hwTID = GetWindowThreadProcessId(hWnd, out hwPID);//获取线程ID和进程ID
ProcInfo = Process.GetProcessById(hwPID);//通过进程ID获取进程所有信息
ProcNameResult = ProcInfo.ProcessName;//获取进程名
ProcTitleResult = ProcInfo.MainWindowTitle;//主进程标题
if (ProcNameResult == pchkTetris | ProcNameResult == pchkQQGame1 | ProcNameResult == pchkQQGame2)
{
if (ProcTitleResult == pchkTitle)
{
if (KillPorcess)
{
hwMSR = SendMessage(hWnd, WM_CLOSE, 0, 0);
if (hwMSR == 0)
{
if (ProcNameResult == pchkTetris)
{
ProcInfo.CloseMainWindow();//结束进程
return;
}
return;
}
}
else
{
return;
}
}
}
}
}
}
}
}
}
}
正常情况下你们可以把IF的连续判断用&&连接符号串起来,这样代码就能少很多行,我是为了方便更直观的展示判断的条件才这样写的。