]> git.sesse.net Git - vlc/commitdiff
activex: implement async events handling for JavaScript
authorJean-Paul Saman <jean-paul.saman@m2x.nl>
Wed, 17 Feb 2010 22:35:12 +0000 (23:35 +0100)
committerJean-Paul Saman <jean-paul.saman@m2x.nl>
Thu, 18 Mar 2010 10:16:02 +0000 (11:16 +0100)
Events that do not originate from within the ActiveX JS context (which is a COM context)
cannot cross into ActiveX/COM context. All events received from libvlc are in a different
thread context then the ActiveX/COM code. Thus from a libvlc event handler callback it is
not possible to call into the ActiveX/COM context.

To solve this issue a seperate thread is created that manages sending of all events for
the ActiveX webplugin (including events from libvlc). All events are by default routed
through the GlobalInterfaceTable (GIT) which takes care ActiveX/COM calls that cross
different thread context in the ActiveX/COM world.

Signed-off-by: Jean-Paul Saman <jean-paul.saman@m2x.nl>
projects/activex/connectioncontainer.cpp
projects/activex/connectioncontainer.h

index 757f5139154175b1e8f0a5f08479c3060c248bb8..b9f601a8b8d703d15e98f4520ce39cc2940286d2 100644 (file)
@@ -2,8 +2,10 @@
  * connectioncontainer.cpp: ActiveX control for VLC
  *****************************************************************************
  * Copyright (C) 2005 the VideoLAN team
+ * Copyright (C) 2010 M2X BV
  *
  * Authors: Damien Fouilleul <Damien.Fouilleul@laposte.net>
+ *          Jean-Paul Saman <jpsaman@videolan.org>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 
 #include "utils.h"
 
+#include <assert.h>
+#include <rpc.h>
+#include <rpcndr.h>
+
 using namespace std;
 
+////////////////////////////////////////////////////////////////////////////////////////////////
+DEFINE_GUID(IID_IGlobalInterfaceTable,     0x00000146, 0x0000, 0x0000, 0xc0,0x00, 0x00,0x00,0x00,0x00,0x00,0x46);
+DEFINE_GUID(CLSID_StdGlobalInterfaceTable, 0x00000323, 0x0000, 0x0000, 0xc0,0x00, 0x00,0x00,0x00,0x00,0x00,0x46);
+
+const GUID  IID_IGlobalInterfaceTable = { 0x00000146, 0, 0, {0xc0, 0, 0, 0, 0, 0, 0, 0x46} };
+const CLSID CLSID_StdGlobalInterfaceTable = { 0x00000323, 0, 0, {0xc0, 0, 0, 0, 0, 0, 0, 0x46} };
 ////////////////////////////////////////////////////////////////////////////////////////////////
 
 /* this function object is used to return the value from a map pair */
@@ -89,8 +101,104 @@ public:
     {};
 };
 
