Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Firing Events among ActiveX controls on IE

0.00/5 (No votes)
24 Aug 2005 5  
The ActiveX control fires events to others using IConnectionPointContainer interface on the Internet Explorer.

Introduction

A couple of connectable objects on IE Browser, that I've been retrieving a few sample source/article for my new job throughout the week before last, and I found no article but several posts needing the same knowledge. This is one of the reasons this article has been just born. The key techniques of the article are IConnectionPointContainer and IOleContainer implementations. You might scribble freely in any small circle and it does simultaneously serve each step of the scribbling to a large circle even over different IE instances. The small ones are connection-point servers (CPServer), the large one a client (CPClient). All of them are ActiveXs for IE browser.

Demo usage

The downloadable demo Zip has the following 5 files:

  • CPServer.dll, CPClient.dll
  • 2Server&1Client.html, anotherServer.html
  • readme.txt
  1. Right-click each DLL in Explorer and in the context menu, select 'Open from application' and 'Register' (or type 'regsvr32 xxx.dll' for each DLL at the command prompt).
  2. Double-click xxx.html in Explorer (or right-click in the Visual Studio editor, and select 'Preview').

Demo by Windows Installer usage

The downloadable demo by Windows Installer makes auto installation with registrations for you. It's also simple to uninstall. After the installation, you may find two short-cut icons that point to installed HTML files on your desktop. Click them.

A red state signals connection break

You might left-click in any large circle CPClient to make it exclusive towards events from small circles CPServer. This means executing IConnectionPoint::Unadvise as a method. Simultaneously the CPClient changes its background from red to blue like signal as an upper image.

The 2Server&1Client.html has two CPServers and one CPClient model, and the anotherServer.html one CPServer model.

Save/Load/Get menu for scribble data

You might right-click in large circle CPClient to get Save/Load/Get menu for scribble data. A local file shall be saved/loaded into/from the CPClient.dll's folder and has a fixed name 'scribble.dat'. Two URLs are inserted in the HTML as <PARAM NAME="SushiURL" VALUE="http://www.informax.co.jp/pen.dat"> and <PARAM NAME="PhotoURL" VALUE="http://www.informax.co.jp/photo.dat"> added to CPClient object's script. Once you have gotten the pen.dat/photo.dat via Internet, you are also able to save it as a local file named 'scribble.dat'.

Scribble data format

It is a very simple text format. Every Scribble vector/line starts parameters '0 0 0' with carriage return. After this to the end of the vector/line, there follows those which consist of logical X,Y axes and RGB color number such as '50 111 16777215'.

Workspace and its projects

Workspace

This workspace is using VC++6.0, ATL and STL. ATL for COM interface implementation, STL for the coordinates collection of mouse movement of the scribble, for another collection of connection-point cookies and for etc.. The workspace consists of two projects both for building an ActiveX control. The first one builds CPClient.dll module, the second does CPServer.dll.

CPServer Implementation

My English is poor, so that the basic ActiveX/ATL implementation should be explained by other helpful articles contributed to this site. Both ActiveX controls are launched in the order of their script line position at the HTML file by IE browser. Here are the main extracts of the 2Server&1Client.html script:

<BODY bgColor="#ffff99">
   <OBJECT ID="CPServer" CLASSID="CLSID:xxx-...-xxx" HEIGHT=230 WIDTH=200 ALIGN=LEFT>
     <PARAM NAME="PenColor" VALUE="#ff0000">
   </OBJECT>
   <OBJECT ID="CPServer" CLASSID="CLSID:xxx-...-xxx" HEIGHT=230 WIDTH=200 ALIGN=RIGHT>
     <PARAM NAME="PenColor" VALUE="#000000">
   </OBJECT>  
   <OBJECT ID="CPClient" CLASSID="CLSID:xxx-...-xxx" HEIGHT=330 WIDTH=300>
     ...
   </OBJECT>
</BODY>

Note that the above PARAM tag line sets each CPServer's pen color to red or black in RGB hexadecimal format, but you are able to set any favorite color number in 'xxxxxx' as follows:

   <PARAM NAME="PenColor" VALUE="#xxxxxx">

The following are the main extracts of the anotherServer.html script. Also note a line setting CPServer's pen color to LIME-GREEN.

<BODY bgColor="#ffff99">
   <OBJECT ID="CPServer" CLASSID="CLSID:xxx-...-xxx" HEIGHT=230 WIDTH=200 ALIGN=LEFT>
     <PARAM NAME="PenColor" VALUE="#32CD32">
   </OBJECT>
</BODY>

The CPServer's put-property for pen color is implemented as a class member function to follow:

STDMETHODIMP CCPServer::put_PenColor(BSTR newVal)
{
   _bstr_t b (newVal, true);
   // define inclusive charactors as RGB hexadecimal format

   std::string s, s16 ("0123456789abcdefABCDEF");
   s = (LPCSTR)b;
   // check string's format if like "#ff00aa"

   if (s.length()) {  
     if (s.at(0) == '#') {
       s = s.substr(1, s.length() - 1);
       if (s.length() == 6) {
         int st = s.find_first_not_of (s16);
         if (st == -1) {
           sscanf (s.c_str (), "%x", &m_PenColor);
           m_PenColor = ((m_PenColor >> 0x0) & 0xff) << 0x10 |
                        ((m_PenColor >> 0x8) & 0xff) << 0x8  |
                        ((m_PenColor >> 0x10) & 0xff) << 0x0;
           return S_OK;
         }
       }
     }
   }
   // set black  

   m_PenColor = 0;
   return S_OK;
}

