Windows核心编程 第26章 窗口消 息
窗 口 消 息
Wi n d o w s允许一个进程至多建立10 000个不同类型的用户对象(User object):图符、光标、窗口类、菜单、加速键表等等。当一个线程调用一个函数来建立某个对象时,则该对象就归这个线程的进程所拥有。这样,当进程结束时,如果没有明确删除这个对象,则操作系统会自动删除这个对象。对窗口和挂钩( h o o k )这两种U s e r对象,它们分别由建立窗口和安装挂钩的线程所拥有。如果一个线程建立一个窗口或安装一个挂钩,然后线程结束,操作系统会自动删除窗口或卸载挂钩。
26.1 线程的消息队列
当一个线程第一次被建立时,系统假定线程不会被用于任何与用户相关的任务。这样可以减少线程对系统资源的要求。但是,一旦这个线程调用一个与图形用户界面有关的函数(例如检查它的消息队列或建立一个窗口),系统就会为该线程分配一些另外的资源,以便它能够执行与用户界面有关的任务。特别是,系统分配一个T H R E A D I N F O结构,并将这个数据结构与线程联系起来。
这个T H R E A D I N F O结构包含一组成员变量,利用这组成员,线程可以认为它是在自己独占的环境中运行。T H R E A D I N F O是一个内部的、未公开的数据结构,用来指定线程的登记消息队列(posted-message queue)、发送消息队列( send-message queue)、应答消息队列( r e p l y -message queue)、虚拟输入队列(virtualized-input queue)、唤醒标志 (wake flag)、以及用来描述线程局部输入状态的若干变量。
26.2 将消息发送到线程的消息队列中
(1)BOOL WINAPI PostMessageW(
_In_opt_ HWND hWnd,
_In_ UINT Msg,
_In_ WPARAM wParam,
_In_ LPARAM lParam);.
当一个线程调用这个函数时,系统要确定是哪一个线程建立了用 h w n d参数标识的窗口。然后系统分配一块内存,将这个消息参数存储在这块内存中,并将这块内存增加到相应线程的登记消息队列中。并且,这个函数还设置 Q S _ P O S T M E S S A G E唤醒位(后面会简单讨论)。函数P o s t M e s s a g e在登记了消息之后立即返回,调用该函数的线程不知道登记的消息是否被指定窗口的窗口过程所处理。实际上,有可能这个指定的窗口永远不会收到登记的消息。如果建立这个特定窗口的线程在处理完它的消息队列中的所有消息之前就结束了,就会发生这种事。
还可以通过调用P o s t T h r e a d M e s s a g e将消息放置在线程的登记消息队列中。
(2)BOOL WINAPI PostThreadMessageW(
_In_ DWORD idThread,
_In_ UINT Msg,
_In_ WPARAM wParam,
_In_ LPARAM lParam);
P o s t T h r e a d M e s s a g e函数所期望的线程由第一个参数d w T h r e a d I d所标记。当消息被设置到队列中时,M S G结构的h w n d成员将设置成N U L L。当程序要在主消息循环中执行一些特殊处理时要调用这个函数。
要对线程编写主消息循环以便在 G e t M e s s a g e或P e e k M e s s a g e取出一个消息时,主消息循环代码检查h w n d是否为N U L L,并检查M S G结构的m s g成员来执行特殊的处理。如果线程确定了该消息不被指派给一个窗口,则不调用D i s p a t c h M e s s a g e,消息循环继续取下一个消息。
像P o s t M e s s a g e函数一样,P o s t T h r e a d M e s s a g e在向线程的队列登记了消息之后就立即返回。调用该函数的线程不知道消息是否被处理。
向线程的队列发送消息的函数还有P o s t Q u i t M e s s a g e:
(3)VOID PostQuitMessage(int nExitCode);
为了终止线程的消息循环,可以调用这个函数。调用P o s t Q u i t M e s s a g e类似于调用:
PostThreadMessage(GetCurrentThreadid(),WM_QUIT ,nExitCode ,0);
但是,P o s t Q u i t M e s s a g e并不实际登记一个消息到任何一个 T H R E A D I N F O结构的队列。只是在内部,P o s t Q u i t M e s s a g e设定Q S_Q U I T唤醒标志(后面将要讨论),并设置T H R E A D I N F O结构的n E x i t C o d e成员。因为这些操作永远不会失败,所以 P o s t Q u i t M e s s a g e的原型被定义成返回V O I D。
注意 可以通过调用G e t Wi n d o w s T h r e a d P r o c e s s I d来确定是哪个线程建立了一个窗口。
DWORD GetWindowThreadProcessId(HWND hwnd ,PDWORD pdwProcessId);
这个函数返回线程的I D ,这个线程建立了h w n d参数所标识的窗口。线程I D 在全系统范围内是唯一的。还可以通过对p d w P r o c e s s I d参数传递一个D W O R D 地址来获取拥有该线程的进程I D ,这个进程I D 在全系统范围内也是唯一的。通常,我们不需要进程I D ,只须对这个参数传递一个N U L L。
26.3 向窗口发送消息
使用S e n d M e s s a g e函数可以将窗口消息直接发送给一个窗口过程:
LRESULT WINAPI SendMessageW(
_In_ HWND hWnd,
_In_ UINT Msg,
_Pre_maybenull_ _Post_valid_ WPARAM wParam,
_Pre_maybenull_ _Post_valid_ LPARAM lParam);
窗口过程将处理这个消息。只有当消息被处理之后, S e n d M e s s a g e才能返回到调用程序。由于具有这种同步特性,比之P o s t M e s s a g e或P o s t T h r e a d M e s s a g e,S e n d M e s s a g e用得更频繁。调用这个函数的线程在下一行代码执行之前就知道窗口消息已经被完全处理。
S e n d M e s s a g e是如何工作的呢?如果调用S e n d M e s s a g e的线程向该线程所建立的一个窗口发送一个消息,S e n d M e s s a g e就很简单:它只是调用指定窗口的窗口过程,将其作为一个子例程。当窗口过程完成对消息的处理时,它向 S e n d M e s s a g e返回一个值。S e n d M e s s a g e再将这个值返回给调用线程。
但是,当一个线程向其他线程所建立的窗口发送消息时, S e n d M e s s a g e的内部工作就复杂得多(即使两个线程在同一进程中也是如此)。Wi n d o w s要求建立窗口的线程处理窗口的消息。所以当一个线程用S e n d M e s s a g e向一个由其他进程所建立的窗口发送一个消息,也就是向其他的线程发送消息,发送线程不可能处理窗口消息,因为发送线程不是运行在接收进程的地址空间中,因此不能访问相应窗口过程的代码和数据。实际上,发送线程要挂起,而由另外的线程处理消息。所以为了向其他线程建立的窗口发送一个窗口消息,系统必须执行下面将讨论的动作。
首先,发送的消息要追加到接收线程的发送消息队列,同时还为这个线程设定Q S _ S E N D M E S S A G E标志(后面将讨论)。其次,如果接收线程已经在执行代码并且没有等待消息(如调用G e t M e s s a g e、P e e k M e s s a g e或Wa i t M e s s a g e),发送的消息不会被处理,系统不能中断线程来立即处理消息。当接收进程在等待消息时,系统首先检查 Q S _ S E N D M E S S A G E唤醒标志是否被设定,如果是,系统扫描发送消息队列中消息的列表,并找到第一个发送的消息。有可能在这个队列中有几个发送的消息。例如,几个线程可以同时向一个窗口分别发送消息。当发生这样的事时,系统只是将这些消息追加到接收线程的发送消息队列中。
当接收线程等待消息时,系统从发送消息队列中取出第一个消息并调用适当的窗口过程来处理消息。如果在发送消息队列中再没有消息了,则 Q S _ S E N D M E S S A G E唤醒标志被关闭。当接收线程处理消息的时候,调用 S e n d M e s s a g e的线程被设置成空闲状态(i d l e),等待一个消息出现在它的应答消息队列中。在发送的消息处理之后,窗口过程的返回值被登记到发送线程的应答消息队列中。发送线程现在被唤醒,取出包含在应答消息队列中的返回值。这个返回值就是调用S e n d M e s s a g e的返回值。这时,发送线程继续正常执行。
当一个线程等待S e n d M e s s a g e返回时,它基本上是处于空闲状态。但它可以执行一个任务:如果系统中另外一个线程向一个窗口发送消息,这个窗口是由这个等待 S e n d M e s s a g e返回的线程所建立的,则系统要立即处理发送的消息。在这种情况下,系统不必等待线程去调用G e t M e s s a g e、Peek Message或Wa i t M e s s a g e。
由于Wi n d o w s使用上述方法处理线程之间发送的消息,所以有可能造成线程挂起( h a n g)。例如,当处理发送消息的线程含有错误时,会导致进入死循环。那么对于调用 S e n d M e s s a g e的线程会发生什么事呢?它会恢复执行吗?这是否意味着一个程序中的 b u g会导致另一个程序挂起?答案是确实有这种可能。
利用4个函数——S e n d M e s s a g e Ti m e o u t、S e n d M e s s a g e C a l l b a c k、S e n d N o t i f y M e s s a g e和R e p l y M e s s a g e,可以编写保护性代码防止出现这种情况。第一个函数是 S e n d M e s s a g e Ti m e o u t:
LRESULT WINAPI SendMessageTimeoutW(
_In_ HWND hWnd,
_In_ UINT Msg,
_In_ WPARAM wParam,
_In_ LPARAM lParam,
_In_ UINT fuFlags,
_In_ UINT uTimeout,
_Out_opt_ PDWORD_PTR lpdwResult);
利用S e n d M e s s a g e Ti m e o u t函数,可以规定等待其他线程答回你消息的时间最大值。前 4个参数与传递给S e n d M e s s a g e的参数相同。对f u F l a g s参数,可以传递值S M TO _ N O R M A L (定义为0 )、S M TO _ A B O RT I F H U N G、S M TO _ B L O C K、S M TO _ N O T I M E O U T I F N O T H U N G或这些标志的组合。
S M TO _ A B O RT I F H U N G标志是告诉S e n d M e s s a g e Ti m e o u t去查看接收消息的线程是否处于挂起状态,如果是,就立即返回。S M TO _ N O T I M E O U T I F N O T H U N G标志使函数在接收消息的线 程 没 有 挂 起 时 不 考 虑 等 待 时 间 限 定 值 。 S M T O _ B L O C K 标 志 使 调 用 线 程 在S e n d M e s s a g e Ti m e o u t返回之前,不再处理任何其他发送来的消息。 S M TO _ N O R M A L标志在Wi n u s e r. h中定义成0,如果不想指定任何其他标志及组合,就使用这个标志。
前面说过,一个线程在等待发送的消息返回时可以被中断,以便处理另一个发送来的消息。使用S M TO _ B L O C K标志阻止系统允许这种中断。仅当线程在等待处理发送的消息的时候 (不能处理别的发送消息),才使用这个标志。使用S M TO _ B L O C K可能会产生死锁情况,直到等待时间期满。例如,如果你的线程向另外一个线程发送一个消息,而这个线程又需要向你的线程发送消息。在这种情况下,两个线程都不能继续执行,并且都将永远挂起。
S e n d M e s s a g e Ti m e o u t函数中的u Ti m e o u t参数指定等待应答消息时间的毫秒数。如果这个函数执行成功,返回T R U E,消息的结果复制到一个缓冲区中,该缓冲区的地址由 p d w R e s u l t参数指定。
用来在线程间发送消息的第二个函数是S e n d M e s s a g e C a l l b a c k:
BOOL WINAPI SendMessageCallbackW(
_In_ HWND hWnd,
_In_ UINT Msg,
_In_ WPARAM wParam,
_In_ LPARAM lParam,
_In_ SENDASYNCPROC lpResultCallBack,
_In_ ULONG_PTR dwData);
同样,前4个参数同S e n d M e s s a g e中使用的一样。当一个线程调用S e n d M e s s a g e C a l l b a c k时,该函数发送消息到接收线程的发送消息队列,并立即返回使发送线程可以继续执行。当接收线程完成对消息的处理时,一个消息被登记到发送线程的应答消息队列中。然后,系统通过调用一个函数将这个应答通知给发送线程,该函数是使用下面的原型编写的。
VOID CALLBACK ResultCallBack(
HWND hwnd,
UINT uMsg,
ULONG_PTR dwData,
LRESULT lResult);
因为S e n d M e s s a g e C a l l b a c k在执行线程间发送时会立即返回,所以在接收线程完成对消息的处理时不是立即调用这个回调函数。而是由接收线程先将一个消息登记到发送线程的应答消息队列。发送线程在下一次调用G e t M e s s a g e、P e e k M e s s a g e、Wa i t M e s s a g e或某个S e n d M e s s a g e*函数时,消息从应答消息队列中取出,并执行R e s u l t C a l l B a c k函数。
S e n d M e s s a g e C a l l b a c k函数还有另外一个用处。Wi n d o w s提供了一种广播消息的方法,用这种方法你可以向系统中所有现存的重叠( o v e r l a p p e d)窗口广播一个消息。这可以通过调用S e n d M e s s a g e函数,对参
数h w n d传递H W N D _ B R O A D C A S T(定义为-1)。使用这种方法广播的消息,其返回值我们并不感兴趣,因为 S e n d M e s s a g e函数只能返回一个 L R E S U LT。但使用S e n d M e s s a g e C a l l b a c k,就可以向每一个重叠窗口广播消息,并查看每一个返回结果。对每一个处理消息的窗口的返回结果都要调用R e s u l t C a l l b a c k函数。
如果调用S e n d M e s s a g e C a l l b a c k向一个由调用线程所建立的窗口发送一个消息,系统立即调用窗口过程,并且在消息被处理之后,系统调用 R e s u l t C a l l B a c k函数。在R e s u l t C a l l B a c k函数返回之后,系统从调用S e n d M e s s a g e C a l l b a c k的后面的代码行开始执行。
线程间发送消息的第三个函数是S e n d N o t i f y M e s s a g e
BOOL WINAPI SendNotifyMessageW(
_In_ HWND hWnd,
_In_ UINT Msg,
_In_ WPARAM wParam,
_In_ LPARAM lParam);
S e n d N o t i f y M e s s a g e将一个消息置于接收线程的发送消息队列中,并立即返回到调用线程。这一点与P o s t M e s s a g e函数一样,但S e n d N o t i f y M e s s a g e在两方面与P o s t M e s s a g e不同。
首先,S e n d N o t i f y M e s s a g e是向另外的线程所建立的窗口发送消息,发送的消息比起接收线程消息队列中存放的登记消息有更高的优先级。换句话说,由 S e n d N o t i f y M e s s a g e函数存放在队列中的消息总是在P o s t M e s s a g e函数登记到队列中的消息之前取出。
其次,当向一个由调用进程建立的窗口发送消息时, S e n d N o t i f y M e s s a g e同S e n d M e s s a g e函数完全一样:S e n d N o t i f y M e s s a g e在消息被处理完之后才能返回。
第四个用于线程发送消息的函数是R e p l y M e s s a g e:
BOOL ReplyMessage(LRESULT lResult);
这个函数与前面讨论过的三个函数不同。线程使用 S e n d M e s s a g e Ti m e o u t、S e n d M e s s a g eC a l l b a c k和S e n d N o t i f y M e s s a g e发送消息,是为了保护自己以免被挂起。而线程调用 R e p l yM e s s a g e是为了接收窗口消息。当一个线程调用 R e p l y M e s s a g e时,它是要告诉系统,为了知道消息结果,它已经完成了足够的工作,结果应该包装起来并登记到发送线程的应答消息队列中。这将使发送线程醒来,获得结果,并继续执行。
有时候,你可能想知道究竟是在处理线程间的消息发送,还是在处理线程内的消息发送。
为了搞清楚这一点,可以调用I n S e n d M e s s a g e:
BOOL InSendMessage();
还可以调用另外一个函数来确定窗口过程正在处理的消息类型:
DWORD InSendMessageEx(PVOID pvReserved);
26.4 唤醒一个线程
当一个线程调用G e t M e s s a g e或Wa i t M e s s a g e,但没有对这个线程或这个线程所建立窗口的消息时,系统可以挂起这个线程,这样就不再给它分配 C P U时间。当有一个消息登记或发送到这个线程,系统要设置一个唤醒标志,指出现在要给这个线程分配 C P U时间,以便处理消息。正常情况下,如果用户不按键或移动鼠标,就没有消息发送给任何窗口。这意味着系统中大多数线程没有被分配给C P U时间。
26.4.1 队列状态标志
当一个线程正在运行时,它可以通过调用G e t Q u e u e S t a t u s函数来查询队列的状态:
DWORD GetQueueStatus(UNIT fuFlags)
26.4.2 从线程的队列中提取消息的算法
当一个线程调用G e t M e s s a g e或P e e k M e s s a g e时,系统必须检查线程的队列状态标志的情况,并确定应该处理哪个消息。图2 6 - 2和下面叙述的步骤说明了系统是如何确定线程应该处理的下一个消息的情况。
26.4.3 利用内核对象或队列状态标志唤醒线程
G e t M e s s a g e或P e e k M e s s a g e函数导致一个线程睡眠,直到该线程需要处理一个与用户界(U I)相关的任务。有时候,若能让线程被唤醒去处理一个与 U I有关的任务或其他任务,就会带来许多方便。例如,一个线程可能启动一个长时间运行的操作,并可以让用户取消这个操作。这个线程需要知道何时操作结束(与 U I无关的任务),或用户是否按了C a n c e l按钮(与U I相关的任务)来结束操作。
一个线程可以调用M s g Wa i t F o r M u l t i p l e O b j e c t s或M s g Wa i t F o r M u l t i p l e O b j e c t s E x函数,使线程等待它自已的消息。
DWORD MsgWaitForMultipleObjectsEx(
DWORD nCount,
PHANDLE phObject,
DWORD dwMilliseconds,
DWORD dwWakeMask,
DWORD dwFlags);
M s g Wa i t F o rM u l t i p l e O b j e c t s E x是M s g Wa i t F o r M u l t i p l e O b j e c t s的一个超集( s u p e r s e t)。新的特性是通过d w F l a g s参数引进的。对这个参数,可以指定下面标志的任意组合(见表 2 6 - 3)。
如果不想要任何这些附加的特性,可对参数d w F l a g s传递零(0)。
下面是有关M s g Wa i t F o r M u l t i p l e O b j e c t s(E x)的一些重要内容:
• 由于这个函数只是向内核句柄的数组增加一个内部事件内核对象, n C o u n t参数的最大值是M A X I M U M _ WA I T _ O B J E C T减1或6 3。
• 当对f Wa i t A l l参数传递FA L S E时,那么当一个内核对象是有信号的(s i g n a l e d),或当指定的消息类型出现在线程的队列时,函数返回。
• 当对f Wa i t A l l参数传递T R U E时,那么当所有内核对象成为有信号状态,并且指定的消息类型出现在线程的队列中时,函数返回。这种行为似乎使许多开发人员感到惊讶。开发人员希望有一种办法,当所有内核对象变成有信号的或者当指定的消息类型出现在线程
的队列中时,可以唤醒线程。但没有函数能够这样。
• 当调用这两个函数时,实际是查看是否有指定类型的新消息被放入调用线程的队列。注意,上述最后一条会使许多开发人员吃惊。这里有一个例子。假定一个线程的队列目前包含有两个按键消息。如果这个线程现在要调用 M s g Wa i t F o r M u l t i p l e O b j e c t s(E x),其中d w Wa k e M a s k参数设置成Q S _ I N P U T,线程将被唤醒,从队列中取出第一个按键消息,并处理这个消息。现在,如果这个线程要再调用 M s g Wa i t F o r M u l t i p l e O b j e c t s(E x),线程将不会被唤醒,因为线程的队列中没有“新”的消息。
对开发人员来说,这已变成了一个主要问题,为此微软增加了 M W M O _ I N P U TAVA I LA B L E标志,这只用于M s g Wa i t F o r M u l t i p l e O b j e c t s E x,而不用于M s g Wa i t F o r M u l t i p l e O b j e c t s。
这里是一个例子,讲述如何适当地编码一个使用M s g Wa i t F o r M u l t i p l e O b j e c ts E x的消息循
26.5 通过消息发送数据
本节将讨论系统如何利用窗口消息在进程之间传送数据。一些窗口消息在其 l P a r a m参数中指出了一个内存块的地址。例如, W M _ S E T T E X T消息使用l P a r a m参数作为指向一个以零结尾的字符串的指针,这个字符串为窗口规定了新的文本标题串。考虑下面的调用:
SendMessage(FindWindow(NULL ,” C a l c u l a t o r”) ,WM_SETTEXT ,
0 ,(LPARAM)”XXXXXXX”)
这个调用看起来不会有害。它确定 C a l c u l a t o r程序窗口的窗口句柄,并试图将窗口的标题改成“A Test Caption”。
微软建立了一个特殊的窗口消息,W M _ C O P Y D ATA以解决这个(中间数据共享)问题:
COPYDATASTRUCT cds;
SendMessage(hwndReceicer,WM_COPYDATA ,(WPARAM)hwndSender,(LPARAM) &cds);
C O P Y D ATA S T R U C T是一个结构,定义在Wi n U s e r. h文件中,形式如下面的样子:
typedef struct tagCOPYDATASTRUCT {
ULONG_PTR dwData;
DWORD cbData;
_Field_size_bytes_(cbData) PVOID lpData;
} COPYDATASTRUCT, *PCOPYDATASTRUCT;
当一个进程要向另一个进程的窗口发送一些数据时,必须先初始化 C O P Y D ATA S T R U C T结构。数据成员d w D a t a是一个备用的数据项,可以存放任何值。例如,你有可能向另外的进程发送不同类型或不同类别的数据。可以用这个数据来指出要发送数据的内容。
c b D a t a数据成员规定了向另外的进程发送的字节数, l p D a t a数据成员指向要发送的第一个字节。l p D a t a所指向的地址,当然在发送进程的地址空间中。
当S e n d M e s s a g e看到要发送一个W M _ C O P Y D ATA消息时,它建立一个内存映像文件,大小是c b D a t a字节,并从发送进程的地址空间中向这个内存映像文件中复制数据。然后再向目的窗口发送消息。在接收消息的窗口过程处理这个消息时, l P a r a m参数指向已在接收进程地址空间的一个C O P Y D ATA S T R U C T结构。这个结构的l p D a t a成员指向接收进程地址空间中的共享内存映像文件的视图。
Windows核心编程 第26章 窗口消 息的更多相关文章
- windows核心编程 第8章201页旋转锁的代码在新版Visual Studio运行问题
// 全局变量,用于指示共享的资源是否在使用 BOOL g_fResourceInUse = FALSE; void Func1() { //等待访问资源 while(InterlockedExcha ...
- windows核心编程 第5章job lab示例程序 解决小技巧
看到windows核心编程 第5章的最后一节,发现job lab例子程序不能在我的系统(win8下)正常运行,总是提示“进程在一个作业里” 用process explorer程序查看 ...
- Windows核心编程 第六章 线程基础知识 (上)
第6章 线程的基础知识 理解线程是非常关键的,因为每个进程至少需要一个线程.本章将更加详细地介绍线程的知识.尤其是要讲述进程与线程之间存在多大的差别,它们各自具有什么作用.还要介绍系统如何使用线程内核 ...
- windows核心编程---第七章 用户模式下的线程同步
用户模式下的线程同步 系统中的线程必须访问系统资源,如堆.串口.文件.窗口以及其他资源.如果一个线程独占了对某个资源的访问,其他线程就无法完成工作.我们也必须限制线程在任何时刻都能访问任何资源.比如在 ...
- Windows核心编程 第27章 硬件输入模型和局部输入状态
第27章 硬件输入模型和局部输入状态 这章说的是按键和鼠标事件是如何进入系统并发送给适当的窗口过程的.微软设计输入模型的一个主要目标就是为了保证一个线程的动作不要对其他线程的动作产生不好的影响. 27 ...
- Windows核心编程 第十七章 -内存映射文件(下)
17.3 使用内存映射文件 若要使用内存映射文件,必须执行下列操作步骤: 1) 创建或打开一个文件内核对象,该对象用于标识磁盘上你想用作内存映射文件的文件. 2) 创建一个文件映射内核对象,告诉系统该 ...
- Windows核心编程 第七章 线程的调度、优先级和亲缘性(下)
7.6 运用结构环境 现在应该懂得环境结构在线程调度中所起的重要作用了.环境结构使得系统能够记住线程的状态,这样,当下次线程拥有可以运行的C P U时,它就能够找到它上次中断运行的地方. 知道这样低层 ...
- Windows核心编程 第四章 进程(上)
第4章 进 程 本章介绍系统如何管理所有正在运行的应用程序.首先讲述什么是进程,以及系统如何创建进程内核对象,以便管理每个进程.然后将说明如何使用相关的内核对象来对进程进行操作.接着,要介绍进 ...
- Windows核心编程 第三章 内核对象
第3章内核对象 在介绍Windows API的时候,首先要讲述内核对象以及它们的句柄.本章将要介绍一些比较抽象的概念,在此并不讨论某个特定内核对象的特性,相反只是介绍适用于所有内核对象的特性. 首先介 ...
随机推荐
- 免费报表工具 积木报表(JiMuReport)的安装
分享一b/s报表工具(服务),积木报表(JiMuReport),张代浩大佬出品. 官网:http://www.jimureport.com/ 离线版官方下载:https://github.com/zh ...
- 「CTSC 2013」组合子逻辑
Tag 堆,贪心 Description 给出一个数列 \(n\) 个数,一开始有一个括号包含 \([1,n]\),你需要加一些括号,使得每个括号(包括一开始的)所包含的元素个数 \(\leq\) 这 ...
- python基础学习之描述符和装饰器
描述符的了解: 描述符协议: python描述符是一个"绑定行为"的对象属性,在描述符协议中,它可以通过方法重写属性的访问.这些方法有: __get__, __set__, 和__ ...
- 【Azure 服务总线】详解Azure Service Bus SDK中接收消息时设置的maxConcurrentCalls,prefetchCount参数
(Azure Service Bus服务总线的两大类消息处理方式: 队列Queue和主题Topic) 问题描述 使用Service Bus作为企业消息代理,当有大量的数据堆积再Queue或Topic中 ...
- 使用jQuery实现ajax请求
<%-- Created by IntelliJ IDEA. User: Administrator Date: 2021/3/13 Time: 14:54 To change this tem ...
- PTA 数组元素的区间删除
6-6 数组元素的区间删除 (20 分) 给定一个顺序存储的线性表,请设计一个函数删除所有值大于min而且小于max的元素.删除后表中剩余元素保持顺序存储,并且相对位置不能改变. 函数接口定义: ...
- Java-TreeMap和Guava-HashMultiset
一.Java-TreeMap 1.数据结构 底层数据结构是裸的红黑树,保证元素有序,没有比较器Comparator的情况按照key的自然排序,可自定义比较器.线程不安全. 可以存null,但是key不 ...
- 201871010113-贾荣娟 实验三 结对项目—《D{0-1}KP 实例数据集算法实验平台》项目报告
项目 内容 课程班级博客链接 18级卓越班 这个作业要求链接 实验三-软件工程结对项目 这个课程学习目标 掌握软件开发流程,提高自身能力 这个作业在哪些方面帮助我实现了学习目标 本次实验让我对软件工程 ...
- C#补位函数PadLeft和PadRight
左边补位 PadLeft 用法: string str = "100"; str.PadLeft(5,'0') 输出:00100 右边补位 PadRight 用法: str.Pad ...
- 华为云PB级数据库GaussDB(for Redis)揭秘第八期:用高斯 Redis 进行计数
摘要:高斯Redis,计数的最佳选择! 一.背景 当我们打开手机刷微博时,就要开始和各种各样的计数器打交道了.我们注册一个帐号后,微博就会给我们记录一组数据:关注数.粉丝数.动态数-:我们刷帖时,关注 ...