我看到一个问题是在 win7 系统上,如果开机启动的软件是 WPF 软件,而这个 WPF 软件在系统的 wisptis 进程启动之前就启动了,那么 WPF 将会调起 wisptis 进程。而在 wisptis 进程已经启动完成,此时启动 WPF 进程不会再打开新的 wisptis 进程。但是被 WPF 启动的 wisptis 进程存在这样的问题,在触摸屏上 win7 的双指打开右键菜单等功能不可用

在 WPF 启动时,将会在 Window 类的 Visibility 修改时调用到 WispLogic.RegisterHwndForInput 方法进行初始化触摸,这部分详细请看 WPF 触摸到事件 而在初始化触摸时,需要用到 PenIMC 的逻辑

在 win7 系统上,触摸需要通过 wisptis 进程的辅助才能让 WPF 进程能够完成实时触摸,这里的 wisptis 是 Windows Ink Services Platform Tablet Input Subsystem 进程,用于处理触摸书写等功能。也是 RealTimeStylus 的提供,通过一些不靠谱的文档和经验,其实 PenIMC 的核心逻辑就是 RealTimeStylus 提供的。上面这句话对或不对我不敢说,只能说用 RealTimeStylus 可以实现 PenIMC 的效果,而且 API 和参数差不多

那么 PenIMC 又是什么呢?其实 PenIMC 是 penimc2_v0400.dll 文件,在不同的版本的 .NET Framework 和系统上这个文件是不同的,包括文件名也不同,看这个文件命名就知道。没错,你可以在 penimc2_v0400.dll 文件所在的文件夹找到一堆 penimc 文件。这个文件就是提供给 WPF 的触摸核心 PenThreadWorker 的 COM 组件(其实没有文档说这货是纯 COM 组件) 也就是和触摸相关的

也就是在 WPF 窗口打开显示将会初始化触摸,初始化触摸需要依赖 PenIMC 组件,这个库本质是对 RealTimeStylus 的封装,依赖于 win7 的 Windows Ink Services Platform Tablet Input Subsystem (wisptis) 服务。这也能说明为什么会尝试调起 wisptis 进程

具体调起的代码放在 WPF 的 src\Microsoft.DotNet.Wpf\src\PenImc\dll\PimcManager.cpp 文件里面,实现的代码大概如下

