在 WPF 里面有其他軟件完全比不上的超快速的觸摸,這個觸摸是通過 PenImc 獲取的。現在 WPF 開源了,本文就帶大家來閱讀觸摸底層的代碼,閱讀本文需要一點 C# 和 C++ 基礎
現在 WPF 開源,所有源代碼都可以在官方代碼找到,本文只是讓大家能夠更快的了解整個觸摸的代碼和更快的了解代碼,和知道對應的功能在哪個代碼
在WPF的觸摸的 PenThreadWorker 調用 ThreadProc 的方法,就通過 MS.Win32.Penimc.UnsafeNativeMethods.GetPenEvent 方法獲取觸摸。本文僅討論在 PenThreadWorker 下層的內容,在此上層的內容,請看WPF 觸摸到事件
那么在 PenImc 里面做了什么?
在 PenImc 原理里面,其實就是通過共享內存和 COM 的方式通過 RealTimeStylus 的方式快速獲取觸摸消息
先通過 WISPTIS_SM_SECTION_NAME 和 WISPTIS_SM_MUTEX_NAME 分別拿到共享內存和進程鎖這樣可以通過鎖通知共享內存收到消息,然后通過讀取內存的信息返回到上層
整個初始化的代碼放在 PimcContext.cpp 里
在 HRESULT CPimcContext::InitNamedCommunications(__in CComPtr<ITabletContextP> pCtxP)
的方法里面,初始 szSectionName 字符串作為命名管道連接方法
TCHAR szSectionName[MAX_PATH + 1];
StringCchPrintf(
szSectionName,
LENGTHOFARRAY(szSectionName),
WISPTIS_SM_SECTION_NAME,
dwPid,
dwFileMappingId);
而 WISPTIS_SM_SECTION_NAME
的定義如下
#define WISPTIS_SM_MORE_DATA_EVENT_NAME _T("wisptis-1-%d-%u")
#define WISPTIS_SM_MUTEX_NAME _T("wisptis-2-%d-%u")
#define WISPTIS_SM_SECTION_NAME _T("wisptis-3-%d-%u")
#define WISPTIS_SM_THREAD_EVENT_NAME _T("wisptis-4-%u")
此時通過打開內存的方式
m_hFileMappingSharedMemory = OpenFileMapping(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, szSectionName);
可以獲取內存信息
m_pSharedMemoryHeader = (SHAREDMEMORY_HEADER*)MapViewOfFile(
m_hFileMappingSharedMemory, // handle
FILE_MAP_READ | FILE_MAP_WRITE, // desired access
0, // offset in file, High
0, // offset in file, Low
sizeof(SHAREDMEMORY_HEADER)); // number of bytes to map
m_pbSharedMemoryRawData = (BYTE*)MapViewOfFile(
m_hFileMappingSharedMemory, // handle
FILE_MAP_READ, // desired access
0, // offset in file, High
0, // offset in file, Low
m_pSharedMemoryHeader->cbTotal);// number of bytes to map
關於打開的代碼請看
ITabletContextP::UseNamedSharedMemoryCommunications method - Win32 apps
此時就可以通過 m_pbSharedMemoryRawData
獲取內存信息
這就是初始化的代碼
在 WPF 調用 GetPenEvent 方法,將會進入 PimcContext.cpp 的 GetPenEvent 方法
在這個方法里面先通過 MsgWaitForMultipleObjectsEx 等待 Wisp 服務的收集,在收集完成之后會釋放鎖,進入 GetPenEventCore 方法
在 GetPenEventCore 使用很長的判斷邏輯,其中主要是判斷當前是獲取數據才會進入到 WPF 的收集到觸摸點
switch (dwWait)
{
case WAIT_TIMEOUT:
m_fSingleFireTimeout = FALSE; // (only fire the timeout once before more data shows up)
*pEvt = 1; // timeout event
*pCursorId = 0;
*pcPackets = 0;
*pcbPacket = 0;
*pPackets = NULL;
break;
case WAIT_OBJECT_0 + 0: // update
// 忽略代碼
case WAIT_OBJECT_0 + 1: // more data
// 這里就是等待共享內存
DWORD dwWaitAccess = WaitForSingleObject(m_hMutexSharedMemory, INFINITE);
}
通過上面代碼可以看到在 m_hMutexSharedMemory
的信息,可以在 m_pSharedMemoryHeader
讀取
switch (m_pSharedMemoryHeader->dwEvent)
{
case WM_TABLET_PACKET:
case WM_TABLET_CURSORDOWN:
case WM_TABLET_CURSORUP:
*pEvt = m_pSharedMemoryHeader->dwEvent;
*pCursorId = m_pSharedMemoryHeader->cid;
*pcPackets = m_pSharedMemoryHeader->cPackets;
*pcbPacket = m_pSharedMemoryHeader->cbPackets / m_pSharedMemoryHeader->cPackets;
CHR(EnsurePackets(m_pSharedMemoryHeader->cbPackets));
CopyMemory(m_pbPackets, m_pbSharedMemoryPackets, m_pSharedMemoryHeader->cbPackets);
*pPackets = (INT_PTR)m_pbPackets;
#ifdef DELIVERY_PROFILING
for (INT iPacket = 0; iPacket < *pcPackets; iPacket++)
{
INT iOffset = iPacket * (*pcbPacket) / sizeof(LONG);
switch (m_pSharedMemoryHeader->dwEvent)
{
case WM_TABLET_PACKET: ProfilePackets(/*fDown*/FALSE, /*fUp*/FALSE, ((LONG*)m_pbSharedMemoryPackets)[iOffset + 0], ((LONG*)m_pbSharedMemoryPackets)[iOffset + 1]); break;
case WM_TABLET_CURSORDOWN: ProfilePackets(/*fDown*/TRUE, /*fUp*/FALSE, ((LONG*)m_pbSharedMemoryPackets)[iOffset + 0], ((LONG*)m_pbSharedMemoryPackets)[iOffset + 1]); break;
case WM_TABLET_CURSORUP: ProfilePackets(/*fDown*/FALSE, /*fUp*/TRUE, ((LONG*)m_pbSharedMemoryPackets)[iOffset + 0], ((LONG*)m_pbSharedMemoryPackets)[iOffset + 1]); break;
}
}
#endif
break;
case WM_TABLET_CURSORINRANGE:
case WM_TABLET_CURSOROUTOFRANGE:
*pEvt = m_pSharedMemoryHeader->dwEvent;
*pCursorId = m_pSharedMemoryHeader->cid;
*pcPackets = 0;
*pcbPacket = 0;
*pPackets = NULL;
break;
case WM_TABLET_SYSTEMEVENT:
*pEvt = m_pSharedMemoryHeader->dwEvent;
*pCursorId = m_pSharedMemoryHeader->cid;
*pcPackets = 0;
*pcbPacket = 0;
*pPackets = NULL;
m_sysEvt = m_pSharedMemoryHeader->sysEvt;
m_sysEvtData = m_pSharedMemoryHeader->sysEvtData;
break;
default:
*pEvt = 0;
*pCursorId = 0;
*pcPackets = 0;
*pcbPacket = 0;
*pPackets = NULL;
break;
}
定義的代碼放在 pentypes.h 文件
#define WM_TABLET_DEFBASE 0x02C0
#define WM_TABLET_CONTEXTCREATE (WM_TABLET_DEFBASE + 0)
#define WM_TABLET_CONTEXTDESTROY (WM_TABLET_DEFBASE + 1)
#define WM_TABLET_CURSORNEW (WM_TABLET_DEFBASE + 2)
#define WM_TABLET_CURSORINRANGE (WM_TABLET_DEFBASE + 3)
#define WM_TABLET_CURSOROUTOFRANGE (WM_TABLET_DEFBASE + 4)
#define WM_TABLET_CURSORDOWN (WM_TABLET_DEFBASE + 5)
#define WM_TABLET_CURSORUP (WM_TABLET_DEFBASE + 6)
#define WM_TABLET_PACKET (WM_TABLET_DEFBASE + 7)
#define WM_TABLET_ADDED (WM_TABLET_DEFBASE + 8)
#define WM_TABLET_DELETED (WM_TABLET_DEFBASE + 9)
#define WM_TABLET_SYSTEMEVENT (WM_TABLET_DEFBASE + 10)
#define WM_TABLET_MAX (WM_TABLET_DEFBASE + WM_TABLET_MAXOFFSET)
這里的 WM_TABLET_CURSORINRANGE 是 (WM_TABLET_DEFBASE + 3) 也就是 707 對應在 WPF 定義的 PenEventPenInRange 的值
const int PenEventPenInRange = 707;
const int PenEventPenOutOfRange = 708;
const int PenEventPenDown = 709;
const int PenEventPenUp = 710;
const int PenEventPackets = 711;
const int PenEventSystem = 714;
也就是上面的代碼就是整個觸摸的核心代碼
更多代碼請看 https://github.com/dotnet/wpf/
IRealTimeStylus::GetPacketDescriptionData (rtscom.h) - Win32 apps