Hungry Mind , Blog about everything in IT - C#, Java, C++, .NET, Windows, WinAPI, ...

C++ and .NET interop

Речь пойдет о механизме и нюансах исполнения .NET кода из C++ и, соответственно, тонкостях взаимодействия неуправляемого кода и управляемого (С++ с CLR).

Сценарий таков: С++ приложение инициализирует CLR engine в своем процессе, загружает .NET сборки и использует .NET объекты. Обращение к методам и свойствам объектов происходит через COM интерфейсы, в нашем варианте другого механизма нет.

Конкретный случай - нужно показать форму .NET и взаимодействовать с ней (вызывать методы и получать уведомления).

Рассмотрим диаграмму классов .NET библиотеки:
NETLibrary class diagram

Главное действующее лицо - NETForm, остальные классы нужны для взаимодействия через COM (их можно вообще вынести в отдельную сборку, а NETForm создавать так, будто это просто .NET форма).

C++ будет использовать интерфейс INETFormController для взаимодействия с классом NETFormController, который в свою очередь будет адаптером для NETForm.

using System;
using System.Runtime.InteropServices;
 
namespace NETLibrary
{
   [ComVisible(true)]
   [Guid("AB185BF6-B576-46F8-880B-16484A2893FD")]
   [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
   public interface INETFormController
   {
      void AddCallback(INETFormControllerCallback callback);
      void Show();
      void Close();
      void RequestNotification();
   }
}

INETFormControllerCallback - это COM source интерфейс:

using System;
using System.Runtime.InteropServices;
 
namespace NETLibrary
{
   [ComVisible(true)]
   [Guid("466444F6-2206-4E61-BE39-D3D781E8D8DB")]
   [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
   public interface INETFormControllerCallback
   {
      void Shown();
      void ButtonClicked();
      void Notification();
   }
}

Использование INETFormControllerCallback в качестве source интерфейса позволяет задействовать унифицированный механизм подписки и отписки от уведомлений COM объекта (подробнее - Using IConnectionPointContainer). Кстати, RCW поддерживает этот интерфейс, если к классу прикреплен атрибут ComSourceInterfaces.

На форме есть поле ввода и кнопка, для начала попытаемся показать форму и получить уведомление о том, что кнопку нажали:

using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
 
namespace NETLibrary
{
   [ComVisible(false)]
   [ClassInterface(ClassInterfaceType.None)]
   [ComSourceInterfaces(typeof(INETFormControllerCallback))]
   class NETFormController : INETFormController
   {
      public NETFormController() {
         InitializeForm();
      }
 
      public void AddCallback(INETFormControllerCallback callback) {
         Shown += callback.Shown;
         ButtonClicked += callback.ButtonClicked;
         Notification += callback.Notification;
      }
 
      public void Show() {
         _netForm.Show();
      }
 
      public void Close() {
         _netForm.Close();
      }
 
      public void RequestNotification() {
         FireNotification(this, EventArgs.Empty);
      }
 
      public event Action Shown;
      public event Action ButtonClicked;
      public event Action Notification;
 
      private void InitializeForm() {
         _netForm = new NETForm();
         _netForm.Shown += FireShown;
         _netForm.ButtonClicked += FireButtonClicked;
      }
 
      private void FireShown(Object sender, EventArgs args) {
         if (Shown != null) {
            Shown();
         }
      }
 
      private void FireButtonClicked(Object sender, EventArgs args) {
         if (ButtonClicked != null) {
            ButtonClicked();
         }
      }
 
      private void FireNotification(Object sender, EventArgs args) {
         if (Notification != null) {
            Notification();
         }
      }
 
      NETForm _netForm;
   }
}

Далее - C++ (код инициализации CLR я взял из (All-In-One Code Framework.zip\Visual Studio 2010\CppHostCLR):

#include "stdafx.h"
 
NETLibrary::INETFormControllerPtr g_pNETObj;
 
class ATL_NO_VTABLE CNETFormControllerCallback : public ATL::CComObjectRootEx<CComSingleThreadModel>,
                                                 public NETLibrary::INETFormControllerCallback
{
 
protected:
   DECLARE_PROTECT_FINAL_CONSTRUCT()
   BEGIN_COM_MAP(CNETFormControllerCallback)
      COM_INTERFACE_ENTRY(NETLibrary::INETFormControllerCallback)
   END_COM_MAP()
 
public:
   STDMETHOD(raw_Shown)() {
      OutputDebugString(_T("Shown\n"));
      return(S_OK);
   }
 
   STDMETHOD(raw_ButtonClicked)() {
      OutputDebugString(_T("ButtonClicked\n"));
      g_pNETObj->RequestNotification();
      return(S_OK);
   }
 
   STDMETHOD(raw_Notification)() {
      OutputDebugString(_T("Notification\n"));
      return(S_OK);
   }
 
};
 
int _tmain(int argc, _TCHAR* argv[]) {
   HRESULT hr;
 
   CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
 
   CComPtr<ICLRMetaHost> spMetaHost;
   CComPtr<ICLRRuntimeInfo> spRuntimeInfo;
   CComPtr<ICorRuntimeHost> spCorRuntimeHost;
   IUnknownPtr spAppDomainThunk;
   _AppDomainPtr spDefaultAppDomain;
 
   hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&spMetaHost)); if (FAILED(hr)) return(hr);
   hr = spMetaHost->GetRuntime(L"v4.0.30319", IID_PPV_ARGS(&spRuntimeInfo)); if (FAILED(hr)) return(hr);
   BOOL fLoadable;
   hr = spRuntimeInfo->IsLoadable(&fLoadable); if (FAILED(hr)) return(hr);
   hr = spRuntimeInfo->GetInterface(CLSID_CorRuntimeHost, IID_PPV_ARGS(&spCorRuntimeHost)); if (FAILED(hr)) return(hr);
   hr = spCorRuntimeHost->Start(); if (FAILED(hr)) return(hr);
   hr = spCorRuntimeHost->GetDefaultDomain(&spAppDomainThunk); if (FAILED(hr)) return(hr);
   hr = spAppDomainThunk->QueryInterface(IID_PPV_ARGS(&spDefaultAppDomain)); if (FAILED(hr)) return(hr);
   const _AssemblyPtr spAssembly = spDefaultAppDomain->Load_2(bstr_t(L"NETLibrary"));
   const variant_t vtObject = spAssembly->CreateInstance(bstr_t(L"NETLibrary.NETFormController"));
 
