UIDiag.cpp 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. // =============================================================================
  2. // 诊断日志唯一出口的实现。说明见 Utils/UIDiag.h。
  3. //
  4. // 为什么是独立 .cpp(而不是 header-only + inline):
  5. // 如果写成头文件里的 inline 函数,每个包含它的 .cpp 都会有一份 static 局部
  6. // 变量副本(s_buf / path 缓存),日志行为虽然仍正确,但每次都重新
  7. // GetModuleFileName + 打开/关闭文件,且在 DLL 边界上语义容易含混。
  8. // 放 .cpp 里只编译一份 —— 这也是"全库只有本 .cpp 真正落盘"的实现基础。
  9. // =============================================================================
  10. #include "StdAfx.h"
  11. #include "UIDiag.h"
  12. #include <stdarg.h>
  13. #include <stdio.h>
  14. #include <stdlib.h>
  15. namespace DuiLibDiag
  16. {
  17. // ★ 全库唯一的日志开关:需要重新开启诊断时把这里改回 true 并重编。
  18. //
  19. // 2026-09-24(第 41 轮):Win10 触摸键盘输入问题(第 40 轮修:CEditUI 合成鼠标手势
  20. // 只发 DOWN 不发 UP ⇒ 原生 EDIT 停在半截手势)已真机验收通过,收尾关闭诊断输出。
  21. // 2026-09-25(R37):toast 加圆角后完全消失,靠 R35/R36 三轮埋点现场定位到
  22. // 「UpdateLayeredWindow 返回 FALSE / ERROR_INVALID_PARAMETER(87),位图正确却提交
  23. // 不上去 ⇒ 整窗空白」。真因是 toast_wnd.xml 的 alpha="230"(SetLayeredWindowAttributes
  24. // ⇒ 系统重定向模式)与 roundcorner="10,10"(要调 ULW_ALPHA)两套 alpha 机制互斥;
  25. // 已在 SetOpacity / SetRoundCorner / RefreshLayeredStyleBit 处根治。取证埋点使命
  26. // 完成,再次关闭;下次排查按同样的「装现场再修」套路走。
  27. // 关闭后 Enabled() 是编译期常量 false,整个 Log 函数体会被当死代码消除
  28. // ⇒ 运行时零开销、零文件 IO,二进制里也不再出现 touch_trace.log / DIAG-BUILD 字样。
  29. // 埋点代码全部保留在源码里,随时改回 true 重编即可复现现场。
  30. inline bool Enabled()
  31. {
  32. return false;
  33. }
  34. // 极简 TCHAR* → UTF-8 char* 转换(仅用于日志路径;非线程安全,够用即可)
  35. static const char* Narrow(LPCTSTR s)
  36. {
  37. static char s_buf[MAX_PATH * 3] = { 0 };
  38. #ifdef _UNICODE
  39. s_buf[0] = '\0';
  40. if (s != NULL)
  41. ::WideCharToMultiByte(CP_UTF8, 0, s, -1, s_buf, (int)sizeof(s_buf) - 1, NULL, NULL);
  42. return s_buf;
  43. #else
  44. return s;
  45. #endif
  46. }
  47. void Log(LPCTSTR fmt, ...)
  48. {
  49. if (!Enabled()) return;
  50. // ★ 版本横幅 —— 整个日志里只写一次。
  51. //
  52. // 存在的唯一理由:收银机不是本机,DLL 要手工拷过去。用户说"还是不行"时,
  53. // 第一步永远是先确认"他跑的是不是这一轮编出来的 DLL"。
  54. // 只要日志第一行有这行横幅,就说明新库确实被加载了。
  55. static bool s_bBannerWritten = false;
  56. const bool bWriteBanner = !s_bBannerWritten;
  57. s_bBannerWritten = true;
  58. TCHAR path[MAX_PATH] = { 0 };
  59. ::GetModuleFileName(NULL, path, MAX_PATH);
  60. TCHAR* p = _tcsrchr(path, _T('\\'));
  61. if (p != NULL) *(p + 1) = _T('\0');
  62. _tcscat_s(path, MAX_PATH, _T("touch_trace.log"));
  63. TCHAR msg[1024] = { 0 };
  64. va_list ap;
  65. va_start(ap, fmt);
  66. _vsntprintf_s(msg, _countof(msg), _TRUNCATE, fmt, ap);
  67. va_end(ap);
  68. char utf8[2048] = { 0 };
  69. #ifdef _UNICODE
  70. if (::WideCharToMultiByte(CP_UTF8, 0, msg, -1, utf8, (int)sizeof(utf8) - 1, NULL, NULL) <= 0)
  71. return;
  72. #else
  73. _tcscpy_s(utf8, _countof(utf8), msg);
  74. #endif
  75. FILE* f = NULL;
  76. // 保护:单个日志超过 32MB 就不再追加,避免在收银机上长期运行把日志写到失控。
  77. // (全量 INPUT-TRACE 很啰嗦,阈值从 4MB 放宽到 32MB,否则一轮诊断还没跑完就静默停写了。)
  78. // 注意:GetFileAttributesEx 是 TCHAR 宏(Unicode 工程下展开为 ...ExW),
  79. // 必须传 TCHAR 的 path,不能传 Narrow(path) 得到的 UTF-8 char*(否则 C2664)。
  80. WIN32_FILE_ATTRIBUTE_DATA fad = { 0 };
  81. if (::GetFileAttributesEx(path, GetFileExInfoStandard, &fad)
  82. && fad.nFileSizeHigh == 0 && fad.nFileSizeLow >= (32u * 1024u * 1024u))
  83. return;
  84. // fopen_s 只吃 char*(窄字符路径),这里才需要 UTF-8 的 Narrow(path)
  85. if (fopen_s(&f, Narrow(path), "a") == 0 && f != NULL)
  86. {
  87. if (bWriteBanner)
  88. {
  89. fprintf(f, "======== DIAG-BUILD 2026-09-25 R35-toast (SetFocus(NULL) null-deref fix; diagnostics OFF by default) ========\n");
  90. }
  91. fprintf(f, "[%8u][T%5u] %s\n", (unsigned)::GetTickCount(), (unsigned)::GetCurrentThreadId(), utf8);
  92. // 立刻冲盘:程序被强杀 / 挂死 / 断电时,日志尾部(往往正是关键现场)不能丢。
  93. fflush(f);
  94. fclose(f);
  95. }
  96. }
  97. // ★【R49d】键盘追踪开关:与 Enabled() 独立。要彻底关掉键盘追踪就把它改回 false。
  98. inline bool KeyTraceEnabled()
  99. {
  100. return true;
  101. }
  102. // 与 Log() 同构,只换文件名与开关;逻辑一字不改(上限改成 4MB,
  103. // 因为它只记录 ESC / 回车 / Tab,正常一天也就几十到几百行)。
  104. void LogKey(LPCTSTR fmt, ...)
  105. {
  106. if (!KeyTraceEnabled()) return;
  107. TCHAR path[MAX_PATH] = { 0 };
  108. ::GetModuleFileName(NULL, path, MAX_PATH);
  109. TCHAR* p = _tcsrchr(path, _T('\\'));
  110. if (p != NULL) *(p + 1) = _T('\0');
  111. _tcscat_s(path, MAX_PATH, _T("dlgkey_trace.log"));
  112. TCHAR msg[1024] = { 0 };
  113. va_list ap;
  114. va_start(ap, fmt);
  115. _vsntprintf_s(msg, _countof(msg), _TRUNCATE, fmt, ap);
  116. va_end(ap);
  117. char utf8[2048] = { 0 };
  118. #ifdef _UNICODE
  119. if (::WideCharToMultiByte(CP_UTF8, 0, msg, -1, utf8, (int)sizeof(utf8) - 1, NULL, NULL) <= 0)
  120. return;
  121. #else
  122. _tcscpy_s(utf8, _countof(utf8), msg);
  123. #endif
  124. FILE* f = NULL;
  125. WIN32_FILE_ATTRIBUTE_DATA fad = { 0 };
  126. if (::GetFileAttributesEx(path, GetFileExInfoStandard, &fad)
  127. && fad.nFileSizeHigh == 0 && fad.nFileSizeLow >= (4u * 1024u * 1024u))
  128. return;
  129. if (fopen_s(&f, Narrow(path), "a") == 0 && f != NULL)
  130. {
  131. fprintf(f, "[%8u][T%5u] %s\n", (unsigned)::GetTickCount(), (unsigned)::GetCurrentThreadId(), utf8);
  132. fflush(f);
  133. fclose(f);
  134. }
  135. }
  136. void LogButtonActivate(LPCTSTR name, bool captureState, unsigned buttonState, bool pushed)
  137. {
  138. // 位域含义(取自 UIManager.h 的 #define,勿凭记忆写):
  139. // UISTATE_DISABLED=0x04 / UISTATE_HOT=0x08
  140. // UISTATE_PUSHED=0x10 / UISTATE_CAPTURED=0x40
  141. Log(_T("BUTTON-ACTIVATE name=%s mgrCaptured=%d state=0x%04X pushed=%d hot=%d captured=%d disabled=%d"),
  142. name != NULL ? name : _T("null"),
  143. (int)(captureState ? 1 : 0),
  144. buttonState,
  145. (int)(pushed ? 1 : 0),
  146. (int)((buttonState & 0x0008u) ? 1 : 0),
  147. (int)((buttonState & 0x0040u) ? 1 : 0),
  148. (int)((buttonState & 0x0004u) ? 1 : 0));
  149. }
  150. }