国家不幸诗家幸,乒乓不好程序好。
嗯,昨天话还没写完,夜里就把问题给解决了。夜里 12 点钟爬上床,心里还是放不下,手机上不太想用 chatgpt,就用传统的 Google,搜索一二,一二再变三四,好,点开一个 stackoverflow 的页面,
How to use CHILDID_SELF?
https://stackoverflow.com/questions/73911444/how-to-use-childid-self
这个小子,把我想问的问题给问出来了,问得还挺认真的,他提到的那篇微软的帖子,我也看过,
How to get caret position in ANY application from C#?
https://learn.microsoft.com/en-us/answers/questions/818647/how-to-get-caret-position-in-any-application-from
C# 和 C++ 不分家的,而且,最后,人家给的是一个完整的 C/C++ 程序,我运行了一下,发现是可以直接跑通的。于是,夜里十二点半,我告诉赵桑,能不能给我五分钟,下床,测试,测试成功,五分钟后,上床,我对赵桑说,赵桑,我的毕业设计结束了,就两行代码,解决了我的问题!激动地辗转反侧,书也不读了,看比赛,乒乓球比赛。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| #include <iostream> #include <Windows.h> #include <oleacc.h>
#pragma comment(lib, "Oleacc.lib")
typedef struct { long x; long y; long w; long h; } Rect;
int main(int argc, char* argv[]) { HWND hwnd; DWORD pid; DWORD tid;
CoInitialize(nullptr);
while (true) { system("cls");
GUITHREADINFO info; info.cbSize = sizeof(GUITHREADINFO);
hwnd = GetForegroundWindow(); tid = GetWindowThreadProcessId(hwnd, &pid);
GetGUIThreadInfo(tid, &info);
IAccessible* object = nullptr; if (SUCCEEDED(AccessibleObjectFromWindow(info.hwndFocus, OBJID_CARET, IID_IAccessible, (void**)&object))) { Rect rect;
VARIANT varCaret; varCaret.vt = VT_I4; varCaret.lVal = CHILDID_SELF;
if (SUCCEEDED(object->accLocation(&rect.x, &rect.y, &rect.w, &rect.h, varCaret))) { std::cout << rect.x << std::endl; }
object->Release(); }
Sleep(10); }
CoUninitialize();
return 0; }
|
魔法的两行是:
1 2
| CoInitialize(nullptr); CoUninitialize();
|
都说 Windows 里面有很多黑魔法,Windows 外面也不少嘛,该死!你就在一个 GetCaretPos
函数里面把所有的东西都给我封装好,不可以吗?
马龙输掉了,输给了林昀儒,我觉得可以接受,林的反手很好,其为人更是讨人喜欢,带有一点腼腆,英语好,有礼貌。
第二天上午,在钩子程序中测试,发现是不可以在钩子程序中使用,程序如下,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| #include <Windows.h> #include <oleacc.h>
#include <iostream>
#pragma comment(lib, "Oleacc.lib")
HHOOK g_hook = NULL;
void getCaret() { typedef struct { long x; long y; long w; long h; } Rect;
HWND hwnd; DWORD pid; DWORD tid;
CoInitialize(nullptr);
GUITHREADINFO info; info.cbSize = sizeof(GUITHREADINFO);
hwnd = GetForegroundWindow(); std::cout << "hwnd test => " << hwnd << '\n'; tid = GetWindowThreadProcessId(hwnd, &pid); std::cout << "tid test => " << tid << '\n';
GetGUIThreadInfo(tid, &info);
IAccessible *object = nullptr; if (SUCCEEDED(AccessibleObjectFromWindow(info.hwndFocus, OBJID_CARET, IID_IAccessible, (void **)&object))) { Rect rect;
VARIANT varCaret; varCaret.vt = VT_I4; varCaret.lVal = CHILDID_SELF; if (SUCCEEDED(object->accLocation(&rect.x, &rect.y, &rect.w, &rect.h, varCaret))) { std::cout << rect.x << ", " << rect.y << std::endl; }
object->Release(); }
CoUninitialize(); }
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { KBDLLHOOKSTRUCT *s = reinterpret_cast<KBDLLHOOKSTRUCT *>(lParam); if (nCode >= 0 && wParam == WM_KEYDOWN) { if ((GetAsyncKeyState(VK_CONTROL) & 0x8000) && (s->vkCode == VK_SPACE)) { getCaret(); } }
return CallNextHookEx(g_hook, nCode, wParam, lParam); }
int main() { g_hook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, NULL, 0);
MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); }
UnhookWindowsHookEx(g_hook);
return 0; }
|
经过研究,发现这是钩子程序本身的问题,它的线程不一样,具体是怎么样一种道理我暂时还没搞明白,折腾了两三个小时之后,我决定直接把这个模块给上到我的输入法程序中,因为在我的输入法程序中,这个获取 caret 的模块是跑在主函数中的,线程一致,所以,试验了一下,果然可以!
我的输入法仓库如下,
https://github.com/fanlumaster/FullIME
大体上,上面就是我解决这个问题的心路历程,这个博客更多还是写给将来的我来回忆的吧。我在昨天白天也研究出了另一种方法,那个方法如我前一篇博客所说,只能解决像 word、notepad、文件管理器这种使用 wpf、winform 技术的经典应用程序中的获取 caret 的问题。
下面,总结一下我目前的两种方法吧,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| #include <Windows.h> #include <oleacc.h>
#include <utility>
#pragma comment(lib, "oleacc.lib")
std::pair<int, int> getCaretPosByGUIThreadInfo() { std::pair<int, int> caretPos; HWND target_window = GetForegroundWindow(); GUITHREADINFO info; info.cbSize = sizeof(GUITHREADINFO); BOOL result = GetGUIThreadInfo(GetWindowThreadProcessId(target_window, NULL), &info) && info.hwndCaret; if (!result) { } POINT pt; pt.x = info.rcCaret.left; pt.y = info.rcCaret.top; ClientToScreen(info.hwndCaret, &pt); if (pt.x == 0 && pt.y == 0) { caretPos.first = 20; caretPos.second = 10; return caretPos; } caretPos.first = static_cast<int>(pt.x); caretPos.second = static_cast<int>(pt.y + 30); return caretPos; }
std::pair<int, int> getCaretPosByAcc() { std::pair<int, int> pos = std::make_pair(100, 100); typedef struct { long x; long y; long w; long h; } Rect;
HWND hwnd; DWORD pid; DWORD tid;
CoInitialize(nullptr);
GUITHREADINFO info; info.cbSize = sizeof(GUITHREADINFO);
hwnd = GetForegroundWindow(); tid = GetWindowThreadProcessId(hwnd, &pid);
GetGUIThreadInfo(tid, &info);
IAccessible *object = nullptr; if (SUCCEEDED(AccessibleObjectFromWindow(info.hwndFocus, OBJID_CARET, IID_IAccessible, (void **)&object))) { Rect rect;
VARIANT varCaret;
varCaret.vt = VT_I4; varCaret.lVal = CHILDID_SELF; if (SUCCEEDED(object->accLocation(&rect.x, &rect.y, &rect.w, &rect.h, varCaret))) {
pos.first = rect.x + 8; pos.second = rect.y + rect.h; }
object->Release(); }
CoUninitialize(); return pos; }
|
关于那两行魔法代码,即初始化和释放 COM 的代码,可以放在 main 函数中,这样就不用每次都初始化和释放了。
附一点点生活的小事儿,今天重看了异邦人·无皇刃潭,最后那段打斗及配乐依旧令我震撼。
参考:
1、https://stackoverflow.com/questions/73911444/how-to-use-childid-self
2、https://learn.microsoft.com/en-us/answers/questions/818647/how-to-get-caret-position-in-any-application-from
3、https://baohaojun.github.io/blog/2013/10/04/0-Win32-IME-Programming.html
4、https://lindexi.gitee.io/post/WPF-%E8%87%AA%E5%AE%9A%E4%B9%89%E6%96%87%E6%9C%AC%E6%A1%86%E8%BE%93%E5%85%A5%E6%B3%95-IME-%E8%B7%9F%E9%9A%8F%E5%85%89%E6%A0%87.html
5、ChatGPT