   g_pNETObj = vtObject;
 
   const NETLibrary::INETFormControllerCallbackPtr spCallback = new ATL::CComObjectNoLock<CNETFormControllerCallback>();
 
   g_pNETObj->AddCallback(spCallback);
   g_pNETObj->Show();
 
   MSG msg;
   while (GetMessage(&msg, NULL, 0, 0) > 0) {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }
 
   return msg.wParam;
}

AddCallback я добавил чтобы упростить код (правильно - использовать connection point).

Совсем забыл - в проекте NETLibrary настроен post-build event для создания библиотеки типов:

"c:\Program Files\Microsoft SDKs\Windows\v7.0A\bin\NETFX 4.0 Tools\TlbExp.exe" $(TargetPath) /out:$(TargetDir)\$(TargetName).tlb

C++ использует директиву #import для генерации кода:

#import "../Debug/NETLibrary.tlb" rename_namespace("NETLibrary")

Итог запуска приложения - форма на экране, в окне отладки строчка Shown, а каждое нажатие на кнопку добавляет строчку ButtonClicked. Есть только одно маленькое но - не работает фокус. Вообще. Подробнее можно прочитать в Windows Forms and Unmanaged Applications Overview: the form may behave unexpectedly. For example, when you press the TAB key, the focus does not change from one control to another control. When you press the ENTER key while a command button has focus, the button's Click event is not raised. You may also experience unexpected behavior for keystrokes or mouse activity.

Там же предлагают решения - 1) использовать метод ShowDialog и 2) показывать форму в отдельном потоке с запуском цикла сообщений WinForms. Модальный диалог нам не подходит, поэтому идем в сторону создания потока.

Вторая версия NETFormController:

using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
 
namespace NETLibrary
{
   [ComVisible(false)]
   [ClassInterface(ClassInterfaceType.None)]
   [ComSourceInterfaces(typeof(INETFormControllerCallback))]
   class NETFormController : INETFormController
   {
      public NETFormController() {
         _thread = new Thread(ThreadProc);
         _thread.SetApartmentState(ApartmentState.STA);
         _thread.Start();
         _threadInitialized.WaitOne();
      }
 
      public void AddCallback(INETFormControllerCallback callback) {
         Shown += callback.Shown;
         ButtonClicked += callback.ButtonClicked;
         Notification += callback.Notification;
      }
 
      public void Show() {
         _remoteSync.Invoke((MethodInvoker)delegate { _netForm.Show(); });
      }
 
      public void Close() {
         _remoteSync.Invoke((MethodInvoker)delegate { _netForm.Close(); });
      }
 
      public void RequestNotification() {
         _remoteSync.Invoke((MethodInvoker)delegate { FireNotification(this, EventArgs.Empty); });
      }
 
      public event Action Shown;
      public event Action ButtonClicked;
      public event Action Notification;
 
      private void InitializeForm() {
         _netForm = new NETForm();
         _netForm.Shown += FireShown;
         _netForm.ButtonClicked += FireButtonClicked;
      }
 
      private void ThreadProc() {
         _remoteSync = new Control();
         _remoteSync.CreateControl();
         InitializeForm();
         _threadInitialized.Set();
         Application.Run();
      }
 
      private void FireShown(Object sender, EventArgs args) {
         if (Shown != null) {
            Shown();
         }
      }
 
      private void FireButtonClicked(Object sender, EventArgs args) {
         if (ButtonClicked != null) {
            ButtonClicked();
         }
      }
 
      private void FireNotification(Object sender, EventArgs args) {
         if (Notification != null) {
            Notification();
         }
      }
 
      Thread _thread;
      AutoResetEvent _threadInitialized = new AutoResetEvent(false);
      Control _remoteSync;
      NETForm _netForm;
   }
}

Запуск приложения приводит в недоумение:

A first chance exception of type 'System.InvalidCastException' occurred in NETLibrary.dll

Additional information: Unable to cast COM object of type 'System.__ComObject' to interface type 'NETLibrary.INETFormControllerCallback'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{466444F6-2206-4E61-BE39-D3D781E8D8DB}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).

Происходит это вот здесь:

private void FireShown(Object sender, EventArgs args) {
   if (Shown != null) {
      Shown();
   }
}

CLR пытается нам сообщить, что переданный CNETFormControllerCallback не поддерживает интерфейс INETFormControllerCallback! Недавно поддерживал, а теперь вдруг перестал. Не имеет значения, что там вызов делегата - фактически выполняется следующий код:

callback.Shown();

Что же произошло? Тоесть WTF? Вернем прежнюю реализацию NETFormController и посмотрим как используется CNETFormControllerCallback. Для этого я добавил точки останова в CComObjectNoLock.

> CPPHost.exe!ATL::CComObjectNoLock<CNETFormControllerCallback>::QueryInterface(const _GUID & iid={...}, void * * 
  ppvObject=0x003ded84) Line 3057 C++
  clr.dll!SafeQueryInterfaceHelper() + 0x4f bytes
  clr.dll!SafeQueryInterface() + 0x45 bytes
  clr.dll!RCW::SafeQueryInterfaceRemoteAware() + 0x3d bytes
  clr.dll!RCW::GetComIPForMethodTableFromCache() + 0x6b bytes
  clr.dll!RCW::GetComIPFromRCW() + 0x31 bytes
  clr.dll!ComObject::GetComIPFromRCW() + 0x40 bytes
  clr.dll!ComObject::GetComIPFromRCWEx() + 0x64 bytes
  clr.dll!StubHelpers::ProcessByrefValidationList() + 0x231b2 bytes
  clr.dll!StubHelpers::GetCOMIPFromRCW() + 0x7c bytes
  012306b4()
  System.Windows.Forms.ni.dll!5d67efc3()

Здесь нас просят {466444F6-2206-4E61-BE39-D3D781E8D8DB}, что есть INETFormControllerCallback. Вывод первый - вызов COM метода намного сложнее, чем кажется на первый взгляд. RCW выполняет множество операций прежде, чем произойдет фактический переход по адресу обработчика из таблицы виртуальных функций. Вывод второй - вызов происходит динамически. Нажатие на кнопку не приводит к срабатыванию точки останова! Вывод третий - RCW использует кеш для сохранения результатов своей деятельности.

Посмотрим, что происходит в случае показа формы из другого потока. По логике - обратный вызов должен происходить в потоке формы, т.е. С++ обработчик должен исполняться в нем же.

> ole32.dll!_CoUnmarshalInterface@12()
  clr.dll!IUnkEntry::UnmarshalIUnknownForCurrContext() + 0x153 bytes
  clr.dll!IUnkEntry::GetIUnknownForCurrContext() + 0x1d6a22 bytes
  clr.dll!RCW::SafeQueryInterfaceRemoteAware() + 0x16 bytes
  clr.dll!RCW::GetComIPForMethodTableFromCache() + 0x6b bytes
  clr.dll!RCW::GetComIPFromRCW() + 0x31 bytes
  clr.dll!ComObject::GetComIPFromRCW() + 0x40 bytes
  clr.dll!ComObject::GetComIPFromRCWEx() + 0x64 bytes
  clr.dll!StubHelpers::ProcessByrefValidationList() + 0x231b2 bytes
  clr.dll!StubHelpers::GetCOMIPFromRCW() + 0x7c bytes
  013807c4()
  user32.dll!___fnDWORD@4() + 0x24 bytes
  System.Windows.Forms.ni.dll!5cd54ae8()

Как видно - вызов уходит в ole32.dll. А она в свою очередь захочет библиотеку типа для маршаллинга вызовов(?). А ни библиотека, ни вообще любая информация о интерфейсах в реестре не числится! Далее:

  user32.dll!_PeekMessageW@20()  + 0xf4 bytes
  ole32.dll!CCliModalLoop::MyPeekMessage()  + 0x30 bytes
  ole32.dll!CCliModalLoop::PeekRPCAndDDEMessage()  + 0x30 bytes
  ole32.dll!CCliModalLoop::BlockFn()  - 0x5cf0 bytes
  ole32.dll!ModalLoop()  + 0x52 bytes
  ole32.dll!SwitchSTA()  + 0x21 bytes
  ole32.dll!CRpcChannelBuffer::SwitchAptAndDispatchCall()  - 0x1837 bytes
  ole32.dll!CRpcChannelBuffer::SendReceive2()  + 0xa6 bytes
  ole32.dll!CCliModalLoop::SendReceive()  + 0x1e bytes
  ole32.dll!CAptRpcChnl::SendReceive()  + 0x72 bytes
  ole32.dll!CCtxComChnl::SendReceive()  + 0x47 bytes
  ole32.dll!NdrExtpProxySendReceive()  + 0x43 bytes
  rpcrt4.dll!NdrFixedArrayBufferSize()  + 0x76a bytes
  ole32.dll!_ObjectStublessClient@8()  + 0x7a bytes
  ole32.dll!_ObjectStubless@0()  + 0xf bytes
  ole32.dll!CStdMarshal::RemoteAddRef()  + 0xb9 bytes
  ole32.dll!CStdMarshal::MarshalClientIPID()  + 0x4e bytes
  ole32.dll!CStdMarshal::MarshalIPID()  - 0xcf27 bytes
  ole32.dll!CStdMarshal::MarshalObjRef()  + 0x92 bytes
  ole32.dll!CStdMarshal::MarshalInterface()  + 0x76 bytes
  ole32.dll!CDestObjectWrapper::MarshalInterface()  + 0x19d1 bytes
  ole32.dll!_CoMarshalInterface@24()  + 0x83 bytes

Верите или нет - по дороге выполняется PostMessage в основной поток с целью выполнить вызов именно там! Доказательство - полученное сообщение WM_USER для скрытого окна синхронизации вызовов:

> kernel32.dll!_FindActCtxSectionGuid@20()  + 0x56 bytes 
  ole32.dll!CRIFTable::GetPSClsidHelper()  + 0x2ed4 bytes 
  ole32.dll!CRIFTable::GetPSClsid()  + 0x34 bytes 
  ole32.dll!CStdMarshal::GetPSFactory()  + 0x3d bytes 
  ole32.dll!CStdMarshal::CreateStub()  + 0x5401 bytes 
  ole32.dll!CStdMarshal::ConnectSrvIPIDEntry()  + 0x26 bytes 
  ole32.dll!CStdMarshal::MarshalServerIPID()  + 0x74 bytes 
  ole32.dll!CRemoteUnknown::RemQueryInterface()  + 0x128 bytes 
  rpcrt4.dll!_Invoke@12()  + 0x2a bytes 
  rpcrt4.dll!_NdrStubCall2@16()  + 0x22f bytes 
  ole32.dll!_CStdStubBuffer_Invoke@12()  + 0x70 bytes 
  ole32.dll!SyncStubInvoke()  + 0x34 bytes 
  ole32.dll!StubInvoke()  + 0x7b bytes 
  ole32.dll!CCtxComChnl::ContextInvoke()  + 0xe6 bytes 
  ole32.dll!MTAInvoke()  + 0x1a bytes 
  ole32.dll!STAInvoke()  + 0x4a bytes 
  ole32.dll!AppInvoke()  + 0x92 bytes 
  ole32.dll!ComInvokeWithLockAndIPID()  + 0x27c bytes 
  ole32.dll!ComInvoke()  + 0x71 bytes 
  ole32.dll!ThreadDispatch()  + 0x1a bytes 
  ole32.dll!ThreadWndProc()  + 0xa0 bytes 
  user32.dll!_InternalCallWinProc@20()  + 0x23 bytes 
  user32.dll!_UserCallWinProcCheckWow@32()  + 0xb3 bytes 
  user32.dll!_DispatchMessageWorker@8()  + 0xe6 bytes 
  user32.dll!_DispatchMessageW@4()  + 0xf bytes 
  CPPHost.exe!wmain(int argc=0x00000001, wchar_t * * argv=0x015e1480)  Line 58 + 0xf bytes C++
  CPPHost.exe!__tmainCRTStartup()  Line 552 + 0x19 bytes C
  CPPHost.exe!wmainCRTStartup()  Line 371 C
  kernel32.dll!@BaseThreadInitThunk@12()  + 0x12 bytes 
  ntdll.dll!___RtlUserThreadStart@8()  + 0x27 bytes 
  ntdll.dll!__RtlUserThreadStart@8()  + 0x1b bytes 