+////////////////////////////////////////////////////////////////////////////////////////////////
+// Condition variable emulation
+////////////////////////////////////////////////////////////////////////////////////////////////
+
+static void ConditionInit(HANDLE *handle)
+{
+    *handle = CreateEvent(NULL, TRUE, FALSE, NULL);
+    assert(*handle != NULL);
+}
+
+static void ConditionDestroy(HANDLE *handle)
+{
+    CloseHandle(*handle);
+}
+
+static void ConditionWait(HANDLE *handle, CRITICAL_SECTION *lock)
+{
+    DWORD dwWaitResult;
+
+    do {
+        LeaveCriticalSection(lock);
+        dwWaitResult = WaitForSingleObjectEx(*handle, INFINITE, TRUE);
+        EnterCriticalSection(lock);
+    } while(dwWaitResult == WAIT_IO_COMPLETION);
+
+    assert(dwWaitResult != WAIT_ABANDONED); /* another thread failed to cleanup! */
+    assert(dwWaitResult != WAIT_FAILED);
+    ResetEvent(*handle);
+}
+
+static void ConditionSignal(HANDLE *handle)
+{
+    SetEvent(*handle);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////
+// Event handling thread
+//
+// It bridges the gap between libvlc thread context and ActiveX/COM thread context.
+////////////////////////////////////////////////////////////////////////////////////////////////
+DWORD WINAPI ThreadProcEventHandler(LPVOID lpParam)
+{
+    CoInitialize(NULL);
+    VLCConnectionPointContainer *pCPC = (VLCConnectionPointContainer *)lpParam;
+
+    while(pCPC->isRunning)
+    {
+        EnterCriticalSection(&(pCPC->csEvents));
+        ConditionWait(&(pCPC->sEvents), &(pCPC->csEvents));
+
+        if (!pCPC->isRunning)
+        {
+            LeaveCriticalSection(&(pCPC->csEvents));
+            break;
+        }
+
+        while(!pCPC->_q_events.empty())
+        {
+            VLCDispatchEvent *ev = pCPC->_q_events.front();
+            pCPC->_q_events.pop();
+            pCPC->_p_events->fireEvent(ev->_dispId, &ev->_dispParams);
+            delete ev;
+        }
+        LeaveCriticalSection(&(pCPC->csEvents));
+    }
+
+    CoUninitialize();
+    return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////
+// VLCConnectionPoint
 ////////////////////////////////////////////////////////////////////////////////////////////////
 
+VLCConnectionPoint::VLCConnectionPoint(IConnectionPointContainer *p_cpc, REFIID iid) :
+        _iid(iid), _p_cpc(p_cpc)
+{
+    // Get the Global Interface Table per-process singleton:
+    CoCreateInstance(CLSID_StdGlobalInterfaceTable, 0,
+                          CLSCTX_INPROC_SERVER,
+                          IID_IGlobalInterfaceTable,
+                          reinterpret_cast<void**>(&m_pGIT));
+};
+
+VLCConnectionPoint::~VLCConnectionPoint()
+{
+    // Revoke interfaces from the GIT:
+    map<DWORD,LPUNKNOWN>::iterator end = _connections.end();
+    map<DWORD,LPUNKNOWN>::iterator iter = _connections.begin();
+
+    while( iter != end )
+    {
+        m_pGIT->RevokeInterfaceFromGlobal((DWORD)iter->second);
+        ++iter;
+    }
+    m_pGIT->Release();
+};
+
 STDMETHODIMP VLCConnectionPoint::GetConnectionInterface(IID *iid)
 {
     if( NULL == iid )
@@ -113,17 +221,24 @@ STDMETHODIMP VLCConnectionPoint::GetConnectionPointContainer(LPCONNECTIONPOINTCO
 STDMETHODIMP VLCConnectionPoint::Advise(IUnknown *pUnk, DWORD *pdwCookie)
 {
     static DWORD dwCookieCounter = 0;
+    HRESULT hr;
 
     if( (NULL == pUnk) || (NULL == pdwCookie) )
         return E_POINTER;
 
-    if( SUCCEEDED(pUnk->QueryInterface(_iid, (LPVOID *)&pUnk)) )
+    hr = pUnk->QueryInterface(_iid, (LPVOID *)&pUnk);
+    if( SUCCEEDED(hr) )
     {
-        *pdwCookie = ++dwCookieCounter;
-        _connections[*pdwCookie] = pUnk;
-        return S_OK;
+        DWORD dwGITCookie;
+        hr = m_pGIT->RegisterInterfaceInGlobal( pUnk, _iid, &dwGITCookie );
+        if( SUCCEEDED(hr) )
+        {
+            *pdwCookie = ++dwCookieCounter;
+            _connections[*pdwCookie] = (LPUNKNOWN) dwGITCookie;
+        }
+        pUnk->Release();
     }
-    return CONNECT_E_CANNOTCONNECT;
+    return hr;
 };
 
 STDMETHODIMP VLCConnectionPoint::Unadvise(DWORD pdwCookie)
@@ -131,8 +246,7 @@ STDMETHODIMP VLCConnectionPoint::Unadvise(DWORD pdwCookie)
     map<DWORD,LPUNKNOWN>::iterator pcd = _connections.find((DWORD)pdwCookie);
     if( pcd != _connections.end() )
     {
-        pcd->second->Release();
-
+        m_pGIT->RevokeInterfaceFromGlobal((DWORD)pcd->second);
         _connections.erase(pdwCookie);
         return S_OK;
     }
@@ -154,17 +268,26 @@ void VLCConnectionPoint::fireEvent(DISPID dispId, DISPPARAMS *pDispParams)
     map<DWORD,LPUNKNOWN>::iterator end = _connections.end();
     map<DWORD,LPUNKNOWN>::iterator iter = _connections.begin();
 
+    HRESULT hr = S_OK;
+
     while( iter != end )
     {
-        LPUNKNOWN pUnk = iter->second;
-        if( NULL != pUnk )
+        DWORD dwCookie = (DWORD)iter->second;
+        LPUNKNOWN pUnk;
+
+        hr = m_pGIT->GetInterfaceFromGlobal( dwCookie, _iid,
+                                             reinterpret_cast<void **>(&pUnk) );
+        if( SUCCEEDED(hr) )
         {
             IDispatch *pDisp;
-            if( SUCCEEDED(pUnk->QueryInterface(_iid, (LPVOID *)&pDisp)) )
+            hr = pUnk->QueryInterface(_iid, (LPVOID *)&pDisp);
+            if( SUCCEEDED(hr) )
             {
-                pDisp->Invoke(dispId, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, pDispParams, NULL, NULL, NULL);
+                pDisp->Invoke(dispId, IID_NULL, LOCALE_USER_DEFAULT,
+                              DISPATCH_METHOD, pDispParams, NULL, NULL, NULL);
                 pDisp->Release();
             }
+            pUnk->Release();
         }
         ++iter;
     }
@@ -177,15 +300,21 @@ void VLCConnectionPoint::firePropChangedEvent(DISPID dispId)
 
     while( iter != end )
     {
-        LPUNKNOWN pUnk = iter->second;
-        if( NULL != pUnk )
+        LPUNKNOWN pUnk;
+        HRESULT hr;
+
+        hr = m_pGIT->GetInterfaceFromGlobal( (DWORD)iter->second, IID_IUnknown,
+                                              reinterpret_cast<void **>(&pUnk) );
+        if( SUCCEEDED(hr) )
         {
             IPropertyNotifySink *pPropSink;
-            if( SUCCEEDED(pUnk->QueryInterface(IID_IPropertyNotifySink, (LPVOID *)&pPropSink)) )
+            hr = pUnk->QueryInterface( IID_IPropertyNotifySink, (LPVOID *)&pPropSink );
+            if( SUCCEEDED(hr) )
             {
                 pPropSink->OnChanged(dispId);
                 pPropSink->Release();
             }
+            pUnk->Release();
         }
         ++iter;
     }
@@ -198,18 +327,20 @@ VLCDispatchEvent::~VLCDispatchEvent()
     //clear event arguments
     if( NULL != _dispParams.rgvarg )
     {
-        for(unsigned int c=0; c<_dispParams.cArgs; ++c)
-            VariantClear(_dispParams.rgvarg+c);
+        for(unsigned int c = 0; c < _dispParams.cArgs; ++c)
+            VariantClear(_dispParams.rgvarg + c);
         CoTaskMemFree(_dispParams.rgvarg);
     }
     if( NULL != _dispParams.rgdispidNamedArgs )
         CoTaskMemFree(_dispParams.rgdispidNamedArgs);
 };
 
+////////////////////////////////////////////////////////////////////////////////////////////////
+// VLCConnectionPointContainer
 ////////////////////////////////////////////////////////////////////////////////////////////////
 
 VLCConnectionPointContainer::VLCConnectionPointContainer(VLCPlugin *p_instance) :
-    _p_instance(p_instance), _b_freeze(FALSE)
+    _p_instance(p_instance), freeze(FALSE), isRunning(TRUE)
 {
     _p_events = new VLCConnectionPoint(dynamic_cast<LPCONNECTIONPOINTCONTAINER>(this),
             _p_instance->getDispEventID());
@@ -220,10 +351,31 @@ VLCConnectionPointContainer::VLCConnectionPointContainer(VLCPlugin *p_instance)
             IID_IPropertyNotifySink);
 
     _v_cps.push_back(dynamic_cast<LPCONNECTIONPOINT>(_p_props));
+
+    // init protection
+    InitializeCriticalSection(&csEvents);
+    ConditionInit(&sEvents);
+
+    // create thread
+    hThread = CreateThread(NULL, NULL, ThreadProcEventHandler,
+                           dynamic_cast<LPVOID>(this), NULL, NULL);
 };
 
 VLCConnectionPointContainer::~VLCConnectionPointContainer()
 {
+    EnterCriticalSection(&csEvents);
+    isRunning = FALSE;
+    ConditionSignal(&sEvents);
+    LeaveCriticalSection(&csEvents);
+
+    do {
+        /* nothing wait for thread to finish */;
+    } while(WaitForSingleObjectEx (hThread, INFINITE, TRUE) == WAIT_IO_COMPLETION);
+    CloseHandle(hThread);
+
+    ConditionDestroy(&sEvents);
+    DeleteCriticalSection(&csEvents);
+
     delete _p_props;
     delete _p_events;
 };
@@ -261,44 +413,32 @@ STDMETHODIMP VLCConnectionPointContainer::FindConnectionPoint(REFIID riid, IConn
     return NOERROR;
 };
 
-void VLCConnectionPointContainer::freezeEvents(BOOL freeze)
+void VLCConnectionPointContainer::freezeEvents(BOOL bFreeze)
 {
-    if( ! freeze )
-    {
-        // release queued events
-        while( ! _q_events.empty() )
-        {
-            VLCDispatchEvent *ev = _q_events.front();
-            _q_events.pop();
-            _p_events->fireEvent(ev->_dispId, &ev->_dispParams);
-            delete ev;
-        }
-    }
-    _b_freeze = freeze;
+    EnterCriticalSection(&csEvents);
+    freeze = bFreeze;
+    LeaveCriticalSection(&csEvents);
 };
 
 void VLCConnectionPointContainer::fireEvent(DISPID dispId, DISPPARAMS* pDispParams)
 {
-    if( _b_freeze )
-    {
-        // queue event for later use when container is ready
-        _q_events.push(new VLCDispatchEvent(dispId, *pDispParams));
-        if( _q_events.size() > 10 )
-        {
-            // too many events in queue, get rid of older one
-            delete _q_events.front();
-            _q_events.pop();
-        }
-    }
-    else
+    EnterCriticalSection(&csEvents);
+
+    // queue event for later use when container is ready
+    _q_events.push(new VLCDispatchEvent(dispId, *pDispParams));
+    if( _q_events.size() > 1024 )
     {
-        _p_events->fireEvent(dispId, pDispParams);
+        // too many events in queue, get rid of older one
+        delete _q_events.front();
+        _q_events.pop();
     }
+    ConditionSignal(&sEvents);
+    LeaveCriticalSection(&csEvents);
 };
 
 void VLCConnectionPointContainer::firePropChangedEvent(DISPID dispId)
 {
-    if( ! _b_freeze )
+    if( ! freeze )
         _p_props->firePropChangedEvent(dispId);
 };
 
index 704c57fd1716075f7b8e420ce23577a620b0c704..e4f9589f03383edc00e0dc8887d62647d5e5eb9e 100644 (file)
@@ -2,8 +2,10 @@
  * connectioncontainer.h: ActiveX control for VLC
  *****************************************************************************
  * Copyright (C) 2005 the VideoLAN team
+ * Copyright (C) 2010 M2X BV
  *
  * Authors: Damien Fouilleul <Damien.Fouilleul@laposte.net>
+ *          Jean-Paul Saman <jpsaman@videolan.org>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 #include <vector>
 #include <queue>
 #include <map>
+#include <cguid.h>
 
 class VLCConnectionPoint : public IConnectionPoint
 {
 
 public:
 
-    VLCConnectionPoint(IConnectionPointContainer *p_cpc, REFIID iid) :
-        _iid(iid), _p_cpc(p_cpc) {};
-    virtual ~VLCConnectionPoint() {};
+    VLCConnectionPoint(IConnectionPointContainer *p_cpc, REFIID iid);
+    virtual ~VLCConnectionPoint();
 
     // IUnknown methods
     STDMETHODIMP QueryInterface(REFIID riid, void **ppv)
@@ -69,6 +71,7 @@ public:
 private:
 
     REFIID _iid;
+    IGlobalInterfaceTable *m_pGIT;
     IConnectionPointContainer *_p_cpc;
     std::map<DWORD, LPUNKNOWN> _connections;
 };
@@ -121,14 +124,20 @@ public:
     void fireEvent(DISPID, DISPPARAMS*);
     void firePropChangedEvent(DISPID dispId);
 
-private:
+public:
+    CRITICAL_SECTION csEvents;
+    HANDLE sEvents;
 
     VLCPlugin *_p_instance;
-    BOOL _b_freeze;
+    BOOL isRunning;
+    BOOL freeze;
     VLCConnectionPoint *_p_events;
     VLCConnectionPoint *_p_props;
     std::vector<LPCONNECTIONPOINT> _v_cps;
     std::queue<class VLCDispatchEvent *> _q_events;
+
+private:
+    HANDLE  hThread;
 };
 
 #endif