Advanced C++ and .NET interop
В этой статье речь пойдет о проблемах использования элементов управления WinForms из нативного приложения, MFC например.
В предыдущем
выпуске серии статей о взаимодействии управляемого и неуправляемого кода мы
выяснили, что .NET форма испытывает некие трудности при использовании ее метода
Show
из неуправляемого приложения - фокус не работает, не двигается
по нажатию клавиши Tab
. Пришло время разобраться почему так происходит.
Движением фокуса в контейнерах WinForms занимается пачка методов, самый важный
из которых -
ContainerControl::ProcessTabKey
. Давайте выясним каким образом
поток выполнения попадает в этот метод:
> WinFormsMFCHost.exe!WinFormsMFCHost::SampleForm::ProcessTabKey(bool forward = true) Line 48 C++
System.Windows.Forms.dll!System.Windows.Forms.ContainerControl.ProcessDialogKey(System.Windows.Forms.Keys keyData = LButton | Back) Line 1141 + 0x19 bytes C#
System.Windows.Forms.dll!System.Windows.Forms.Form.ProcessDialogKey(System.Windows.Forms.Keys keyData) Line 5245 + 0x9 bytes C#
WinFormsMFCHost.exe!WinFormsMFCHost::SampleForm::ProcessDialogKey(System::Windows::Forms::Keys keyData = System::Windows::Forms::Keys::Tab) Line 43 + 0xb bytes C++
System.Windows.Forms.dll!System.Windows.Forms.ContainerControl.ProcessDialogKey(System.Windows.Forms.Keys keyData) Line 1152 + 0x16 bytes C#
WinFormsMFCHost.exe!WinFormsMFCHost::SampleControl::ProcessDialogKey(System::Windows::Forms::Keys keyData = System::Windows::Forms::Keys::Tab) Line 42 + 0xb bytes C++
System.Windows.Forms.dll!System.Windows.Forms.Control.ProcessDialogKey(System.Windows.Forms.Keys keyData) Line 10170 + 0x14 bytes C#
System.Windows.Forms.dll!System.Windows.Forms.Control.PreProcessMessage(ref System.Windows.Forms.Message msg) Line 9864 + 0xf bytes C#
System.Windows.Forms.dll!System.Windows.Forms.Control.PreProcessControlMessageInternal(System.Windows.Forms.Control target = {System.Windows.Forms.TabControl}, ref System.Windows.Forms.Message msg = {System.Windows.Forms.Message}) Line 9949 + 0x10 bytes C#
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.PreTranslateMessage(ref System.Windows.Forms.NativeMethods.MSG msg = {System.Windows.Forms.NativeMethods.MSG}) Line 3618 + 0xb bytes C#
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.LocalModalMessageLoop(System.Windows.Forms.Form form = {WinFormsMFCHost.SampleForm}) Line 3503 + 0xb bytes C#
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(int reason = 0x00000004, System.Windows.Forms.ApplicationContext context = {System.Windows.Forms.Application.ModalApplicationContext}) Line 3429 + 0xd bytes C#
System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoop(int reason, System.Windows.Forms.ApplicationContext context) Line 3306 + 0xa bytes C#
System.Windows.Forms.dll!System.Windows.Forms.Application.RunDialog(System.Windows.Forms.Form form) Line 1517 + 0x2b bytes C#
System.Windows.Forms.dll!System.Windows.Forms.Form.ShowDialog(System.Windows.Forms.IWin32Window owner) Line 6122 + 0x8 bytes C#
System.Windows.Forms.dll!System.Windows.Forms.Form.ShowDialog() Line 6022 + 0x7 bytes C#
Самая важная часть стека выделена красным. .NET использует концепцию пре трансляции
сообщений. Она очень похожа на механизм, который используется в MFC. Суть его простая
- в цикле сообщений перед их диспатчеризацией выполняется CWnd::WalkPreTranslateTree
активного окна, которая в свою очередь вызывает
CWnd::PreTranslateMessage
.
Сообщения можно съедать, изменять и вообще выполнять любую другую логику. Например
- управлять фокусом. Логично ведь - элемент управления сам не может знать как ему
перемещать фокус внутри контейнера (форма, диалог - не важно). И вот тут к нам пришла
засада - если цикл сообщений крутит не .NET, а MFC например, - ThreadContext::PreTranslateMessage
не будет вызван, соответственно все необходимые методы далее по стеку. Фокус не
перемещается.
И вот я задался вопросом - как же работают программы типа Visual Studio, Excel,
Word, Outlook, где .NET интегрирован и работает нормально - показывает формы, диалоги,
используются пользовательские элементы управления (наследники UserControl
).
Ответ я нашел в исходниках WinForms после длительного хардфакинга в рефлекторе и
отладчике. Все банально - WinForms использует скрытый интерфейс для взаимодействия
с хост-приложением. Этот интерфейс позволяет проталкивать необходимый вызов
ThreadContext::PreTranslateMessage
.
Посмотрим что происходит при вызове метода Show
формы:
> System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.ComponentManager.get() Line 2549 + 0x17 bytes C# System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.FormActivated(bool activate) Line 2975 + 0x7 bytes C# System.Windows.Forms.dll!System.Windows.Forms.Form.WmActivate(ref System.Windows.Forms.Message m = {System.Windows.Forms.Message}) Line 6842 + 0x2d bytes C# System.Windows.Forms.dll!System.Windows.Forms.Form.WndProc(ref System.Windows.Forms.Message m) Line 7433 C# System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.OnMessage(ref System.Windows.Forms.Message m) Line 14109 + 0xe bytes C# System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.WndProc(ref System.Windows.Forms.Message m) Line 14164 + 0xb bytes C# System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.DebuggableCallback(System.IntPtr hWnd, int msg = 0x00000006, System.IntPtr wparam, System.IntPtr lparam) Line 779 + 0xe bytes C# user32.dll!_InternalCallWinProc@20() + 0x23 bytes user32.dll!_UserCallWinProcCheckWow@32() + 0xb3 bytes user32.dll!_DispatchClientMessage@20() + 0x4b bytes user32.dll!___fnDWORD@4() + 0x24 bytes ntdll.dll!_KiUserCallbackDispatcher@12() + 0x2e bytes user32.dll!_NtUserShowWindow@8() + 0xc bytes [Managed to Native Transition] System.Windows.Forms.dll!System.Windows.Forms.Control.SetVisibleCore(bool value = true) Line 11762 + 0x46f bytes C# System.Windows.Forms.dll!System.Windows.Forms.Form.SetVisibleCore(bool value = true) Line 2684 C# System.Windows.Forms.dll!System.Windows.Forms.Control.Show() Line 12059 + 0x10 bytes C#
ComponentManager
- WTF? Код:
internal UnsafeNativeMethods.IMsoComponentManager ComponentManager { get { Debug.WriteLineIf(CompModSwitches.MSOComponentManager.TraceInfo, "Application.ComponentManager.Get:"); if (componentManager == null) { // The CLR is a good COM citizen and will pump messages when things are waiting. // This is nice; it keeps the world responsive. But, it is also very hard for // us because most of the code below causes waits, and the likelihood that // a message will come in and need a component manager is very high. Recursing // here is very very bad, and will almost certainly lead to application failure // later on as we come out of the recursion. So, we guard it here and return // null. EVERYONE who accesses the component manager must handle a NULL return! // if (fetchingComponentManager) { return null; } fetchingComponentManager = true; try { UnsafeNativeMethods.IMsoComponentManager msocm = null; Application.OleRequired(); // Attempt to obtain the Host Application MSOComponentManager // IntPtr msgFilterPtr = (IntPtr)0; if (NativeMethods.Succeeded(UnsafeNativeMethods.CoRegisterMessageFilter(NativeMethods.NullHandleRef, ref msgFilterPtr)) && msgFilterPtr != (IntPtr)0) { IntPtr dummy = (IntPtr)0; UnsafeNativeMethods.CoRegisterMessageFilter(new HandleRef(null, msgFilterPtr), ref dummy); object msgFilterObj = Marshal.GetObjectForIUnknown(msgFilterPtr); Marshal.Release(msgFilterPtr); UnsafeNativeMethods.IOleServiceProvider sp = msgFilterObj as UnsafeNativeMethods.IOleServiceProvider; if (sp != null) { try { IntPtr retval = IntPtr.Zero; // PERF ALERT ([....]): Using typeof() of COM object spins up COM at JIT time. // Guid compModGuid = typeof(UnsafeNativeMethods.SMsoComponentManager).GUID; // Guid compModGuid = new Guid("000C060B-0000-0000-C000-000000000046"); Guid iid = new Guid("{000C0601-0000-0000-C000-000000000046}"); int hr = sp.QueryService( ref compModGuid, ref iid, out retval); if (NativeMethods.Succeeded(hr) && retval != IntPtr.Zero) { // Now query for hte message filter. IntPtr pmsocm; try { Guid IID_IMsoComponentManager = typeof(UnsafeNativeMethods.IMsoComponentManager).GUID; hr = Marshal.QueryInterface(retval, ref IID_IMsoComponentManager, out pmsocm); } finally { Marshal.Release(retval); } if (NativeMethods.Succeeded(hr) && pmsocm != IntPtr.Zero) { // Ok, we have a native component manager. Hand this over to // our broker object to get a proxy we can use try { msocm = ComponentManagerBroker.GetComponentManager(pmsocm); } finally { Marshal.Release(pmsocm); } } if (msocm != null) { // If the resulting service is the same pUnk as the // message filter (a common implementation technique), // then we want to null msgFilterObj at this point so // we don't call RelaseComObject on it below. That would // also release the RCW for the component manager pointer. if (msgFilterPtr == retval) { msgFilterObj = null; } externalComponentManager = true; Debug.WriteLineIf(CompModSwitches.MSOComponentManager.TraceInfo, "Using MSO Component manager"); // Now attach app domain unload events so we can // detect when we need to revoke our component // AppDomain.CurrentDomain.DomainUnload += new EventHandler(OnDomainUnload); AppDomain.CurrentDomain.ProcessExit += new EventHandler(OnDomainUnload); } } } catch { } } if (msgFilterObj != null && Marshal.IsComObject(msgFilterObj)) { Marshal.ReleaseComObject(msgFilterObj); } } // Otherwise, we implement component manager ourselves // if (msocm == null) { msocm = new ComponentManager(); externalComponentManager = false; // We must also store this back into the message filter for others // to use. // // Debug.WriteLineIf(CompModSwitches.MSOComponentManager.TraceInfo, "Using our own component manager"); } if (msocm != null && componentID == INVALID_ID) { // Finally, if we got a compnent manager, register ourselves with it. // Debug.WriteLineIf(CompModSwitches.MSOComponentManager.TraceInfo, "Registering MSO component with the component manager"); NativeMethods.MSOCRINFOSTRUCT info = new NativeMethods.MSOCRINFOSTRUCT(); info.cbSize = Marshal.SizeOf(typeof(NativeMethods.MSOCRINFOSTRUCT)); info.uIdleTimeInterval = 0; info.grfcrf = NativeMethods.MSOCM.msocrfPreTranslateAll | NativeMethods.MSOCM.msocrfNeedIdleTime; info.grfcadvf = NativeMethods.MSOCM.msocadvfModal; IntPtr localComponentID; bool result = msocm.FRegisterComponent(this, info, out localComponentID); componentID = unchecked((int)(long)localComponentID); Debug.Assert(componentID != INVALID_ID, "Our ID sentinel was returned as a valid ID"); if (result && !(msocm is ComponentManager)) { messageLoopCount++; } Debug.Assert(result, "Failed to register WindowsForms with the ComponentManager -- DoEvents and modal dialogs will be broken. size: " + info.cbSize); Debug.WriteLineIf(CompModSwitches.MSOComponentManager.TraceInfo, "ComponentManager.FRegisterComponent returned " + result.ToString()); Debug.WriteLineIf(CompModSwitches.MSOComponentManager.TraceInfo, "ComponentManager.FRegisterComponent assigned a componentID == [0x" + Convert.ToString(componentID, 16) + "]"); componentManager = msocm; } } finally { fetchingComponentManager = false; } } return componentManager; } }
Нихуя себе! Сказал я себе. Этот метод выполняет следующие действия:
- Дважды вызывает
CoRegisterMessageFilter
чтобы получить реализациюIMessageFilter
для текущего потока; - Запрашивает у полученного объекта интерфейс
IOleServiceProvider
, который на самом деле -IServiceProvider
, объявление находится в файлеServProv.h
; - Вызывает метод
QueryService
для получения реализации необходимой службы -IMsoComponentManager
.static const GUID SERVICEID_MSO_COMPONENT_MANAGER = { 0x000C060B, 0x0000, 0x0000, { 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 } };
- Если действия выше неудачны - используется своя реализация
IMsoComponentManager
; - Далее
ThreadContext
регистрирует себя в менеджере с помощью методаFRegisterComponent
(ThreadContext
поддерживает интерфейсIMsoComponent
).
Задача стала ясна - реализовать COM-объект с необходимыми интерфейсами, зарегистрировать
его вызовом CoRegisterMessageFilter
и проталкивать сообщения из
CWinThread::PreTranslateMessage
во все активные IMsoComponent
вызывая IMsoComponent::FPreTranslateMessage
.
На самом деле все чуть сложней - активный компонент максимум один, но зато есть
еще так называемый tracking компонент. Например при открытии конекстного меню
ThreadContext
становится tracking, а при закрытии возвращается в обычное
состояние. При этом он не обязательно будет активным.
Погуглив на тему IMsoComponentManager
и IMsoComponent
,
я не нашел никакой полезной информации. Ни tlb, ни idl. С какой-то попытки наткнулся
на файл msoci.h
в неком SVN репозитарии и выяснил, что он является
частью Visual Studio 2010 SDK и лежит в папке C:\Program Files\Microsoft Visual
Studio 2010 SDK\VisualStudioIntegration\Common\Inc\office10
. Очень порадовала
документация к методам. Ведь она там есть! Причем детальное описание.
Собственно, реализация IMsoComponentManager
:
#pragma once #include "objidl.h" #include <ServProv.h> #include <msoci.h> #include <unordered_map> class CCOMMessageFilter : public IMessageFilter, public IServiceProvider, public IMsoComponentManager { public: CCOMMessageFilter(); virtual ~CCOMMessageFilter(); public: BOOL PreTranslateMessage(MSG *pMsg); public: // IUnknown HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID *ppvObj); ULONG STDMETHODCALLTYPE AddRef(); ULONG STDMETHODCALLTYPE Release(); private: // IMessageFilter DWORD STDMETHODCALLTYPE HandleInComingCall(DWORD dwCallType, HTASK htaskCaller, DWORD dwTickCount, LPINTERFACEINFO lpInterfaceInfo); DWORD STDMETHODCALLTYPE RetryRejectedCall(HTASK htaskCallee, DWORD dwTickCount, DWORD dwRejectType); DWORD STDMETHODCALLTYPE MessagePending(HTASK htaskCallee, DWORD dwTickCount, DWORD dwPendingType); private: // IServiceProvider HRESULT STDMETHODCALLTYPE QueryService(REFGUID guidService, REFIID riid, LPVOID *ppvObj); private: // IMsoComponentManager BOOL STDMETHODCALLTYPE FDebugMessage(HMSOINST hinst, UINT message, WPARAM wParam, LPARAM lParam); BOOL STDMETHODCALLTYPE FRegisterComponent(IMsoComponent *piComponent, const MSOCRINFO *pcrinfo, DWORD_PTR *pdwComponentID); BOOL STDMETHODCALLTYPE FRevokeComponent(DWORD_PTR dwComponentID); BOOL STDMETHODCALLTYPE FUpdateComponentRegistration(DWORD_PTR dwComponentID, const MSOCRINFO *pcrinfo); BOOL STDMETHODCALLTYPE FOnComponentActivate(DWORD_PTR dwComponentID); BOOL STDMETHODCALLTYPE FSetTrackingComponent(DWORD dwComponentID, ULONG grftrack); void STDMETHODCALLTYPE OnComponentEnterState(DWORD_PTR dwComponentID, ULONG uStateID, ULONG uContext, ULONG cpicmExclude, IMsoComponentManager **rgpicmExclude, DWORD dwReserved); BOOL STDMETHODCALLTYPE FOnComponentExitState(DWORD_PTR dwComponentID, ULONG uStateID, ULONG uContext, ULONG cpicmExclude, IMsoComponentManager **rgpicmExclude); BOOL STDMETHODCALLTYPE FInState(DWORD uStateID, LPVOID pvoid); BOOL STDMETHODCALLTYPE FContinueIdle(); BOOL STDMETHODCALLTYPE FPushMessageLoop(DWORD_PTR dwComponentID, ULONG uReason, void *pvLoopData); BOOL STDMETHODCALLTYPE FCreateSubComponentManager(IUnknown *pUnkOuter, IUnknown *pUnkServProv, REFIID riid, LPVOID *ppvObj); BOOL STDMETHODCALLTYPE FGetParentComponentManager(IMsoComponentManager **ppicm); BOOL STDMETHODCALLTYPE FGetActiveComponent(DWORD dwgac, IMsoComponent **ppic, MSOCRINFO *pcrinfo, DWORD dwReserved); private: ULONG _refCount; typedef CComPtr<IMsoComponent> IMsoComponentPtr; struct MsoComponentRegistration { IMsoComponentPtr ComponentPtr; MSOCRINFO Info; }; typedef std::tr1::unordered_map<DWORD_PTR, MsoComponentRegistration> component_registrations_t; typedef std::tr1::unordered_map<DWORD_PTR, IMsoComponentPtr> tracking_components_t; DWORD_PTR _nextComponentId; DWORD_PTR _activeComponentId; IMsoComponentPtr _activeComponentPtr; component_registrations_t _components; tracking_components_t _tracking; bool _trackingIteration; tracking_components_t _track; tracking_components_t _untrack; };
#include "stdafx.h" #include "COMMessageFilter.h" #define INIT_MSO_GUIDS #include <msoguids.h> CCOMMessageFilter::CCOMMessageFilter() : _refCount(0), _nextComponentId(0), _activeComponentId(-1), _trackingIteration(false) { } /*virtual*/ CCOMMessageFilter::~CCOMMessageFilter() { } BOOL CCOMMessageFilter::PreTranslateMessage(MSG *pMsg) { BOOL bRet = FALSE; if (!_tracking.empty()) { // Tracking ones first _trackingIteration = true; for (tracking_components_t::const_reverse_iterator pos = _tracking.rbegin(); pos != _tracking.rend(); ++pos) { bRet = bRet || pos->second->FPreTranslateMessage(pMsg); } _trackingIteration = false; // Insert tracked components _tracking.insert(_track.begin(), _track.end()); _track.clear(); // Delete untracked components for (tracking_components_t::const_iterator pos = _untrack.begin(); pos != _untrack.end(); ++pos) { _tracking.erase(pos->first); } _untrack.clear(); if (bRet) return(bRet); } // Active one if (_activeComponentPtr) bRet = _activeComponentPtr->FPreTranslateMessage(pMsg); return(bRet); } HRESULT STDMETHODCALLTYPE CCOMMessageFilter::QueryInterface(REFIID riid, LPVOID *ppvObj) { if (!ppvObj) return(E_POINTER); if (riid == IID_IUnknown) { *ppvObj = static_cast<IUnknown *>(static_cast<IMessageFilter *>(this)); } else if (riid == IID_IMessageFilter) { *ppvObj = static_cast<IMessageFilter *>(this); } else if (riid == IID_IServiceProvider) { *ppvObj = static_cast<IServiceProvider *>(this); } else if (riid == IID_IMsoComponentManager) { *ppvObj = static_cast<IMsoComponentManager *>(this); } else { *ppvObj = NULL; return(E_NOINTERFACE); } AddRef(); return(S_OK); } ULONG STDMETHODCALLTYPE CCOMMessageFilter::AddRef() { return(InterlockedIncrement(&_refCount)); } ULONG STDMETHODCALLTYPE CCOMMessageFilter::Release() { const ULONG count = InterlockedDecrement(&_refCount); if (count == 0) delete this; return(count); } // IMessageFilter DWORD STDMETHODCALLTYPE CCOMMessageFilter::HandleInComingCall(DWORD dwCallType, HTASK htaskCaller, DWORD dwTickCount, LPINTERFACEINFO lpInterfaceInfo) { return(SERVERCALL_RETRYLATER); } DWORD STDMETHODCALLTYPE CCOMMessageFilter::RetryRejectedCall(HTASK htaskCallee, DWORD dwTickCount, DWORD dwRejectType) { return(100); } DWORD STDMETHODCALLTYPE CCOMMessageFilter::MessagePending(HTASK htaskCallee, DWORD dwTickCount, DWORD dwPendingType) { return(PENDINGMSG_WAITNOPROCESS); } // IOleServiceProvider HRESULT STDMETHODCALLTYPE CCOMMessageFilter::QueryService(REFGUID guidService, REFIID riid, LPVOID *ppvObj) { TRACE(_T("CCOMMessageFilter::QueryService\n")); if (!ppvObj) return(E_POINTER); *ppvObj = NULL; static const GUID SERVICEID_MSO_COMPONENT_MANAGER = { 0x000C060B, 0x0000, 0x0000, { 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 } }; if (guidService == SERVICEID_MSO_COMPONENT_MANAGER) { return(QueryInterface(riid, ppvObj)); } return(E_NOINTERFACE); } BOOL STDMETHODCALLTYPE CCOMMessageFilter::FDebugMessage(HMSOINST hinst, UINT message, WPARAM wParam, LPARAM lParam) { TRACE(_T("CCOMMessageFilter::FDebugMessage\n")); return(TRUE); } BOOL STDMETHODCALLTYPE CCOMMessageFilter::FRegisterComponent(IMsoComponent *piComponent, const MSOCRINFO *pcrinfo, DWORD_PTR *pdwComponentID) { TRACE(_T("CCOMMessageFilter::FRegisterComponent\n")); if (!piComponent) return(E_POINTER); if (!pcrinfo) return(E_POINTER); if (!pdwComponentID) return(E_POINTER); *pdwComponentID = _nextComponentId++; const MsoComponentRegistration reg = { piComponent, *pcrinfo }; _components[*pdwComponentID] = reg; return(TRUE); } BOOL STDMETHODCALLTYPE CCOMMessageFilter::FRevokeComponent(DWORD_PTR dwComponentID) { TRACE(_T("CCOMMessageFilter::FRevokeComponent\n")); _components.erase(dwComponentID); if (_trackingIteration) { const tracking_components_t::const_iterator pos = _tracking.find(dwComponentID); if (pos != _tracking.end()) _untrack.insert(*pos); } else _tracking.erase(dwComponentID); if (dwComponentID == _activeComponentId) { _activeComponentId = -1; _activeComponentPtr.Release(); } return(TRUE); } BOOL STDMETHODCALLTYPE CCOMMessageFilter::FUpdateComponentRegistration(DWORD_PTR dwComponentID, const MSOCRINFO *pcrinfo) { TRACE(_T("CCOMMessageFilter::FUpdateComponentRegistration\n")); if (!pcrinfo) return(E_POINTER); const component_registrations_t::iterator pos = _components.find(dwComponentID); if (pos != _components.end()) { pos->second.Info = *pcrinfo; return(TRUE); } return(FALSE); } BOOL STDMETHODCALLTYPE CCOMMessageFilter::FOnComponentActivate(DWORD_PTR dwComponentID) { TRACE(_T("CCOMMessageFilter::FOnComponentActivate\n")); const component_registrations_t::iterator pos = _components.find(dwComponentID); if (pos != _components.end()) { _activeComponentId = pos->first; _activeComponentPtr = pos->second.ComponentPtr; return(TRUE); } return(FALSE); } BOOL STDMETHODCALLTYPE CCOMMessageFilter::FSetTrackingComponent(DWORD dwComponentID, ULONG grftrack) { TRACE(_T("CCOMMessageFilter::FSetTrackingComponent\n")); const BOOL bTrack = grftrack; if (!bTrack) { if (_trackingIteration) { const tracking_components_t::const_iterator pos = _tracking.find(dwComponentID); if (pos != _tracking.end()) _untrack.insert(*pos); else _track.erase(dwComponentID); } else _tracking.erase(dwComponentID); return(TRUE); } const component_registrations_t::iterator pos = _components.find(dwComponentID); if (pos != _components.end()) { if (_trackingIteration) _track[dwComponentID] = pos->second.ComponentPtr; else _tracking[dwComponentID] = pos->second.ComponentPtr; return(TRUE); } return(FALSE); } void STDMETHODCALLTYPE CCOMMessageFilter::OnComponentEnterState(DWORD_PTR dwComponentID, ULONG uStateID, ULONG uContext, ULONG cpicmExclude, IMsoComponentManager **rgpicmExclude, DWORD dwReserved) { TRACE(_T("CCOMMessageFilter::OnComponentEnterState\n")); } BOOL STDMETHODCALLTYPE CCOMMessageFilter::FOnComponentExitState(DWORD_PTR dwComponentID, ULONG uStateID, ULONG uContext, ULONG cpicmExclude, IMsoComponentManager **rgpicmExclude) { TRACE(_T("CCOMMessageFilter::FOnComponentExitState\n")); return(TRUE); } BOOL STDMETHODCALLTYPE CCOMMessageFilter::FInState(DWORD uStateID, LPVOID pvoid) { TRACE(_T("CCOMMessageFilter::FInState\n")); return(TRUE); } BOOL STDMETHODCALLTYPE CCOMMessageFilter::FContinueIdle() { TRACE(_T("CCOMMessageFilter::FContinueIdle\n")); return(TRUE); } BOOL STDMETHODCALLTYPE CCOMMessageFilter::FPushMessageLoop(DWORD_PTR dwComponentID, ULONG uReason, void *pvLoopData) { TRACE(_T("CCOMMessageFilter::FPushMessageLoop\n")); return(FALSE); } BOOL STDMETHODCALLTYPE CCOMMessageFilter::FCreateSubComponentManager(IUnknown *pUnkOuter, IUnknown *pUnkServProv, REFIID riid, LPVOID *ppvObj) { TRACE(_T("CCOMMessageFilter::FCreateSubComponentManager\n")); if (!ppvObj) return(E_POINTER); *ppvObj = NULL; return(FALSE); } BOOL STDMETHODCALLTYPE CCOMMessageFilter::FGetParentComponentManager(IMsoComponentManager **ppicm) { TRACE(_T("CCOMMessageFilter::FGetParentComponentManager\n")); if (!ppicm) return(E_POINTER); *ppicm = NULL; return(FALSE); } BOOL STDMETHODCALLTYPE CCOMMessageFilter::FGetActiveComponent(DWORD dwgac, IMsoComponent **ppic, MSOCRINFO *pcrinfo, DWORD dwReserved) { TRACE(_T("CCOMMessageFilter::FGetActiveComponent\n")); if (ppic) *ppic = NULL; DWORD_PTR componentId = -1; if (dwgac == msogacTracking || dwgac == msogacTrackingOrActive) { if (!_tracking.empty()) { componentId = _tracking.rbegin()->first; } } if (componentId == -1 && (dwgac == msogacActive || dwgac == msogacTrackingOrActive)) { componentId = _activeComponentId; } const component_registrations_t::iterator pos = _components.find(componentId); if (pos != _components.end()) { MsoComponentRegistration ® = pos->second; if (pcrinfo) *pcrinfo = reg.Info; if (ppic) reg.ComponentPtr.CopyTo(ppic); return(TRUE); } return(FALSE); }
Использование:
/*virtual*/ BOOL CWinFormsMFCHostApp::PreTranslateMessage(MSG *pMsg) { const BOOL bRet = m_pComponentManager->PreTranslateMessage(pMsg); if (bRet) return(bRet); return(__super::PreTranslateMessage(pMsg)); }
Инициализация:
/*virtual*/ BOOL CWinFormsMFCHostApp::InitInstance() { INITCOMMONCONTROLSEX InitCtrls; InitCtrls.dwSize = sizeof(InitCtrls); InitCtrls.dwICC = ICC_WIN95_CLASSES; InitCommonControlsEx(&InitCtrls); __super::InitInstance(); if (!AfxOleInit()) { AfxMessageBox(IDP_OLE_INIT_FAILED); return(FALSE); } AfxEnableControlContainer(); m_pComponentManager = new CCOMMessageFilter(); m_pComponentManager->AddRef(); CoRegisterMessageFilter(m_pComponentManager, NULL);
В результате получаем рабочую во всех смыслах .NET форму.
Я, кстати, удивился, что контекстное меню продолжает нормально работать при отсутствии
управляемого цикла сообщений. Эти гады, оказалось, проверяют на наличие цикла сообщений
(а эта проверка, кстати, лезет в ComponentManager
), а если его нет
- создают хук, отправляя все пойманные сообщения в ThreadContext::PreTranslateMessage
!
Microsoft not only eats it's own dogfood, but sometimes devours it. Not even giving a chance to taste...