Получается, что облом происходит на стороне С++ - вызов GetPSFactory завершается с ошибкой, stub не создается. Предварительно на стороне CLR вероятней всего происходит создание proxy, причем проходит успешно (не проверял - под вопросом).

Полагаю, CLR запоминает контекст каждого объекта (когда он приходит извне), а также тесно взаимодействует с ole32.dll если текущий контекст выполнения отличается от оригинального. Кстати, это касается лишь объектов, созданных не CLR, - при получении указателя на один из интерфейсов реализованных RCW, CLR запрашивает у него IManagedObject (кроме всего прочего) и находит оригинальный .NET объект! Я различными способами пытался получить RCW (System.__ComObject) внутри управляемого кода и у меня ничего не получилось. Также примечательно, что все RCW являются Free Threaded (сам RCW может использоваться разными потоками, а вот оборачиваемый им объект должен сам заботиться о синхронизации), поэтому надо быть осторожней с потоками.

Я нашел 3 способа решения этой проблемы (хотя идеально было бы разделить NETFormController на две части и заставить CLR связать одну со второй через COM - чтобы внутри самого CLR использовался OLE маршаллинг, но это что-то из области сферических коней в вакууме):

  1. Регистрация сборки утилитой regasm.exe
  2. Динамическая регистрация фабрики ProxyStub во время исполнения программы
  3. Маршаллинг обратных вызовов вручную

Первый способ - это тягомотина с инсталляцией приложения, версионностью, мусором в реестре и т.д. Второй - много геморроя, использование недокументированных функций и т.д. Третий - большое количество ручного кода и сложность отладки, но все же лучше, чем 1 и 2. Рассмотрим его более детально.

Необходимо пробрасывать обратные вызовы в поток C++ (и уже в его контексте выполнять). Единственно возможный вменяемый способ - через Control.Invoke. Хитрость состоит в том, что можно создать объект Control в потоке С++ и использовать его:

using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
 
namespace NETLibrary
{
   [ComVisible(false)]
   [ClassInterface(ClassInterfaceType.None)]
   [ComSourceInterfaces(typeof(INETFormControllerCallback))]
   class NETFormController : INETFormController
   {
      public NETFormController() {
         _localSync = new Control();
         _localSync.CreateControl();
 
         _thread = new Thread(ThreadProc);
         _thread.SetApartmentState(ApartmentState.STA);
         _thread.Start();
         _threadInitialized.WaitOne();
      }
 
      public void AddCallback(INETFormControllerCallback callback) {
         Shown += callback.Shown;
         ButtonClicked += callback.ButtonClicked;
         Notification += callback.Notification;
      }
 
      public void Show() {
         _remoteSync.Invoke((MethodInvoker)delegate { _netForm.Show(); });
      }
 
      public void Close() {
         _remoteSync.Invoke((MethodInvoker)delegate { _netForm.Close(); });
      }
 
      public void RequestNotification() {
         _remoteSync.Invoke((MethodInvoker)delegate { FireNotification(this, EventArgs.Empty); });
      }
 
      public event Action Shown;
      public event Action ButtonClicked;
      public event Action Notification;
 
      private void InitializeForm() {
         _netForm = new NETForm();
         _netForm.Shown += FireShown;
         _netForm.ButtonClicked += FireButtonClicked;
      }
 
      private void ThreadProc() {
         _remoteSync = new Control();
         _remoteSync.CreateControl();
         InitializeForm();
         _threadInitialized.Set();
         Application.Run();
      }
 
      private void FireShown(Object sender, EventArgs args) {
         if (Shown != null) {
            _localSync.Invoke(Shown);
         }
      }
 
      private void FireButtonClicked(Object sender, EventArgs args) {
         if (ButtonClicked != null) {
            _localSync.Invoke(ButtonClicked);
         }
      }
 
      private void FireNotification(Object sender, EventArgs args) {
         if (Notification != null) {
            _localSync.Invoke(Notification);
         }
      }
 
      Thread _thread;
      AutoResetEvent _threadInitialized = new AutoResetEvent(false);
      Control _localSync;
      Control _remoteSync;      
      NETForm _netForm;
   }
}

Теперь все якобы работает - вызовы пробрасываются в поток С++, в окне отладки должные сообщения о показе формы и нажатиях на кнопку. Но стоит усложнить взаимодействие и возникнет проблема - синхронные вызовы Control.Invoke не реентрабельны. Это означает, что вызор любого метода INETFormController из CNETFormControllerCallback приведет к блокировке приложения. Ровно как и попытка послать уведомление внутри синхронного вызова к INETFormController.

Можно посылать уведомления асинхронно - использовать Control.BeginInvoke, но это добавляет кода и усложняет механизм, если результат выполнения важен (например используется output параметр).

Следующий класс решает проблему реентрабельности:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
using Microsoft.Win32.SafeHandles;
 
namespace NETLibrary
{
   internal class UIExecutionContext : ISynchronizeInvoke
   {
      private readonly Thread _thread;
      private AutoResetEvent _contextInitialized;
      private AutoResetEvent _contextInitializationFailed;
      private Exception _contextException;
 
      private readonly ISynchronizeInvoke _hostInvoke;
      private ISynchronizeInvoke _contextInvoke;
 
      private readonly Stack<ContextCall> _hostInvokes = new Stack<ContextCall>(2);
      private readonly Stack<ContextCall> _contextInvokes = new Stack<ContextCall>(2);
 
