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 CPServer
s 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 CPServer
s 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: