概述
- 消息队列是存储在内核中的
- GUI线程才有消息队列
- 一开始创建的都是非GUI线程,指向的是SSDT表(Thread.ServiceTable-> KeServiceDescriptorTable)
- 当线程第一次调用Win32k.sys时,会调用一个函数:PsConvertToGuiThread,我们知道在3环进0环的过程中会取得一个调用号,当调用号在100以下的时候,在ntosknl.exe里面,当调用号大于100则是图形处理函数,调用Win32k.sys
如果是一个GUI线程,win32Thread指向的就是THREADINFO结构,如果是普通线程,这里就是一个空指针
底层实现主要看ETHREAD->ServiceTable是指向SSDT还是Shadow SSDT,详细介绍 - 0x0000-0x03ff(WM_USER-1)为系统定义消息,用户自定义消息范围为0x0400(WM_USER)-0x7fff
- 进程间消息通信,一般会通过CWnd::FindWindow()找到接收进程的窗口句柄,然后通过PostMessage()发送,接收的UI线程就会按消息进行处理
windows消息机制
重要结构体和API说明
内核层对象KTHREAD
typedef struct _KTHREAD
{
DISPATCHER_HEADER Header; //说明该对象是一个分发器对象,可以被等待。线程结束时,等待被满足
UINT64 CycleTime;
ULONG HighCycleTime;
UINT64 QuantumTarget;
PVOID InitialStack; //原始栈位置(高地址)
PVOID StackLimit; //栈低地址
PVOID KernelStack; //内核调用栈开始位置
ULONG ThreadLock;
union
{
KAPC_STATE ApcState;
UCHAR ApcStateFill[23];
};
CHAR Priority;
WORD NextProcessor;
WORD DeferredProcessor;
ULONG ApcQueueLock;
ULONG ContextSwitches;
UCHAR State;
UCHAR NpxState;
UCHAR WaitIrql;
CHAR WaitMode;
LONG WaitStatus;
union
{
PKWAIT_BLOCK WaitBlockList;
PKGATE GateObject;
};
union
{
ULONG KernelStackResident: 1;
ULONG ReadyTransition: 1;
ULONG ProcessReadyQueue: 1;
ULONG WaitNext: 1;
ULONG SystemAffinityActive: 1;
ULONG Alertable: 1;
ULONG GdiFlushActive: 1;
ULONG Reserved: 25;
LONG MiscFlags;
};
UCHAR WaitReason;
UCHAR SwapBusy;
UCHAR Alerted[2];
union
{
LIST_ENTRY WaitListEntry;
SINGLE_LIST_ENTRY SwapListEntry;
};
PKQUEUE Queue;
ULONG WaitTime;
union
{
struct
{
SHORT KernelApcDisable;
SHORT SpecialApcDisable;
};
ULONG CombinedApcDisable;
};
PVOID Teb;
union
{
KTIMER Timer;
UCHAR TimerFill[40];
};
union
{
ULONG AutoAlignment: 1;
ULONG DisableBoost: 1;
ULONG EtwStackTraceApc1Inserted: 1;
ULONG EtwStackTraceApc2Inserted: 1;
ULONG CycleChargePending: 1;
ULONG CalloutActive: 1;
ULONG ApcQueueable: 1;
ULONG EnableStackSwap: 1;
ULONG GuiThread: 1;
ULONG ReservedFlags: 23;
LONG ThreadFlags;
};
union
{
KWAIT_BLOCK WaitBlock[4];
struct
{
UCHAR WaitBlockFill0[23];
UCHAR IdealProcessor;
};
struct
{
UCHAR WaitBlockFill1[47];
CHAR PreviousMode;
};
struct
{
UCHAR WaitBlockFill2[71];
UCHAR ResourceIndex;
};
UCHAR WaitBlockFill3[95];
};
UCHAR LargeStack;
LIST_ENTRY QueueListEntry;
PKTRAP_FRAME TrapFrame;
PVOID FirstArgument;
union
{
PVOID CallbackStack;
ULONG CallbackDepth;
};
PVOID ServiceTable; /*****************/
UCHAR ApcStateIndex;
CHAR BasePriority;
CHAR PriorityDecrement;
UCHAR Preempted;
UCHAR AdjustReason;
CHAR AdjustIncrement;
UCHAR Spare01;
CHAR Saturation;
ULONG SystemCallNumber;
ULONG Spare02;
ULONG UserAffinity;
PKPROCESS Process;
ULONG Affinity;
PKAPC_STATE ApcStatePointer[2];
union
{
KAPC_STATE SavedApcState;
UCHAR SavedApcStateFill[23];
};
CHAR FreezeCount;
CHAR SuspendCount;
UCHAR UserIdealProcessor;
UCHAR Spare03;
UCHAR Iopl;
PVOID Win32Thread; /*****************/
PVOID StackBase;
union
{
KAPC SuspendApc;
struct
{
UCHAR SuspendApcFill0[1];
CHAR Spare04;
};
struct
{
UCHAR SuspendApcFill1[3];
UCHAR QuantumReset;
};
struct
{
UCHAR SuspendApcFill2[4];
ULONG KernelTime;
};
struct
{
UCHAR SuspendApcFill3[36];
PKPRCB WaitPrcb;
};
struct
{
UCHAR SuspendApcFill4[40];
PVOID LegoData;
};
UCHAR SuspendApcFill5[47];
};
UCHAR PowerState;
ULONG UserTime;
union
{
KSEMAPHORE SuspendSemaphore;
UCHAR SuspendSemaphorefill[20];
};
ULONG SListFaultCount;
LIST_ENTRY ThreadListEntry;
LIST_ENTRY MutantListHead;
PVOID SListFaultAddress;
PVOID MdlForLockedTeb;
} KTHREAD, *PKTHREAD;
/*
Header:说明该对象是一个分发器对象,可以被等待。线程结束时,等待被满足。
MutantListHead:指向一个链表头。链表中包含所有属于该线程的突变体对象(mutant,对应互斥体对象)。
InitialStack:原始栈位置(高地址)
StackLimit:栈低地址
KernelStack:内核调用栈开始位置
StackBase:当前栈的基地址。
ThreadLock:自旋锁,用于保护线程数据成员。
ApcState:KAPC_STATE结构,指定线程的APC信息,包括APC链表,是否有APC正在等待,是否正在处理APC。
ApcQueueable:是否可插入APC
NextProcessor:关于处理器调度的选择。
DeferredProcessor:关于处理器调度的选择。
AdjustReason:优先级调整原因
AdjustIncrement:优先级调整调整量
ApcQueueLock:保护APC队列的自旋锁。
ContextSwitches:记录线程进行了多少次切换。
State:线程当前状态。
NpxState:浮点处理器状态。
Alertable:线程是否可以被唤醒。
WaitNext:
WaitIrql:原先的IRQL。
WaitReason:等待原因
WaitMode:线程等待时的处理器模式,内核or用户
WaitStatus:等待的结果状态。
WaitBlockList:KWAIT_BLOCK为元素的链表,记录线程所有等待的分发器对象。每个分发器对象也有一个KWAIT_BLOCK组成的链表,记录所有等待在该对象的线程。
GateObject:等待的门对象,等待门对象和等待分发器对象不会同时发生。
Priority:动态优先级。
BasePriority:基本优先级。
PriorityDecrement:优先级动态调整过程中的递减值。
Saturation:线程基本优先级调整相对于进程基本优先级是否超过了区间的一半。
EnableStackSwap:内核栈是否准许被换出。
SwapBusy:当前是否正在进程上下文切换。
Alerted:线程在警告模式下是否可以被唤醒。
WaitListEntry:双向链表节点,等待被执行时,作为节点加入某链表
SwapListEntry:单链表节点,内核栈需要被换出时,加入KiStackInSwapListHead为头的链表。另外,线程处于DeferredReady状态时加入DeferredReadyListHead为头的链表。
Queue:队列分发器对象,线程正在处理此队列中的项。
WaitTime:线程进入等待时刻的时间点。
KernelApcDisable/SpecialApcDisable:内核APC和特殊内核APC是否被禁止。
TEB:进程地址空间的一个TEB域
Timer:定时器。
AutoAlignment:与KPROCESS相同
DisableBoost:与KPROCESS相同
WaitBlock:4个KWAIT_BLOCK成员的数组,线程等待的分发器少于4个时,使用这里的空间,不分配新空间。
QueueListEntry:线程处理一个队列项时,加入到队列对象的线程链表中的地址。
TrapFrame:指向KTRAP_FRAME类型的指针。用户保存执行现场。
CallbackStack:线程的回调栈地址,在从内核模式返回用户模式时用。
ServiceTable: 指向系统使用的系统服务表,非GUI线程为KeServiceDescriptorTable,GUI线程为KeServiceDescriptorTableShadow。
IdealProcess:理想处理器
Preempted:是否被高优先级线程抢占了。
ProcessReadyQueue:是否在进程对象的ReadyListHead列表中。
KernelStackResident:线程的内核栈是否驻留在线程中。
Affinity:处理器亲和性,为线程指定的处理器集合必须是该集合的子集。
UserAffinity:线程的用户亲和性。
Process:执行线程的进程对象。
ApcStateIndex:指明当前APC状态在ApcStatePointer域中的索引。
Win32Thread:指向Windows子系统管理的区域的指针。
SuspendApc/SuspendSemaphore:用于支持线程挂起的域。
ThreadListEntry:双链表的节点,线程被创建时,加入到进程的ThreadListHead链表中。
SListFaultAddress:上一次用户模式互锁单链表POP操作发生页面错误的地址。
SuspendSemaphore:与上面有关。
*/
THREADINFO结构体,该结构体拥有4个消息队列,以及一些标志
- Sent Message Queue 发送消息队列,储存SendMessage()不同线程时发送来的消息,是一个指针数组
- Posted Message Queue 登记消息队列,储存PostMessage()发送来的消息(同线程也进队列),是一个指针数组
- Virtualized Input Queue 输入消息队列,接收键盘等虚拟输入信息和系统内部消息队列,是一个指针数组
- Reply Message Queue 响应消息队列,不同线程时,发送线程使用SendMessage函数发送信息后线程被挂起,接收线程处理完后返回的信息存放于此,发送线程被唤醒,发送线程从该队列取出返回信息,是一个指针数组
- nExitCode:确定线程退出状态,是一个int型,不同数值说明线程处于不同状态
- 唤醒标志:判断是否处于唤醒状态
MSG结构
typedef struct tagMSG {
HWND hwnd; //该消息所属的窗口句柄
UINT message; //指定消息的类型
WPARAM wParam; //用于指定消息的附加信息,根据消息不同,代表不同意思
LPARAM lParam; //用于指定消息的附加信息,根据消息不同,代表不同意思
DWORD time; //该消息投递到消息列队当中的时间
POINT pt; //该消息投递到消息列队当时,鼠标的当前位置
} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;
API
函数原型:LRESULT SendMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM IParam);
hWnd:指定接收消息的窗口;HWND_BROADCAST,则消息将被发送到系统中所有顶层窗口,包括无效或不可见的非自身拥有的窗口、被覆盖的窗口和弹出式窗口,但消息不被发送到子窗口;NULL直接返回
Msg:发送的消息
wParam:消息wParam参数,附加参1
IParam:消息lParam参数,附加参2
/*接收线程与SendMessage调用线程属于同一线程,阻塞至处理返回
接收线程与SendMessage调用线程不属于同一线程,消息被投放到send message queue,然后返回*/
LRESULT SendMessageTimeout(
HWND hWnd, // 同SendMessage
UINT Msg, // 同SendMessage
WPARAM wParam, // 同SendMessage
LPARAM lParam, // 同SendMessage
UINT fuFlags, // 消息发送方式
/*
SMTO_ABORTIFHUNG 如果接受进程处于挂起状态,那么此函数将直接返回,超时值无效。
SMTO_BLOCK 以阻塞方式运行
SMTO_NORMAL 等待函数返回请安,线程可以正常处理其他请求
SMTO_NOTIMEOUTIFNOTHUNG 如果接受现成没有挂起,知道超时值,函数才返回
*/
UINT uTimeout, // 消息超时值,hWnd为HWND_BROADCAST时,在每个窗口超时之前,该函数不会返回。因此,总等待时间可以高达 uTimeout * 顶级窗口数
LPDWORD lpdwResult // 消息处理的结果。 此参数的值取决于指定的消息
);
简单demo
server读:
// Win32Server.cpp : 定义应用程序的入口点。
//
#include "stdafx.h"
#include "Win32Server.h"
#define MAX_LOADSTRING 100
#define MSG_CUSTOM_INFO WM_USER+1
// 全局变量:
HINSTANCE hInst; // 当前实例
WCHAR szTitle[MAX_LOADSTRING]; // 标题栏文本
WCHAR szWindowClass[MAX_LOADSTRING]; // 主窗口类名
// 此代码模块中包含的函数的前向声明:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: 在此放置代码。
OutputDebugStringW(_T("------start------"));
// 初始化全局字符串
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_WIN32SERVER, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// 执行应用程序初始化:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WIN32SERVER));
MSG msg;
// 主消息循环:
while (GetMessage(&msg, nullptr, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
//
// 函数: MyRegisterClass()
//
// 目的: 注册窗口类。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WIN32SERVER));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_WIN32SERVER);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassExW(&wcex);
}
//
// 函数: InitInstance(HINSTANCE, int)
//
// 目的: 保存实例句柄并创建主窗口
//
// 注释:
//
// 在此函数中,我们在全局变量中保存实例句柄并
// 创建和显示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // 将实例句柄存储在全局变量中
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
//
// 函数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// 目的: 处理主窗口的消息。
//
// WM_COMMAND - 处理应用程序菜单
// WM_PAINT - 绘制主窗口
// WM_DESTROY - 发送退出消息并返回
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// 分析菜单选择:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TODO: 在此处添加使用 hdc 的任何绘图代码...
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case MSG_CUSTOM_INFO:
OutputDebugStringW(_T("new message"));
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// “关于”框的消息处理程序。
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
client写:
// Win32Client.cpp : 定义应用程序的入口点。
//
#include "stdafx.h"
#include "Win32Client.h"
#define MAX_LOADSTRING 100
#define IDC_BUTTON_CLICK 150
#define MSG_CUSTOM_INFO WM_USER+1
// 全局变量:
HINSTANCE hInst; // 当前实例
WCHAR szTitle[MAX_LOADSTRING]; // 标题栏文本
WCHAR szWindowClass[MAX_LOADSTRING]; // 主窗口类名
// 此代码模块中包含的函数的前向声明:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: 在此放置代码。
// 初始化全局字符串
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_WIN32CLIENT, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// 执行应用程序初始化:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WIN32CLIENT));
MSG msg;
// 主消息循环:
while (GetMessage(&msg, nullptr, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
void OnButtonClick()
{
HWND hWnd = FindWindowW(_T("WIN32SERVER"), _T("Win32Server"));
PostMessageW(hWnd, MSG_CUSTOM_INFO, 123L, 0); //post是放入到接收线程的队列,这里123以值传递的方式,因此没问题,如果是该进程的堆空间(new出来的),则会导致问题
::MessageBox(NULL, L"已发送123456给另一个进程", L"提示", MB_OK);
}
//
// 函数: MyRegisterClass()
//
// 目的: 注册窗口类。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WIN32CLIENT));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_WIN32CLIENT);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassExW(&wcex);
}
//
// 函数: InitInstance(HINSTANCE, int)
//
// 目的: 保存实例句柄并创建主窗口
//
// 注释:
//
// 在此函数中,我们在全局变量中保存实例句柄并
// 创建和显示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // 将实例句柄存储在全局变量中
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
if (!hWnd)
{
return FALSE;
}
CreateWindowW(_T("BUTTON"), _T("Click Me"), WS_CHILD | WS_VISIBLE, 10, 40, 80, 20, hWnd, (HMENU)IDC_BUTTON_CLICK, NULL, NULL);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
//
// 函数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// 目的: 处理主窗口的消息。
//
// WM_COMMAND - 处理应用程序菜单
// WM_PAINT - 绘制主窗口
// WM_DESTROY - 发送退出消息并返回
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// 分析菜单选择:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
case IDC_BUTTON_CLICK:
//进入按钮点击事件
OnButtonClick();
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TODO: 在此处添加使用 hdc 的任何绘图代码...
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// “关于”框的消息处理程序。
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
就是两个win32空的窗口程序,server端跟进程通信相关的有
#define MSG_CUSTOM_INFO WM_USER+1 //通信消息ID
.......
case MSG_CUSTOM_INFO: //是通信消息ID,输出控制台输出
OutputDebugStringW(_T("new message"));
client端跟进程通信相关的有
void OnButtonClick()
{
HWND hWnd = FindWindowW(_T("WIN32SERVER"), _T("Win32Server")); //找到窗口句柄
PostMessageW(hWnd, MSG_CUSTOM_INFO, 123L, 0); //post通信消息
::MessageBox(NULL, L"已发送123456给另一个进程", L"提示", MB_OK);
}