      private readonly AutoResetEvent _performInvoke = new AutoResetEvent(false);
      private readonly AutoResetEvent _performCallback = new AutoResetEvent(false);
 
 
      public UIExecutionContext() {
         _hostInvoke = new SyncControl("Host");
 
         _thread = new Thread(ExecutionContextRoutine);
         _thread.SetApartmentState(ApartmentState.STA);
         _thread.Name = "UIExecutionContext";
         using (_contextInitialized = new AutoResetEvent(false))
         using (_contextInitializationFailed = new AutoResetEvent(false)) {
            _thread.Start();
            WaitHandle[] waitHandles = new WaitHandle[] { _contextInitializationFailed, _contextInitialized };
            if (WaitHandle.WaitAny(waitHandles) == 0) {
               throw new InvalidOperationException("Failed to create UI execution context.", _contextException);
            }
         }
      }
 
      private bool IsContext {
         get { return (Thread.CurrentThread.ManagedThreadId == _thread.ManagedThreadId); }
      }
 
      private ISynchronizeInvoke TargetInvoke {
         get { return (IsContext ? _hostInvoke : _contextInvoke); }
      }
 
      private AutoResetEvent PerformInvoke {
         get { return (IsContext ? _performInvoke : _performCallback); }
      }
 
      private AutoResetEvent PerformCallback {
         get { return (IsContext ? _performCallback : _performInvoke); }
      }
 
      private Stack<ContextCall> Callbacks {
         get { return (IsContext ? _contextInvokes : _hostInvokes); }
      }
 
      private Stack<ContextCall> Invokes {
         get { return (IsContext ? _hostInvokes : _contextInvokes); }
      }
 
      #region Implementation of ISynchronizeInvoke
 
      /// <summary>
      /// Asynchronously executes the delegate on the thread that created this Object.
      /// </summary>
      /// <returns>
      /// An <see cref="T:System.IAsyncResult"/> interface that represents the asynchronous operation started by calling this method.
      /// </returns>
      /// <param name="method">A <see cref="T:System.Delegate"/> to a method that takes parameters of the same number and type that are contained in <paramref name="args"/>. </param><param name="args">An array of type <see cref="T:System.Object"/> to pass as arguments to the given method. This can be null if no arguments are needed. </param>
      public IAsyncResult BeginInvoke(Delegate method, Object[] args) {
         return (TargetInvoke.BeginInvoke(method, args));
      }
 
      /// <summary>
      /// Waits until the process started by calling <see cref="M:System.ComponentModel.ISynchronizeInvoke.BeginInvoke(System.Delegate,System.Object[])"/> completes, and then returns the value generated by the process.
      /// </summary>
      /// <returns>
      /// An <see cref="T:System.Object"/> that represents the return value generated by the asynchronous operation.
      /// </returns>
      /// <param name="result">An <see cref="T:System.IAsyncResult"/> interface that represents the asynchronous operation started by calling <see cref="M:System.ComponentModel.ISynchronizeInvoke.BeginInvoke(System.Delegate,System.Object[])"/>. </param>
      public Object EndInvoke(IAsyncResult result) {
         return (TargetInvoke.EndInvoke(result));
      }
 
      /// <summary>
      /// Synchronously executes the delegate on the thread that created this Object and marshals the call to the creating thread.
      /// </summary>
      /// <returns>
      /// An <see cref="T:System.Object"/> that represents the return value from the delegate being invoked, or null if the delegate has no return value.
      /// </returns>
      /// <param name="method">A <see cref="T:System.Delegate"/> that contains a method to call, in the context of the thread for the control. </param><param name="args">An array of type <see cref="T:System.Object"/> that represents the arguments to pass to the given method. This can be null if no arguments are needed. </param>
      public Object Invoke(Delegate method, Object[] args) {
         IAsyncResult asyncResult = null;
         bool doBeginInvoke;
         ContextCall contextCall;
 
         lock (_thread) {
            doBeginInvoke = Callbacks.Count == 0;
 
            if (doBeginInvoke)
               asyncResult = TargetInvoke.BeginInvoke(method, args);
 
            contextCall = new ContextCall(method, args, asyncResult);
            Invokes.Push(contextCall);
            if (!doBeginInvoke) {
               PerformInvoke.Set();
            }
         }
 
         ContextCall callback;
         
         var waitHandles = new[] { PerformCallback, contextCall.AsyncWaitHandle };
 
         for (int wh = WaitHandle.WaitAny(waitHandles); wh == 0; wh = WaitHandle.WaitAny(waitHandles)) {
            // No need to lock here - another thread is doing the same loop
            callback = Callbacks.Peek();
            callback.PerformNoThrow();
            Callbacks.Pop();
         }
         
         lock (_thread) {
            if (doBeginInvoke) {
               Invokes.Pop();
            }
            // Got one more callback            
            callback = PerformCallback.WaitOne(0) ? Callbacks.Pop() : null;
         }
 
         if (callback != null) {
            callback.PerformNoThrow();            
         }
 
         return (doBeginInvoke ? TargetInvoke.EndInvoke(contextCall.AsyncResult) : contextCall.Result);
      }
 
      /// <summary>
      /// Gets a value indicating whether the caller must call <see cref="M:System.ComponentModel.ISynchronizeInvoke.Invoke(System.Delegate,System.Object[])"/> when calling an Object that implements this interface.
      /// </summary>
      /// <returns>
      /// true if the caller must call <see cref="M:System.ComponentModel.ISynchronizeInvoke.Invoke(System.Delegate,System.Object[])"/>; otherwise, false.
      /// </returns>
      public bool InvokeRequired {
         get { return (TargetInvoke.InvokeRequired); }
      }
 
      #endregion
 
      private void ExecutionContextRoutine() {
         try {
            _contextInvoke = new SyncControl("Context");
            _contextInitialized.Set();
            Application.Run();
         }
         catch (Exception ex) {
            _contextException = ex;
         }
      }
 
      #region Nested type: ContextCall
 