The following three inherited interfaces in the CCPServer class' definition are very important:

////////////////////////////////////////////////////////////////////////

// CCPServer

class ATL_NO_VTABLE CCPServer : 
   ...
  public IObjectSafetyImpl<CCPServer, 
                    INTERFACESAFE_FOR_UNTRUSTED_CALLER>,
  public IProvideClassInfo2Impl<&CLSID_CPServer, 
                    &DIID__ICPServerEvents, &LIBID_CPSERVERLib>,
  public IPersistPropertyBagImpl<CCPServer>
{
public:
 ...
BEGIN_COM_MAP(CCPServer)
   ...
   COM_INTERFACE_ENTRY(IObjectSafety)
   COM_INTERFACE_ENTRY(IProvideClassInfo2)
   COM_INTERFACE_ENTRY(IProvideClassInfo)
   COM_INTERFACE_ENTRY(IPersistPropertyBag)
END_COM_MAP()
...
BEGIN_PROP_MAP(CCPServer)
  ...
  // property PenColor added

  PROP_ENTRY("PenColor", 1, CLSID_NULL)
  // Example entries

  // PROP_ENTRY("Property Description", dispid, clsid)

  // PROP_PAGE(CLSID_StockColorPage)

END_PROP_MAP()
...
STDMETHOD(GetInterfaceSafetyOptions)(REFIID riid, DWORD *pdwSupportedOptions, 
                                                  DWORD *pdwEnabledOptions); 
STDMETHOD(SetInterfaceSafetyOptions)(REFIID riid, DWORD dwOptionSetMask, 
                                                  DWORD dwEnabledOptions);
...

Particularly, IProvideClassInfo2 interface is indispensable for implementing the IConnectionPointContainer::FindConnectionPoint function. If you comment out the three lines concerning the IProvideClassInfo2 interface, you shall certainly come across the assertion at the line written above including FindConnioectnPoint. The interface provides enough information to a client so that the client can provide support for the outgoing interface at run time.

Second, IObjectSafety interface allows the IE browser to determine whether this ActiveX is safe without accessing the registry. If you comment out the two lines concerning the IObjectSafety interface and also do two overriding methods GetInterfaceSafetyOptions and SetInterfaceSafetyOptions, you shall certainly be warned of a message dialog concerning the ActiveX's safety, as shown in the following image:

Third, IPersistPropertyBag interface is a base class for using the PARAM from the HTML page to set the property. If you comment out the three lines written above including this interface, you shall certainly be unable to set any pen color but black.

CPClient Implementation

When a CPClient object is launched by the IE browser, it gets the browser's IOleContainer interface pointer. Next, using IOleContainer::EnumObjects function, it enumerates all OLE objects' IUnknown interface embedded in the browser page, of course including the CPClient itself. Then the CPClient can find the CPServer's IUnknown interface pointer. Using this interface, the CPClient finally sets up the connection firing events between the CPServers in the member function CCPClient::DoAdvice as follows:

...
  if (SUCCEEDED (hr)) {
    _bstr_t bstName ("CPServer");
    if (bstName == bstr) {
       // get the IConnectionPointContainer interface pointer

       IConnectionPointContainer* pConnPtContaine  = NULL;
       hr = pUnk->QueryInterface (IID_IConnectionPointContainer, 
                                          (void**)&pConnPtContainer);
       _ASSERT (SUCCEEDED (hr) && pConnPtContainer != NULL);
       // get the IConnectionPoint interface pointer

       IConnectionPoint* pConnPt  = NULL;
       hr = pConnPtContainer->FindConnectionPoint (DIID__ICPServerEvents, 
                                                               &pConnPt);
       _ASSERT (SUCCEEDED (hr) && pConnPt != NULL);
       // set/reset the CPClient's Unknown interface 

       // as the CPServer's outgoing interface

       DWORD dwCookie = 0;
       if (bAdvise) {
         // get a Cookie

         hr = pConnPt->Advise (this->GetUnknown(), &dwCookie);
         // save a Cookie into Cookie collection

         m_vectorCookie.push_back (dwCookie);
       } else {
         // Unadvise all connection-point through Cookie collection 

         for (vector<DWORD>::iterator iter = m_vectorCookie.begin (); 
                                      iter != m_vectorCookie.end (); iter++) {
           dwCookie = (DWORD)*iter;
           hr = pConnPt->Unadvise (dwCookie);
           if (hr == CONNECT_E_NOCONNECTION) continue;
           else  break;
         }
       }
       pConnPt->Release ();
       pConnPtContainer->Release ();
     }
  }
...

IConnectionPoint::Advise function passes a Sink object that is called each time by CPServer's event. Sink object or CPClient IUnknown. And IConnectionPoint::Unadvise function stops the event connection.

Here are the main extracts of the 2Server&1Client.html script:

<BODY bgColor="#ffff99">
   ...
   <OBJECT ID="CPClient" CLASSID="CLSID:xxx-...-xxx" HEIGHT=330 WIDTH=300>
     <PARAM NAME="SushiURL" VALUE="http://www.informax.co.jp/pen.dat">
     <PARAM NAME="PhotoURL" VALUE="http://www.informax.co.jp/photo.dat">
   </OBJECT>
</BODY>

SushiURL property sets the URL to get a sample data of the pen-like sushi scribble prepared for you; PhotoURL property does the photo-like one.

In CPClient's circle, when you right-click and select the 'Get Sushi scribble via Internet' menu, the following code lines in the menber function CCPClient::OnCommandContextMenu will be executed:

...
  case ID_MENU_FILENAME_PEN:
  case ID_MENU_FILENAME_PHOTO:
  {
    // create WAIT cursor

    hWaitCursor = ::LoadCursor(NULL, IDC_WAIT);
    hOldCursor = ::SetCursor(hWaitCursor);
    // clear the stroke vector

    m_stroke.erase(m_stroke.begin(), m_stroke.end());
    // if internet connection is available

    if (::InternetAttemptConnect(0) != ERROR_SUCCESS)
      goto error;
    // open connection to internet

    HINTERNET hInternetConnection = ::InternetOpen("Open", 
                 INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, NULL);
    if (hInternetConnection == NULL)
      goto error;
    // open URL gotten by 'SushiURL' or 'PhotoURL' property 

    // at <PARAM NAME="SushiURL"...> or

    // <PARAM NAME="PhotoURL"...>

    HINTERNET hURLSession = ::InternetOpenUrl(hInternetConnection, 
          wID == ID_MENU_FILENAME_PEN ? m_sSushiUrl.c_str():m_sPhotoUrl.c_str(), 
          NULL, 0, INTERNET_FLAG_RELOAD | INTERNET_FLAG_TRANSFER_BINARY
          | INTERNET_FLAG_RAW_DATA, 0);
    // read the URL file

    std::istringstream* pIstringstream = NULL;
    std::istream* pIstream = NULL;
    if (hURLSession != NULL) {
      DWORD dwBytesAvailable = 0, dwBytesDownloaded = 0;
      std::string sLoad;
      do {
        if (::InternetQueryDataAvailable(hURLSession,
                  &dwBytesAvailable, 0, 0)) {
          std::vector<char> buffer(dwBytesAvailable + 1);
          ::InternetReadFile(hURLSession, &buffer[0], 
                  dwBytesAvailable, &dwBytesDownloaded);
          std::string s(buffer.begin(), dwBytesAvailable);
          // append buffer to string

          sLoad += s;
        } else  // end of data

          break;
      } while (dwBytesDownloaded > 0);
      // create a stream of string

      pIstringstream = new istringstream(sLoad);
      // convert string stream to input stream

      pIstream = new istream(*pIstringstream);
      // close the URL session

      ::InternetCloseHandle(hURLSession);
      // the stroke vector reads from steam

      m_stroke.in_stream(*pIstream);
      delete pIstream;
      delete pIstringstream;
    } else
      goto error;
    // close internet connection

    ::InternetCloseHandle(hInternetConnection);
    ::SetCursor(hOldCursor);
    this->RedrawWindow();
  }
  break;
...

The whole job is getting scribble data from the URL into the vector collection m_stroke. Both m_sSushiUrl and m_sPhotoUrl are class members and each holds a URL addressing different Sushi scribble resources. First it is necessary to change to wait cursor until the job is done, since Internet access is frequently too slow. The std::string sLoad packs up a block of the read data, std::istringstream*pIStringstream packs up all sLoad as a stream memory buffer, and std::istream*pIstream is created as an I/O stream from pIStringstream. The last job is to restore the previous cursor.

Notes

  • F5 key causes CPClient or CPServer to restart through IE.
  • Left-click CPClient twice for re-connecting to all the CPServers whenever a connection break occurs.
  • How to make photo.dat from a sushi bitmap owes to .dan.g.'s article (see below), and I have modified his demo source (ask me about it).

History

Updated on August 17, 2005

  • Improve painting speed and fixed the related bugs.
  • Served a photo-like data and fix the related sources.

Updated on April 4, 2004

  • Enabled saving/loading scribble data to/from a local file.
  • Enabled getting scribble data from URL.
  • Revised article/source on the safety of ActiveX.
  • Supports UNICODE.

Updated on April 1, 2004

  • Enabled scribbling among more than one IE instance.
  • Free pen color parameter at HTML script.

Updated on Dec 1, 2003

  • No flicker during scribbling.
  • Enabled scribbling within only CPServer's circle.

Posted on Jun 2, 2002

  • First distributed.

Tips

  • Here is an interesting matter related with IOLeContainer that I've taken notice of. The cyclic relationship among the interfaces:

Acknowledgement

When putting this project, I got an idea from Andrew's excellent article:

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here