لماذا تفشل رسالة TVM_GETITEM على وجهات نظر شجرة comctl32.ocx أو mscomctl.ocx؟
سؤال
لقد كتبت وظيفة يمكن أن تسفر عن نص العنصر لعرض الشجرة، حتى لو كانت طريقة عرض الأشجار في عملية عن بعد. تقوم الوظيفة بتخصيص قطعتين من الذاكرة في العملية البعيدة، يملأ بنية TVITEM (التي تم نسخها في العملية البعيدة)، ويرسل رسالة TVM_GETITEM وأخيرا يقرأ محتويات ذاكرة الذاكرة البعيدة الثانية مرة أخرى إلى المخزن المؤقت المحلي. هذا هو الرمز:
std::string getTreeViewItemText( HWND treeView, HTREEITEM item )
{
DWORD pid;
::GetWindowThreadProcessId( treeView, &pid );
HANDLE proc = ::OpenProcess( PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, FALSE, pid );
if ( !proc )
// handle error
TVITEM tvi;
ZeroMemory( &tvi, sizeof(tvi) );
LPVOID tvi_ = ::VirtualAllocEx( proc, NULL, sizeof(tvi), MEM_COMMIT, PAGE_READWRITE);
if ( !tvi_ )
// handle error
TCHAR buffer[100] = { 'X' };
LPVOID txt_ = ::VirtualAllocEx( proc, NULL, sizeof(buffer), MEM_COMMIT, PAGE_READWRITE );
if ( !txt_ )
// handle error
tvi.mask = TVIF_TEXT | TVIF_HANDLE;
tvi.pszText = (LPTSTR)txt_;
tvi.cchTextMax = sizeof(buffer) / sizeof(buffer[0] );
tvi.hItem = item;
if ( !::WriteProcessMemory( proc, tvi_, &tvi, sizeof(tvi), NULL ) )
// handle error
if ( !::SendMessage( treeView, TVM_GETITEM, 0, (LPARAM)tvi_ ) )
// handle error
if ( !::ReadProcessMemory( proc, (LPCVOID)txt_, buffer, sizeof( buffer ), NULL ) )
// handle error
::VirtualFreeEx( proc, tvi_, 0, MEM_RELEASE );
::VirtualFreeEx( proc, txt_, 0, MEM_RELEASE );
::CloseHandle( proc );
return buffer;
}
يعمل هذا الرمز بشكل جيد للغاية مع طرق عرض الأشجار العادية التي تحصل عليها عند تمرير WC_TREEVIEW
اسم الفصل إلى CreateWindow
. وبعد ومع ذلك، لاحظت أنه لا يعمل مع الأشجار الأحدث كما توفره MS Common Controls V5 (COMCTL32.OCX) أو MS Common Controls v6 (mscomctl.ocx). في تلك الحالات، يكون النص المرتجع فارغا دائما (المخزن المؤقت هو كل الأصفار). لقد لاحظت أيضا أن دعوة SendMessage تعود صفر (وبالتالي التعامل مع الأخطاء الذي يشير إليه // handle error
تعليقات أعلاه الركلات في). من غير الواضح بالنسبة لي ما إذا كان هذا يشير حقا إلى خطأ، في أي حال، يتم ملء المخزن المؤقت بجميع الأصفار.
يبدو أن جميع رسائل عرض الأشجار الأخرى (مثل TVM_GetitemRect) تعمل بشكل جيد تماما.
هل يعرف أي شخص لماذا هذا؟ حاولت اللعب مع علم يونيكود (لاحظت ذلك TVM_GETITEM
هو إما محددة ل TVM_GETITEMA
أو TVM_GETITEMW
) ولكن هذا لا يبدو للمساعدة.
المحلول
حسنا، دعونا نقدمها لقطة أخرى.
Newer TreeViews نتوقع TVITEMEX
بدلاً من TVITEM
, ، وبما أن لا يوجد معتاد cbSize
الحقل، عنصر التحكم غير قادر على معرفة الإصدار الذي يتلقاه ويفترض TVITEMEX
. وبعد ربما هناك مشكلة مع TreeView غير قادر على الوصول إلى آخر أعضاء TVITEMEX
لأنه لا يتم تخصيص الذاكرة. جرب استخدام TVITEMEX
أو تخصيص ذاكرة أكثر قليلا ل TVITEM
من المطلوبة فعلا.
أيضا النظر في ذلك
لن يتم تخزين النص المرتجع بالضرورة في المخزن المؤقت الأصلي الذي تم تمريره بواسطة التطبيق. من الممكن أن يشير PszText إلى نص في مخزن مؤقت جديد بدلا من وضعه في المخزن المؤقت القديم.
لذلك قد تحتاج إلى القراءة من قطعة واحدة مختلفة من ذاكرة العملية.
وعلى المخزن المؤقت صفرية لأن VirtualalloCex يعيد تعيين الذاكرة.
وكمنتج آخر وربما عديمة الفائدة، حاول استخدام MEM_RESERVE|MEM_COMMIT
بدلا من مجرد MEM_COMMIT
.
نصائح أخرى
لا يعمل الكود كما هو متوقع إذا تم تجميعه مع Unicode المحدد، لكن العملية البعيدة ليست (أو الطريقة الأخرى الأخرى). يجب عليك الاتصال iswindowunicode. أولا في treeView
التعامل مع التحقق مما إذا كان الجانب البعيد يتوقع رسائل يونيكود.
هناك حاجة إلى ذلك منذ التنظيم القياسي في اتجاهين أي sendmessage لا يكفي في هذه الحالة: عليك أن ترسل رسالتين مختلفتين تماما اعتمادا على ما إذا كان الجانب البعيد هو نافذة Unicode أم لا. إذا كان Unicode، استخدم SendMessageW مع TVM_GETITEMW. إذا كان ذلك ansi، فاستخدم sendmessagea مع tvm_getitema.
ينطبق هذا على جميع عناصر التحكم الشائعة، ولكن ليس إلى المجموعة الأساسية من عناصر التحكم (التي تستخدم رسائل النافذة <1024).
أعتقد أيضا أن الكود سوف ينكسر إذا تم تجميعه في ثنائي 64 بت، ولكن العملية البعيدة هي 32 بت (أو الطريقة الأخرى الأخرى). وذلك لأن التعليمات البرمجية هي محلية (قل: 64bit) TVITEM في العملية البعيدة، ثم تتوقع العملية عن بعد TOREAD كما هو متوقع أثناء التعامل مع رسالة TVM_GETITEM (A | W). ومع ذلك، قد يكون حجم الهيكل مختلفا (بسبب أحجام المؤشر المختلفة).
استخدم Spy ++ لمعرفة ما إذا كان TreeView يعالج WM_NOTIFY الرسائل مع علامة إعلام nm_customdraw. إذا فعل ذلك، إذن، حظ سيئ. يتم تخزين البيانات الفعلية داخليا بطريقة أو بأخرى وحصلت على فرصة ضئيلة لسحبها.
ينطبق هذا بنفس القدر على الإصدارات السابقة من Windows BTW.