Hello @soso ,
Thank you for the very detailed write-up. I built a small test text service to compare the behavior against what you are seeing, and the results point to a fairly clear direction, so let me summarize it plainly and address each of your questions.
When a profile is enabled and activated correctly, the activation does apply beyond the calling process. In my test, a fresh separate process read the profile back as active (GetActiveLanguageProfile returned S_OK), and every newly launched TSF app, including Notepad, loaded the text service DLL on its own. Because you are seeing the opposite, the most likely explanation is that your activation is not actually taking effect for the interactive session, even though the call returns S_OK. That directly answers your first two questions: per the documentation, TF_IPPMF_FORSESSION activates the profile for all threads in the current desktop (while TF_IPPMF_FORPROCESS is the per-process scope), so the scope is not a single process or thread, and an S_OK activation followed by S_FALSE from another fresh process is not the expected outcome.
On your third question, S_FALSE is not an error, it simply means the text service is not the currently active one for that process (and pguidProfile comes back as GUID_NULL), so a fresh process reading S_FALSE is a reliable sign that the activation did not persist. Reading it from a separate process is a valid check, but a more precise one is ITfInputProcessorProfileMgr::GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD), which returns the active profile record so you can compare its clsid, langid, and dwFlags and confirm your CLSID is the active keyboard profile with the enabled and active flags set.
For your fourth question, the recommended sequence is to make sure the service is fully registered first, then enable, then activate once, then verify. Concretely: register the COM server (InprocServer32), register the text service and language profile with ITfInputProcessorProfiles::Register and AddLanguageProfile, and register the keyboard category with ITfCategoryMgr::RegisterCategory(GUID_TFCAT_TIP_KEYBOARD); then call EnableLanguageProfile(clsid, langid, guidProfile, TRUE); then ITfInputProcessorProfileMgr::ActivateProfile(TF_PROFILETYPE_INPUTPROCESSOR, langid, clsid, guidProfile, NULL, TF_IPPMF_FORSESSION); and finally verify with GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD). You do not need to re-activate per application. Once it is the active keyboard profile, newly launched TSF apps load the DLL on their own.
For your fifth question, if ActivateProfile returns S_OK but a new Notepad does not load the DLL, it is worth checking both the runtime state and the registration. On the state side, ctfmon.exe is what propagates the active profile to other processes, so if your activation runs at logon before ctfmon is fully up, which fits your "after Windows restart" scenario, the change may not broadcast, and it is worth confirming ctfmon.exe is running when you activate. It also matters that your activator runs on the same desktop and at the same integrity level as the target apps, because TF_IPPMF_FORSESSION is scoped to the current desktop, so if the activator is elevated while Notepad runs as the normal user, the change may not reach it. A very short-lived activator that exits immediately may also not commit the session state reliably, so doing the activation on an STA thread that pumps messages is the safer pattern. On the registration side, please confirm the keyboard category (GUID_TFCAT_TIP_KEYBOARD) is registered for your CLSID, that the profile is actually enabled, and consider calling ITfInputProcessorProfiles::SetDefaultLanguageProfile so your service is the default profile for that language, which helps newly launched apps pick it up consistently after a restart.
On your sixth question, the intended design is to enable and activate the profile once per session, not once per application. After that, TSF loads your service into each newly launched TSF-enabled app automatically. Per-process activation (TF_IPPMF_FORPROCESS) is only for the narrow case where you deliberately want the change limited to a single process.
If it helps to narrow it down, could you reproduce the issue and, in a fresh process, capture what GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD) returns (the clsid, langid, and dwFlags), and confirm whether ctfmon.exe is running and whether your activator and the target apps run on the same desktop and integrity level. That should make it clear whether the activation is reaching the interactive session or being isolated from it.
References
- Text Services Framework (overview)
- ITfInputProcessorProfileMgr::ActivateProfile (and the TF_IPPMF_* flags)
- ITfInputProcessorProfileMgr::GetActiveProfile
- ITfInputProcessorProfiles::Register
- ITfInputProcessorProfiles::EnableLanguageProfile
- ITfInputProcessorProfiles::GetActiveLanguageProfile
- ITfInputProcessorProfiles::SetDefaultLanguageProfile
- ITfCategoryMgr::RegisterCategory
Hope this helps. If you found my response helpful or informative, I would greatly appreciate it if you could follow this guide for your confirmation.
Thank you.