      private class ContextCall : IAsyncResult
      {
         public readonly IAsyncResult AsyncResult;
         private readonly Object[] _args;
         private readonly Delegate _method;
         private AutoResetEvent _asyncWaitHandle;
         private bool _isCompleted;
         private Object _result;
         private Exception _exception;
 
         public ContextCall(Delegate method, Object[] args, IAsyncResult asyncResult) {
            _method = method;
            _args = args;
            AsyncResult = asyncResult;
         }
 
         public Object Result {
            get {
               if (_exception != null)
                  throw _exception;
               return (_result);
            }
         }
 
         #region Implementation of IAsyncResult
 
         /// <summary>
         /// Gets a value that indicates whether the asynchronous operation has completed.
         /// </summary>
         /// <returns>
         /// true if the operation is complete; otherwise, false.
         /// </returns>
         public bool IsCompleted {
            get { return (AsyncResult.IsCompleted); }
         }
 
         /// <summary>
         /// Gets a <see cref="T:System.Threading.WaitHandle"/> that is used to wait for an asynchronous operation to complete.
         /// </summary>
         /// <returns>
         /// A <see cref="T:System.Threading.WaitHandle"/> that is used to wait for an asynchronous operation to complete.
         /// </returns>
         public WaitHandle AsyncWaitHandle {
            get {
               lock (this) {
                  if (AsyncResult != null)
                     return (AsyncResult.AsyncWaitHandle);
                  if (_asyncWaitHandle == null)
                     _asyncWaitHandle = new AutoResetEvent(_isCompleted);
                  return (_asyncWaitHandle);
               }
            }
         }
 
         /// <summary>
         /// Gets a user-defined object that qualifies or contains information about an asynchronous operation.
         /// </summary>
         /// <returns>
         /// A user-defined object that qualifies or contains information about an asynchronous operation.
         /// </returns>
         public Object AsyncState {
            get { return (AsyncResult.AsyncState); }
         }
 
         /// <summary>
         /// Gets a value that indicates whether the asynchronous operation completed synchronously.
         /// </summary>
         /// <returns>
         /// true if the asynchronous operation completed synchronously; otherwise, false.
         /// </returns>
         public bool CompletedSynchronously {
            get { return (AsyncResult.CompletedSynchronously); }
         }
 
         #endregion
 
         public void PerformNoThrow() {
            try {
               _result = _method.DynamicInvoke(_args);
            }
            catch (Exception ex) {
               _exception = ex;
            }
 
            _isCompleted = true;
 
            if (AsyncResult == null && _asyncWaitHandle != null) {
               _asyncWaitHandle.Set();
            }
         }
      }
 
      #endregion
 
      #region Nested type: SyncControl
 
      [DebuggerDisplay("SyncType = {SyncType}")]
      private class SyncControl : Control
      {
         public readonly String SyncType;
 
         public SyncControl(String syncType) {
            SyncType = syncType;
            CreateHandle();
         }
      }
 
      #endregion
   }
}

Он создает выделенный поток, окна для пробрасывания вызовов, а также является контекстно-зависимой реализацией ISynchronizeInvoke. Во время выполнения синхронной операции происходит ожидание синхронных обратных вызовов и их рекурсивное выполнение.

Класс NETFormController теперь выглядит очень просто:

using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
 
namespace NETLibrary
{
   [ComVisible(false)]
   [ClassInterface(ClassInterfaceType.None)]
   [ComSourceInterfaces(typeof(INETFormControllerCallback))]
   class NETFormController : UIExecutionContext, INETFormController
   {
      public NETFormController() {
         Invoke((MethodInvoker)InitializeForm, null);
      }
 
      public void AddCallback(INETFormControllerCallback callback) {
         Shown += callback.Shown;
         ButtonClicked += callback.ButtonClicked;
         Notification += callback.Notification;
      }
 
      public void Show() {
         Invoke((MethodInvoker)delegate { _netForm.Show(); }, null);
      }
 
      public void Close() {
         Invoke((MethodInvoker)delegate { _netForm.Close(); }, null);
      }
 
      public void RequestNotification() {
         Invoke((MethodInvoker)delegate { FireNotification(this, EventArgs.Empty); }, null);
      }
 
      public event Action Shown;
      public event Action ButtonClicked;
      public event Action Notification;
 
      private void InitializeForm() {
         _netForm = new NETForm();
         _netForm.Shown += FireShown;
         _netForm.ButtonClicked += FireButtonClicked;
      }
 
      private void FireShown(Object sender, EventArgs args) {
         if (Shown != null) {
            Invoke(Shown, null);
         }
      }
 
      private void FireButtonClicked(Object sender, EventArgs args) {
         if (ButtonClicked != null) {
            Invoke(ButtonClicked, null);
         }
      }
 
      private void FireNotification(Object sender, EventArgs args) {
         if (Notification != null) {
            Invoke(Notification, null);
         }
      }
 
      NETForm _netForm;
   }
}

Я слегка изменил код чтобы продемонстрировать работающую реентрабельность:

class ATL_NO_VTABLE CNETFormControllerCallback : public ATL::CComObjectRootEx<CComSingleThreadModel>,
                                                 public NETLibrary::INETFormControllerCallback
{
 
public:
   CNETFormControllerCallback() : _zNesting(0) { }
 
protected:
   DECLARE_PROTECT_FINAL_CONSTRUCT()
   BEGIN_COM_MAP(CNETFormControllerCallback)
      COM_INTERFACE_ENTRY(NETLibrary::INETFormControllerCallback)
   END_COM_MAP()
 
public:
   STDMETHOD(raw_Shown)() {
      OutputDebugString(_T("Shown\n"));
      return(S_OK);
   }
 
   STDMETHOD(raw_ButtonClicked)() {
      OutputDebugString(_T("ButtonClicked\n"));
      g_pNETObj->RequestNotification();
      return(S_OK);
   }
 
   STDMETHOD(raw_Notification)() {
      OutputDebugString(_T("Notification\n"));
      if (++_zNesting <= 10)
         g_pNETObj->RequestNotification();
      --_zNesting;
      return(S_OK);
   }
 
private:
   size_t _zNesting;
 
};

При нажатии на кнопку происходит десять рекурсивных вызовов и, соответственно, появляется 10 сообщений в окне отладки.

