在 WPF 裡面有其他軟體完全比不上的超快速的觸摸,這個觸摸是通過 PenImc 獲取的。現在 WPF 開源了,本文就帶大家來閱讀觸摸底層的代碼,閱讀本文需要一點 C# 和 C++ 基礎 ...
在 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