void CPimcManager::LoadWisptis()
{
    // 删除一些代码
        // **********
        // NOTE:    PenIMC has duplicated the code for loading wisptis from InkObj.
        //          Whenever WIC team makes any changes, we should coordinate with them to work on fixes.
        // **********
        if (IsVistaOrGreater())
        {
            // DDVSO 144719. There are some scenarios were we must skip loading wisptis since 
            // they are not supported and can cause delays or crashes.
            if (ShouldLoadWisptis())
            {
                // we do this to signal TabSvc that it needs to spin up wisptis
                //  so that it is at the right IL.
                HANDLE hEventRequest = OpenEvent(EVENT_MODIFY_STATE, FALSE, PENPROCESS_WISPTIS_REQUEST_EVENT);
                HANDLE hEventRunning = OpenEvent(SYNCHRONIZE, FALSE, PENPROCESS_WISPTIS_RUNNING_EVENT);

                //if we don't have the event (TabSvc isn't running), or we timed out,
                // that means Wisptis isn't running, so we'll start it; we do this via
                // ShellExecute so that it gets started at high-IL (as indicated by
                // Wisptis's manifest) to avoid IL-mismatch issues
                //we allow wisptis to be started without TabSvc for backcompat
            
                if(hEventRunning == NULL)
                {
                   // create the event since TabSvc isn't running
                   hEventRunning = CreateEvent(NULL, TRUE, FALSE, PENPROCESS_WISPTIS_RUNNING_EVENT);
                }

                if(hEventRequest != NULL && hEventRunning != NULL)
                {
                    //when this wait returns, wisptis will have registered its classes with COM
                    //if this fails or times out, we'll risk starting wisptis at a mismatched IL
                    DWORD dwResult = SignalObjectAndWait(hEventRequest, hEventRunning, 30000 /* thirty seconds */, FALSE);

                    hr = dwResult == WAIT_OBJECT_0 ? S_OK : E_FAIL;
                }

                // DDVSO:398137
                // Since hEventRequest is no longer of use at this point, close the handle.
                SafeCloseHandle(&hEventRequest);

                if(/* wait timed out */ FAILED(hr) ||
                   /* couldn't open the event for some reason */ hEventRunning == NULL ||
                   /* wisptis isn't already running */ WaitForSingleObject(hEventRunning, 0) == WAIT_TIMEOUT)
                {
                    PVOID pvOldValue = NULL;
                    BOOL bIsWow64 = FALSE;
                    LPFNWOW64DISABLEWOW64FSREDIRECTION fnWow64DisableWow64FsRedirection = NULL;
                    LPFNWOW64REVERTWOW64FSREDIRECTION fnWow64RevertWow64FsRedirection = NULL;
                    HMODULE hKernel32 = NULL;

                    // Check whether this is running under Wow64 and, if so, disable file system redirection
                    // on the current thread - otherwise it will look for wisptis in the syswow64 directory
                    // instead of system32.
                    TPDBG_VERIFY(IsWow64Process(GetCurrentProcess(),&bIsWow64));
                    if (bIsWow64)
                    {
                        // NOTICE-2006/06/13-WAYNEZEN,
                        // Since penimc may also run on the top of XPSP2, We cannot call Wow64DisableWow64FsRedirection/Wow64RevertWow64FsRedirection
                        // directly. Otherwise it will cause Entry Point Not Found error even though we don't really on those functions on 32-bit XP.
                        // So we have to use GetProcAddress to resovle the function address dynamically.
                        hKernel32 = GetModuleHandle(KERNEL32_NAME);
                        fnWow64DisableWow64FsRedirection = (LPFNWOW64DISABLEWOW64FSREDIRECTION)GetProcAddress(
                                                                hKernel32, WOW64DISABLEWOW64FSREDIRECTION_NAME);
                        fnWow64RevertWow64FsRedirection = (LPFNWOW64REVERTWOW64FSREDIRECTION)GetProcAddress(
                                                                hKernel32, WOW64REVERTWOW64FSREDIRECTION_NAME);

                        TPDBG_VERIFY(fnWow64DisableWow64FsRedirection(&pvOldValue));
                    }

                    SHELLEXECUTEINFO sei = {0};

                    sei.cbSize = sizeof(sei);
                    sei.lpFile = WISPTIS_DIR WISPTIS_NAME;
                    sei.lpParameters = WISPTIS_MANUAL_LAUNCH;
                    sei.lpVerb = NULL;
                    sei.fMask = SEE_MASK_FLAG_DDEWAIT | SEE_MASK_DOENVSUBST | SEE_MASK_FLAG_NO_UI;
                    sei.lpDirectory = WISPTIS_DIR;
                    sei.hInstApp = (HINSTANCE)0;

                    // 就是在这里启动进程的
                    BOOL bResult = ShellExecuteEx(&sei);

                    // Restore the file system redirection settings.
                    if (bIsWow64)
                    { 
                        TPDBG_VERIFY(fnWow64RevertWow64FsRedirection(pvOldValue));
                    }

                    hr = bResult ? S_OK : E_FAIL;
                    if(FAILED(hr))
                    {
                       OutputDebugString(L"PimcManager::LoadWisptis failed to ShellExecuteEx.\r\n");
                    }
                }
                   
                if(SUCCEEDED(hr) && hEventRunning != NULL)
                {
                    (void)WaitForSingleObject(hEventRunning, PENPROCESS_WISPTIS_LOADING_TIMEOUT /* 30 seconds */);
                    //regardless of the return from this, we'll still try to spin wisptis up via COM
                }
                
                SafeCloseHandle(&hEventRunning);

                if(SUCCEEDED(hr))
                {
                   CHR(m_pMgrS.CoCreateInstance(CLSID_TabletManagerS)); //, NULL, CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER));

                   // Ensure the WISP tablet manager is added to the GIT.
                   m_wispManagerLock = GitComLockableWrapper<ITabletManager>(m_pMgrS, ComApartmentVerifier::Mta());
                   CHR(m_wispManagerLock.CheckCookie());

                   m_fLoadedWisptis = TRUE;
                }
            }
        }
}

更多关于 PenImc 的原理请参阅 WPF 触摸底层 PenImc 是如何工作的

而为什么 WPF 启动的 wisptis 进程有很多坑?大部分只是启动进程权限问题,比如进程被删除等,更详细我也不知道

规避方法是什么?其实不让触摸执行也就是可以了,但是我如何让 WPF 还能交互?没关系,假装自己是一个古老的应用,只支持鼠标消息就可以啦。但是我想要做多指触摸怎么办?先不要触摸,等待 wisptis 进程启动之后,通过 WPF 模拟触摸设备 方案重新注册一遍触摸

我一开始启动太快了,没关系,我一开始启动的是一个 win32 的启动图,等待后台逻辑判断 wisptis 启动之后,我才打开 WPF 的窗口。根据上面的说法,其实窗口没有修改 Visiliblity 之前是不会初始化触摸的,也就是不会启动 wisptis 进程的

现在 win7 已经不受微软支持了,是时候升级 win10 啦。因为 Win10 下不再是一个进程了,详细请看 Win10 的 WPF 程序的 wisptis 服务是附加到进程的窗口

更多触摸请看 WPF 触摸相关


本文会经常更新,请阅读原文: https://blog.lindexi.com/post/%E4%B8%BA%E4%BB%80%E4%B9%88-WPF-%E8%BD%AF%E4%BB%B6%E5%9C%A8-win7-%E5%90%AF%E5%8A%A8%E6%97%B6%E4%BC%9A%E5%B0%9D%E8%AF%95%E8%B0%83%E8%B5%B7-wisptis-%E8%BF%9B%E7%A8%8B.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

如果你想持续阅读我的最新博客,请点击 RSS 订阅,推荐使用RSS Stalker订阅博客,或者收藏我的博客导航

知识共享许可协议 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接: https://blog.lindexi.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系

微软最具价值专家


无盈利,不卖课,做纯粹的技术博客

以下是广告时间

推荐关注 Edi.Wang 的公众号

欢迎进入 Eleven 老师组建的 .NET 社区

以上广告全是友情推广,无盈利