Окончательный вариант исходно когда можно скачать здесь.

11 коммент.:

Анонимный комментирует...

Hi just wanted to give you a quick heads up and let you know a few of the pictures aren't loading correctly. I'm not
sure why but I think its a linking issue. I've tried it in two different web browsers and both show the same outcome.

http://amherstblock.com/index.php?do=/blog/104002/getting-started-with-social-media-marketing-pertaining-to-website-seo/add-comment/
http://anicomu.com/userinfo.php?uid=8692
http://anepowa.com/newsite/?topic=buying-twitter-followers-why-is-it-renowned
http://apnaflex.com/index.php/georginaj/all
http://amazonface.com/index.php?do=/blog/29924/conveniences-of-twitter-followers-to-buy/

Here is my weblog: loiseandemma

Анонимный комментирует...

Heya! I'm at work surfing around your blog from my new iphone! Just wanted to say I love reading your blog and look forward to all your posts! Carry on the fantastic work!

http://agniyamusic.ru/userinfo.php?uid=6763
http://africansdebate.com/wp/?topic=buy-twitter-followers-without-spending-a-single-thing
http://agonychicks.com/index.php?a=stats&u=elanadawkins
http://africanamericansupersite.com/index.php?do=/blog/162054/instant-important-things-inside-social-media-marketing-outlined/add-comment/
http://aliquotgames.com/main/?topic=buy-twitter-followers-without-having-to-spend-a-penny

Feel free to surf to my weblog - overline

Анонимный комментирует...

After exploring a few of the blog posts on your blog,
I seriously appreciate your way of blogging. I added it to my bookmark site list and
will be checking back soon. Take a look at my website
as well and tell me how you feel.

http://79.170.44.78/mygighost.net/PaulineJi/tab:
info
http://83.150.87.118/WB/kanootti/modules/phpbb_keskustelu/phpBB2/profile.
php?mode=viewprofile&u=902445
http://affiliate-community.net/userinfo.php?uid=6272
http://50-87-72-80.unifiedlayer.com/blogs/270029/284822/very-simple-remedies-inside-of-h
http://74.200.92.133/forum/profile.php?mode=viewprofile&u=82847

Here is my web site: layoutandmor

Анонимный комментирует...

Do you have a spam problem on this site; I also am a blogger, and I was wanting
to know your situation; we have created some nice
practices and we are looking to swap solutions with others, why not shoot me an e-mail if interested.



http://askinahediye.com/node/28963
http://artsonet.com/index.php?do=/profile-92739/info/
http://asylummedia.net/digitalwhippingboy/?topic=good-ways-in-which-to-show-apple-iphone-video-that-includes-twitter-equipment
http://aslegalcr.com/expediente/profile.php?mode=viewprofile&u=16695
http://auto-bookmarks.de/search.php/all/features%20of%20social%20media%20marketing

Also visit my web site ... handball chamb豹

Анонимный комментирует...

Hello there, You've done a fantastic job. I'll definitely digg it and
personally suggest to my friends. I'm sure they'll
be benefited from this web site.

