2507C++,APC可以干的活
原文
窗口
中的异步过程调用
(APC
)是可附加进线程的对象
.每个线程
都有自己的存储要调用的函数和参数
的APC
队列.
想法是,希望由特定线程
而不是某个任意线程
执行a函数
.这是因为线程所属的进程
有时很重要,因此APC
(执行时)可完全访问该进程资源
.
技术上,有用户模式
,内核模式
和特殊内核模式
的APC
,这里,讨论用户模式APC
,即窗口接口
直接支持的用户模式APC
.
还有特殊用户模式APC
,但它一般不可用.下面是线程及其APC
队列的概念表示:
线程==用户模式APC队列 APC APC APC APC
线程==内核模式APC队列 APC APC 线程及其`APC`队列
当在线程中排队用户模式APC
时,APC
只是坐在队列中闲着.要实际运行
当前附加进线程的APC
,该线程必须进入可警告等待
(也叫可警告状态
).
在该状态时,线程队列
中的任何和所有APC
将按顺序执行.但是线程如何进入可警告等待
?
有一些函数
可完成.最简单
的是扩展的休息
函数SleepEx
,这里:
DWORD SleepEx(DWORD msec, BOOL alertable);
如果可警告
为假
,则该函数
与休息
相同.否则,线程将休息指定的时间(可为零)
,如果在其队列中任何APC
退出(或在休息时出现),则现在将执行并结束休息
,此时,SleepEx
的返回值
为WAIT_IO_COMPLETION
而不是零.
典型的调用
可能是SleepEx(0,TRUE)
,以强制运行所有排队的APC
(如果有).可按APC
的"垃集"
对待此调用.如果线程
从未进入可警告等待
,则不会执行任何附加的APC
.
进入可警告等待
的其他方法,涉及使用各种等待函数的扩展版本
,如WaitForSingleObjectEx,WaitForMultipleObjectsEx
,它们都接受与SleepEx
一样的额外的布尔参数
.
这里
这里
MsgWaitForMultipleObjectsEx
也可这样,只是使用(MWMO_ALERTABLE
)标志
,而不是指定可警告状态
的极.
这里
异步I/O
完成
用户模式APC
的"经典"
用法是,取异步I/O
操作的通知.对此,窗口
有多种机制,其中一个涉及APC
.即,ReadFileEx
和WriteFileEx,API
接收一个(与其非Ex
变量相比)异步I/O
操作完成时要调用的回调
的额外的参数
.
这里
这里
问题是,回调包装在请求线程
中排队的APC
中,即只能在该线程进入可警告等待
时执行它.下面是一些概念代码:
HANDLE hFile = ::CreateFile(..., FILE_FLAG_OVERLAPPED, nullptr);
OVERLAPPED ov{};
ov.Offset = ...;
::ReadFileEx(hFile, buffer, size, &ov, OnIoCompleted);//其他工作......//不时执行 APC:
::SleepEx(0, TRUE);
完成
例程有以下原型
:
void OnIoCompletion(DWORD dwErrorCode,DWORD dwNumberOfBytesTransfered,LPOVERLAPPED lpOverlapped);
实际上,这种通知异步/O完成
的机制不是很流行,因为使用同一线程
来完成一般不方便.事实上,线程可能会在I/O
完成之前退出.尽管如此,它仍是一个利用APC
的选项.
进程中注入DLL
有时"强制"另一个进程
加载你提供的DLL
很有用.完成的经典方法
是,使用按LoadLibrary,API
的地址设置"线程函数
"的CreateRemoteThread,API
,因为从二进制
角度来看,线程的函数
和LoadLibrary
有相同原型,因为两者都接受指针
.
这里
这里
给LoadLibrary
传递要加载的DLL
路径.视频
完整源码在此.
该方法的问题是它非常明显
,在创建线程
时会通知反恶意软件内核驱动
,如果由不同进程
中的线程创建,在驱动看来,是可疑的.
顺便,CreateRemoteThread
的"合法"
用法是调试器
通过强制进程
中的新线程
调用DbgBreakPoint
,来强制中断到初始附加
中的目标进程
.
使用APC
,也许可"说服"现有线程
加载我们的DLL
.这要隐蔽得多
,因为它是现有线程
加载DLL
,这非常常见
.为此,可用通用的QueueUserAPC,API
,这里:
DWORD QueueUserAPC(PAPCFUNC pfnAPC,HANDLE hThread,ULONG_PTR dwData
);
幸好,APC
函数有与线程函数
相同的二进制
布局,再次,接收某种指针
.主要问题
是目标线程
可能不会进入可警告的等待
.
为了增加成功概率
,可在目标进程
中排队所有线程的APC
,只需要一个线程
即可进入可警告的等待
.这对像资管
此类有很多线程
的进程效果很好
,几乎总是可工作
,视频.
自然队列
最后,因为在队列
中保存APC
,因此只需使用APC
就可非常自然
地创建一个"工作队列
".如果需要顺序调用函数队列
,可借助C++
中的std::queue<>
自行管理它们.
但是该队列不是线安的
,因此你必须正确
保护它.如果使用的是.NET
,则可用ConcurrentQueue<>
来帮助同步,但仍要构建某种循环
来弹出项目,调用它们
等.
有了APC
,一切都自然而简单
:
void WorkQueue(HANDLE hQuitEvent) {while(::WaitForSingleObjectEx(hQuitEvent, INFINITE, TRUE) != WAIT_OBJECT_0);
}
简单自身.可用事件对象
来退出此无限循环
(从某处调用的SetEvent
).线程等待APC
出现在其队列中,并在出现时运行它们,返回等待.
这里
此队列的客户
调用QueueUserAPC
在该线程中入队工作项
(回调).就是这样,简单而优雅
.