2017-01-24 11 views
0

私はC++/CLIインターフェイスを介してMS UIA(COM)を使用していますが、C#アプリケーションはそのC++/CLIインターフェイスを使用しています(uiacppとしてこのインターフェイス/ dllを呼び出しましょう)COM UIAイベントは登録解除後にのみ呼び出されます

私はほとんど私が直面してる問題は、私は同じイベント(同じ毎回/異なるイベントの登録を解除した後、UIAに私が登録したイベントハンドラがのみ呼び出され、あるhttps://msdn.microsoft.com/en-us/library/windows/desktop/ff625914(v=vs.85).aspx

の例を以下uiacpp機構を扱うイベントを作成しました/イベントタイプ&テスト)。イベントを登録すると、イベントクラスのQueryInterfaceメソッドがUIAから2回呼び出されることがわかります。そのため、UIAは何かを実行します。それから私はテストでイベントを起こしますが、何も起こりません。そして、QueryInterfaceが2回以上呼び出されたイベントの登録を解除すると、イベントハンドラが呼び出されます。次に、UIAによってクリーンアップされた残りの参照(この時点で約6つ以上)が解放メソッドに呼び出されます。

C++/CLIクラス:

class CppUIAutomationEventHandler : 
    public ::IUIAutomationEventHandler 
{ 
private: 
    LONG _refCount; 

public: 
    int _eventCount; 
    gcroot<UIAMan::IUIAutomationEventHandler^> myHandler; 
    static std::list<IUIAutomationEventHandler*> *EventRegister; 

    // Constructor. 
    CppUIAutomationEventHandler() : _refCount(1), _eventCount(0) 
    { 
    } 

    // Constructor. 
    CppUIAutomationEventHandler(
     UIAMan::IUIAutomationEventHandler^ aHandler) 
     : _refCount(1) 
     , _eventCount(0) 
     , myHandler(aHandler) 
    { 
    } 

    // IUnknown methods. 
    ULONG STDMETHODCALLTYPE AddRef() 
    { 
     ULONG ret = InterlockedIncrement(&_refCount); 
     return ret; 
    } 

    ULONG STDMETHODCALLTYPE Release() 
    { 
     ULONG ret = InterlockedDecrement(&_refCount); 
     if (ret == 0) 
     { 
      delete this; 
      return 0; 
     } 
     return ret; 
    } 

    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppInterface) 
    { 
     if (riid == __uuidof(IUnknown) || riid == __uuidof(IUIAutomationEventHandler)) 
      *ppInterface = static_cast<IUIAutomationEventHandler*>(this); 
     else 
     { 
      *ppInterface = NULL; 
      return E_NOINTERFACE; 
     } 
     this->AddRef(); 
     return S_OK; 
    } 

    // IUIAutomationEventHandler methods 
    HRESULT STDMETHODCALLTYPE HandleAutomationEvent(::IUIAutomationElement * pSender, EVENTID eventID) 
    { 
     _eventCount++; 

     myHandler->HandleAutomationEvent(gcnew CUIAutomationElement(pSender, false), eventID); 

     return S_OK; 
    } 
}; 

、ここでC#は、最後のコードを使用して(イベントを登録するために呼び出すREF(マネージC++)クラスメソッドである。ここ

コードです最後に):

void CUIAutomation::AddAutomationEventHandler(
    int eventId 
    , IUIAutomationElement^ element 
    , TreeScope scope 
    , IUIAutomationCacheRequest^ cacheRequest 
    , IUIAutomationEventHandler^ handler) 
{ 
    ::IUIAutomationElement* el = safe_cast<CUIAutomationElement^>(element)->getElement(); 
    ::IUIAutomationEventHandler* _handler = new CppUIAutomationEventHandler(handler); 
    LastHResult = puia->AddAutomationEventHandler(
     eventId 
     , el 
     , (::TreeScope)(int)scope 
     , (cacheRequest != nullptr) ? ((CUIAutomationCacheRequest^)cacheRequest)->getElement() : NULL 
     , _handler); 
    CppUIAutomationEventHandler::EventRegister->push_back(_handler); 
}; 

私はハンドラのリストを使用してそれらの登録を解除しています。

CUIAutomation::CUIAutomation() 
{ 
    CoInitializeEx(NULL, COINIT_MULTITHREADED); 

    ::IUIAutomation* _puia; 
    HRESULT hr = CoCreateInstance(CLSID_CUIAutomation, NULL, 
        CLSCTX_INPROC_SERVER, IID_IUIAutomation, 
        (void**)&_puia); 

    if (SUCCEEDED(hr)) 
     puia = _puia; 
} 

そして最後に、これはC#の呼び出しです:uiacppを使用して

automationhandlerクラスの実装:

class AutomationHandler : IUIAutomationEventHandler 
    { 
     public AutomationHandler() 
     { 
     } 

     public void HandleAutomationEvent(IUIAutomationElement sender, int eventId) 
     { 
      Console.WriteLine("IUIAutomationEventHandler called"); 
     } 
    } 

とC#のレジスタ/またによって作成されたCOMポインタをされプイア登録解除ライン:

var aHandler = new AutomationHandler(); 
uia.AddAutomationEventHandler(UIA_EventIds.UIA_Window_WindowOpenedEventId, uia.GetRootElement(), TreeScope.TreeScope_Subtree, null, aHandler); 

// for debugging 
bool loop = true; 
while(loop) 
{ 
    Thread.Sleep(500); 
} 

uia.RemoveAutomationEventHandler(UIA_EventIds.UIA_Window_WindowOpenedEventId, uia.GetRootElement(), aHandler); 
+0

[System.Windows.Automation]の[AutomationEventHandler'](https://msdn.microsoft.com/en-us/library/system.windows.automationeventhandler.aspx)を使用していないのはなぜですか? https://msdn.microsoft.com/en-us/library/system.windows.automation.aspx)? –

+0

これは、pinvokeまたはtlbimpを使用してC#用に作成されたインターフェイスを最初に作成することを意味するためです。そして、そのアプローチでCOM呼び出しの詳細を監視することはできません。代わりにC++で独自のカスタム作成インターフェイスを使用しています。さらに、C#用のこれらの自動インターフェイスを使用すると安定性が低下するようですが、追加することはできますが、まだ証明できません。それが理由です。 – MattAPiroglu

+0

* System.Windows.Automation *は既に管理されており、C#から使用する必要のあるP/Invokeまたはtlbimpはありません。 –

答えて

1

これらのCOMイベントは、Windowsのメッセージgeループ。

登録と登録解除の間でメッセージをポンピングしないという事実と組み合わせると、登録を解除してメインメッセージループに戻るまでイベントが遅延します。

解決策の1つは、ブロッキングスリープの代わりにawait Task.Delayを使用することです。

+0

グッドネス、それはまさにその理由です!今回は、OSがC/C++プロジェクトで動作するコールバックのスリープスレッドを呼び出すと思っていましたが、.Netは引き続き私の...不承認を勝ち取っています。ありがとう、それは素晴らしい答えです! – MattAPiroglu

+1

@MattAPiroglu:ネイティブコードでスリープしているスレッドでもコールバックが起こることはありません(APCは、スリープの代わりに 'bAlertable = TRUE'を使用し、COMはAPCを使用しません)。 COMを使用しているすべてのコードでメッセージを送信する必要があることは、よく知られています。おそらくあなたのネイティブC++バージョンは、マルチスレッドアパートメントでCOMを初期化し、コールバックは実際に別のスレッドに入っていたでしょうか? –

関連する問題