Here is my weblog übersetzung englisch (http://emiratesbizz.com)

Анонимный комментирует...

Грибок - , вызываемый патогенными микробактериями, является одним из самых часто встречающихся заболеваний, характеризующихся широкой симптоматикой неприятных проявлений. Найти эту острозаразную болезнь можно где угодно: в бассейне, на пляже, в сауне а также любом месте, где бывают большие скопления людей. И невзира на то, что в продаже сейчас имеется громаднейшее множество самых разных фармацевтических мазей и гелей, до появления на рынке инновационного препарата Варанга достаточно быстро покончить с грибковыми бактериями, влияющими не только на общеэстетический вид кожные покровы и ногтевых пластин, но и причиняют серьезный пагубный ущерб тканям, было очень нелегко. Благодаря появлению этого универсального крема, с безаналоговым уникальным природным составом, лечение грибка больше не является проблемой. Не требует много времени и сил. Вылечить грибок стоп будет возможно всего лишь за один месяц применения мази. VarangaOfficial - варанга от грибка - самая большая и исчерпывающа подборка фактов. Воспользовавшись услугами нашего ресурса, вы сможете узнать полную, всеисчерпывающую информацию об этом лекарственном средстве. Лично увидеть данные о клиническом тестировании геля, прочитать реальные отзывы пациентов и медицинского персонала. Изучить инструкцию по применению, прочитать об особенностях и методах работы мази, уяснить, как работает крем Варанга, где необходимо заказывать оригинальный сертифицированный препарат и, как избежать покупки подделки. Мы тщательно проверяем размещаемые на сайте данные. Предоставляем нашим пользователям сведения, которые были взяты исключительно из авторитетных источников. Если вы обнаружили у себя признаки появления грибкового заболевани или уже довольно продолжительное время, без ощутимых результатов пытаетесь избавиться от этого неприятного коварного недуга, у нас на сайте вы найдете быстрый и простой способ устранения проблемы. Приобщайтесь и живите здоровой полноценной жизнью. Теперь все ответы можно отыскать на одном сайте.

Анонимный комментирует...

VarangaOfficial - заказать мазь варанга - все, что нужно знать об этом препарате. Воспользовавшись нашим порталам, вы получите возможность узнать обстоятельную, полную информацию об этом лекарственном средстве. Увидеть данные о клиническом тестировании геля, прочесть отзывы реальных покупателей и врачей, использующих крем в своей лечебной практике. Изучить инструкцию по использованию, прочесть особенности и методы работы комплекса, понять, в чем заключаются особенности работы крема Варанга, где можно приобрести оригинальный сертифицированный препарат и, как избежать покупки подделки. Мы очень тщательно проверяем размещаемые на сайте данные. Предоставляем пользователям нашего ресурса сведения, которые были почерпнуты только из достоверных источников. Если вы обнаружили признаки появления грибка или уже довольно продолжительное время, без ощутимых результатов стараетесь излечиться от этого досадного недуга, у нас на сайте вы найдете простой и быстрый способ устранения проблемы. Присоединяетесь и живите здоровой полноценной жизнью. Мы собрали ответы на все вопросы на одном информационном ресурсе.

Анонимный комментирует...

VarangaOfficial - стоимость препарата варанга - все, что бы хотели знать об этом препарате. Воспользовавшись нашим сайтом, вы получите возможность узнать обстоятельную, полную информацию касающуюся представленного средства. Лично увидеть данные о клиническом тестировании геля, прочесть отзывы реальных покупателей и медицинского персонала. Ознакомиться с инструкцией по применению, прочесть об особенностях и методах работы мази, уяснить, в чем заключаются особенности работы крема Варанга, где нужно покупать оригинальный препарат и, как избежать покупки подделки. Мы скурпулезно проверяем размещаемые данные. Предоставляем нашим пользователям сведения, почерпнутые исключительно из достоверных источников. Если вы обнаружили признаки грибка или уже довольно продолжительное время, без ощутимых результатов пытаетесь избавиться от этого коварного недуга, наш сайт покажет вам быстрый и простой способ решения проблемы. Присоединяетесь и живите здоровой полноценной жизнью. Мы собрали ответы на все вопросы на одном информационном ресурсе.

Анонимный комментирует...

VarangaOfficial - варанга аналог - самая большая и исчерпывающа подборка фактов. Воспользовавшись нашим онлайн-сервисом, вы получите возможность узнать всеисчерпывающую информацию касательно данного лекарственного средства. Увидеть данные о клиническом тестировании геля, прочитать отзывы реальных пользователей и врачей. Изучить инструкцию по использованию, прочитать особенности и методы работы комплекса, понять, почему крем Варанга настолько эффективен, где нужно покупать оригинальный препарат и, как не нарваться на подделку. Мы скурпулезно проверяем размещаемые на сайте данные. Предоставляем посетителям нашего онлайн-ресурса сведения, которые были почерпнуты только из достоверных источников. Если вы обнаружили признаки появления грибка или же долго и безрезультатно пытаетесь избавиться от этого неприятного недуга, наш сайт покажет вам быстрый и простой способ решения проблемы. Приобщайтесь и живите здоровой полноценной жизнью. Мы собрали ответы на все вопросы на одном информационном ресурсе.

Анонимный комментирует...

Faucetpay-это инновационный микро-кошелек для монеро кошелек Приема микроплатежей. На сегодняшний день он поддерживает 8 криптовалют (bitcoin (btc), ethereum (eth), dogecoin (doge), litecoin (ltc), bitcoin cash (bch), dash (dash) digibyte (dgb)) и tron (trx). В кошелек постоянно добавляются новые криптовалюты. С помощью нашего ресурса вы получите возможность получать микроплатежи от кранов (как минимум в 1 сатоши, т. Е. Нет минимальной суммы для входящих платежей). После этого вы получаете возможность накапливать деньги на личном счете, а затем выводить все предложения на центральный кошелек (набрав минимальную сумму для такой операции, которая также очень мала, например, для биткоина, максимальная сумма для вывода будет стоить всего 1000 сатош). В кармане вашего кошелька огромное количество онлайн-проектов для заработка в криптовалютной сети с мгновенным выводом на faucetpay. Эти проекты, которые мы постоянно подвергаем тщательному отбору и при этом не платим, исключены из списка, поэтому вы можете остановить свой выбор на чрезвычайно актуальных и ярких порталах для того, чтобы заработать – биткойны. В кошельке, конечно же, есть обменник, в котором можно обмениваться криптовалютами друг с другом, с минимальными комиссиями, которые, со своей стороны, максимально комфортны. Так, в случае вывода средств с биткоин-бирж, где комиссия слишком дорогая. О процессе подробно есть возможность ознакомиться с приведенным выше самым простым вариантом-это обогатить интерьер биткоина без скрытых платежей. Официальной русскоязычной версии сайта faucetpay нет. Но с помощью встроенного переводчика через браузер или установленного расширения вы сможете решить эту проблему. Так что пользоваться услугами будет гораздо удобнее. А теперь пришло время рассказать вам, как пользоваться микро-кошельком faucetpay. И мы начнем с классического процесса регистрации.

Анонимный комментирует...

Музыкальный ресурс muzvoz.Ru предоставляет простейшую операцию скачать ava max sweet but psycho Для выбора музыки. Для всеобщего удобства мы предусмотрели любой из способов: вариант № 1: вам следует прибегнуть к фильтру поиска, доступному в главном меню ресурса. В этой ситуации поиск музыкального мира в формате mp3 осуществляется путем ввода названий редких композиций, групп, коллективов или определенных исполнителей. вариант № 2: поиск музыки на нашей платформе можно реализовать с помощью жанрового фильтра. Вам просто нужно найти интересующий вас стиль и изучить список предлагаемых композиций. Едва ли не главные преимущества музвоза.Ru заключается в том, что всем его постоянным посетителям и малоизвестным пользователям предлагается слушать треки удаленно бесплатно и без утомительной регистрации. Для того, чтобы прослушать синглы, вам вообще не нужно загружать какое-либо программное обеспечение и устанавливать дополнительные плееры. Слушать музыку онлайн реально прямо на ресурсе, нажав на иконку "воспроизвести". Чтобы снова прослушать интересующие вас хиты, не тратя слишком много минут на их поиск, вы можете использовать пользовательский плейлист (избранное). Это единственный способ, которым вы получаете возможность создать отличный альбом ваших любимых мелодий, который будет легко понять для прослушивания в любое время дня и ночи. Скачивать треки с нашего музыкального портала-настоящее удовольствие. Все новые продукты загружаются в короткие сроки, и люди не принимают обременения на объемы. Скорее всего, вы сможете загружать файлы в формате mp3, или одну песню, или пару альбомов, абсолютно без вкладов и при отсутствии регистрации. При загрузке игры клиенту не нужно вводить капчи, отправлять sms и выполнять другие трудоемкие операции. Весь макет, предлагаемый для загрузки, предлагается в формате mp3 - наиболее распространенном формате для контента и предоставления аудиоинформации.

Отправить комментарий

Copyright 2007-2011 Chabster