
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
- 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).
- 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

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);
std::string s, s16 ("0123456789abcdefABCDEF");
s = (LPCSTR)b;
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;
}
}
}
}
m_PenColor = 0;
return S_OK;
}
The following three inherited interfaces in the CCPServer class' definition are very important:
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)
...
PROP_ENTRY("PenColor", 1, CLSID_NULL)
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) {
IConnectionPointContainer* pConnPtContaine = NULL;
hr = pUnk->QueryInterface (IID_IConnectionPointContainer,
(void**)&pConnPtContainer);
_ASSERT (SUCCEEDED (hr) && pConnPtContainer != NULL);
IConnectionPoint* pConnPt = NULL;
hr = pConnPtContainer->FindConnectionPoint (DIID__ICPServerEvents,
&pConnPt);
_ASSERT (SUCCEEDED (hr) && pConnPt != NULL);
DWORD dwCookie = 0;
if (bAdvise) {
hr = pConnPt->Advise (this->GetUnknown(), &dwCookie);
m_vectorCookie.push_back (dwCookie);
} else {
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:
{
hWaitCursor = ::LoadCursor(NULL, IDC_WAIT);
hOldCursor = ::SetCursor(hWaitCursor);
m_stroke.erase(m_stroke.begin(), m_stroke.end());
if (::InternetAttemptConnect(0) != ERROR_SUCCESS)
goto error;
HINTERNET hInternetConnection = ::InternetOpen("Open",
INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, NULL);
if (hInternetConnection == NULL)
goto error;
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);
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);
sLoad += s;
} else
break;
} while (dwBytesDownloaded > 0);
pIstringstream = new istringstream(sLoad);
pIstream = new istream(*pIstringstream);
::InternetCloseHandle(hURLSession);
m_stroke.in_stream(*pIstream);
delete pIstream;
delete pIstringstream;
} else
goto error;
::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
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: