Click here to Skip to main content
15,886,362 members
Articles / Programming Languages / C++

COM in plain C, Part 6

Rate me:
Please Sign up or sign in to vote.
4.89/5 (32 votes)
22 Jul 2006CPOL25 min read 103K   2.4K   102  
How to write an ActiveX Script Host in C.
// IActiveScriptSite.c
//
// This file contains our MyRealIActiveScriptSite object. This is
// a multiple interface object that has a standard IActiveScriptSite
// as the base object, and a standard IActiveScriptSiteWindow as a
// sub-object.

#include <windows.h>
#include <stddef.h>
#include <activscp.h>
#include "IActiveScriptSite.h"
#include "Extern.h" 

// Our IActiveScriptSite VTable. NOTE: We declare the first arg to be a
// pointer to a MyRealIActiveScriptSite (because that's what it really
// is, and what we want). But the definition of an IActiveScriptSite
// VTable in Microsoft's include file defines it as an IActiveScriptSite
// pointer. So the compiler will likely give us warnings, but they are
// trivial
static STDMETHODIMP QueryInterface(MyRealIActiveScriptSite *, REFIID, void **);
static STDMETHODIMP_(ULONG) AddRef(MyRealIActiveScriptSite *);
static STDMETHODIMP_(ULONG) Release(MyRealIActiveScriptSite *);
static STDMETHODIMP GetLCID(MyRealIActiveScriptSite *, LCID *);
static STDMETHODIMP GetItemInfo(MyRealIActiveScriptSite *, LPCOLESTR, DWORD, IUnknown **, ITypeInfo **);
static STDMETHODIMP GetDocVersionString(MyRealIActiveScriptSite *, BSTR *);
static STDMETHODIMP OnScriptTerminate(MyRealIActiveScriptSite *, const VARIANT *, const EXCEPINFO *);
static STDMETHODIMP OnStateChange(MyRealIActiveScriptSite *, SCRIPTSTATE);
static STDMETHODIMP OnScriptError(MyRealIActiveScriptSite *, IActiveScriptError *);
static STDMETHODIMP OnEnterScript(MyRealIActiveScriptSite *);
static STDMETHODIMP OnLeaveScript(MyRealIActiveScriptSite *);

static const IActiveScriptSiteVtbl SiteTable = {
	QueryInterface,
	AddRef,
	Release,
	GetLCID,
	GetItemInfo,
	GetDocVersionString,
	OnScriptTerminate,
	OnStateChange,
	OnScriptError,
	OnEnterScript,
	OnLeaveScript};

// IActiveScriptSiteWindow VTable
static STDMETHODIMP siteWnd_QueryInterface(IActiveScriptSiteWindow *, REFIID, void **);
static STDMETHODIMP_(ULONG) siteWnd_AddRef(IActiveScriptSiteWindow *);
static STDMETHODIMP_(ULONG) siteWnd_Release(IActiveScriptSiteWindow *);
static STDMETHODIMP GetSiteWindow(IActiveScriptSiteWindow *, HWND *);
static STDMETHODIMP EnableModeless(IActiveScriptSiteWindow *, BOOL);

static const IActiveScriptSiteWindowVtbl SiteWindowTable = {
	siteWnd_QueryInterface,
	siteWnd_AddRef,
	siteWnd_Release,
	GetSiteWindow,
	EnableModeless};


// Since we're going to support using only 1 script engine at a time,
// we need only one IActiveScriptSite. We'll declare it globally to
// make this easy (instead of GlobalAlloc'ing it)
MyRealIActiveScriptSite		MyActiveScriptSite;

const WCHAR				LineNumStr[] = {'L','i','n','e',' '};

#define NUMDIGITS 9

// For uNumToAscii()
const unsigned long ConvChars[NUMDIGITS] = {1000000000, 100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10};





//===========================================================================
//============================ Helper functions =============================
//===========================================================================

/********************* allocIActiveScriptSiteObject() *******************
 * Initializes an MyRealIActiveScriptSite object. Called once at the
 * start of our program.
 */

void initIActiveScriptSiteObject(void)
{
	// Initialize the lpVtbl members of our IActiveScriptSite and
	// IActiveScriptSiteWindow sub-objects
	MyActiveScriptSite.site.lpVtbl = (IActiveScriptSiteVtbl *)&SiteTable;
	MyActiveScriptSite.siteWnd.lpVtbl = (IActiveScriptSiteWindowVtbl *)&SiteWindowTable;
}


 






//===========================================================================
//======================= IActiveScriptSite functions =======================
//===========================================================================

static STDMETHODIMP QueryInterface(MyRealIActiveScriptSite *this, REFIID riid, void **ppv)
{
	// An ActiveX Script Host is supposed to provide an object
	// with multiple interfaces, where the base object is an
	// IActiveScriptSite, and there is an IActiveScriptSiteWindow
	// sub-object. Therefore, a caller can pass an IUnknown or
	// IActiveScript VTable GUID if he wants our IActiveScriptSite.
	// Or, the caller can pass a IActiveScriptSiteWindow VTable
	// GUID if he wants our IActiveScriptSiteWindow sub-object
	if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IActiveScriptSite))
		*ppv = this;
	else if (IsEqualIID(riid, &IID_IActiveScriptSiteWindow))
		*ppv = ((unsigned char *)this + offsetof(MyRealIActiveScriptSite, siteWnd)); 
	else
	{
		*ppv = 0;
		return(E_NOINTERFACE);
	}
	AddRef(this);
	return(S_OK);
}

static STDMETHODIMP_(ULONG) AddRef(MyRealIActiveScriptSite *this)
{
	// Since our MyRealIActiveScriptSite is not allocated, but instead
	// declared statically, we have no need to maintain a reference
	// count
	return(1);
}

static STDMETHODIMP_(ULONG) Release(MyRealIActiveScriptSite *this)
{
	// Since our MyRealIActiveScriptSite is not allocated, but instead
	// declared statically, we have no need to maintain a reference
	// count, nor free it.
	//
	// NOTE: If our MyRealIActiveScriptSite did contain some resources
	// that we had to free, then we'd need to maintain a reference
	// count, and free those resources when the reference count was
	// zero
	return(1);
}

// Called by the script engine to get any pointers to our own host-defined
// objects whose functions a script may directly call. We don't implement
// any such objects here, so this is just a stub.
static STDMETHODIMP GetItemInfo(MyRealIActiveScriptSite *this, LPCOLESTR objectName, DWORD dwReturnMask, IUnknown **objPtr, ITypeInfo **typeInfo)
{
	if (dwReturnMask & SCRIPTINFO_IUNKNOWN) *objPtr = 0;
	if (dwReturnMask & SCRIPTINFO_ITYPEINFO) *typeInfo = 0;
	return(E_FAIL);
}


/* **************************** uNumToAsciiL() *************************
 * Converts the passed ULONG to an ascii string of digits in base 10,
 * putting that string into the passed buffer, left-justified.
 *
 * num =	unsigned long value to convert
 * buffer =	pointer to buffer into which to place the ascii chars. Must
 *			be at least 16 chars wide.
 *
 * RETURNS: Pointer to end of the string (ie, where null byte would be
 * stored if string is to be nul-terminated).
 */

WCHAR * uNumToAscii(unsigned long num, WCHAR * buffer)
{
	register const unsigned long *	conv = &ConvChars[0];
	register unsigned long			div;
	unsigned char					flg;
	unsigned char					i;

	// Haven't got first non-0 digit yet
	flg = 0;
	i = NUMDIGITS;

	// Skip leading 0's
	while (num < *conv)
	{
		++conv;
		--i;
	}

	// Convert digits
	while (num > 9)
	{
		div = *(conv)++;
		*(buffer)++ = flg = (unsigned char)(num/div) + '0';
		num %= div;
		--i;
	}

	// See how many 0 places we need to add
	if (flg)
	{
		while (i--) *(buffer)++ = '0';
	}

	// Convert 1's digit and store
	*(buffer)++ = (unsigned char)num + '0';

	// Null-terminate
//	*buffer = 0;

	// Return end of string
	return(buffer);
}


// Called by the script engine when there is an error running/parsing a script.
// The script engine passes us its IActiveScriptError object whose functions
// we can call to get information about the error, such as the line/character
// position (in the script) where the error occurred, an error message we can
// display to the user, etc. Typically, our OnScriptError will get the error
// message and display it to the user.
static STDMETHODIMP OnScriptError(MyRealIActiveScriptSite *this, IActiveScriptError *scriptError)
{
	ULONG				lineNumber;
	BSTR				desc;
	EXCEPINFO			ei;
	register WCHAR		*msg;
	register WCHAR		*ptr;
	register DWORD		len;

	// Call GetSourcePosition() to retrieve the line # where
	// the error occurred in the script
	scriptError->lpVtbl->GetSourcePosition(scriptError, 0, &lineNumber, 0);

	// Call GetSourceLineText() to retrieve the line in the script that
	// has an error.
	//
	// Note: The IActiveScriptError is supposed to clear our BSTR pointer
	// if it can't return the line. So we shouldn't have to do
	// that first. But you may need to uncomment the following line
	// if a script engine isn't properly written. Unfortunately, too many
	// engines are not written properly, so we uncomment it
	desc = 0;
	scriptError->lpVtbl->GetSourceLineText(scriptError, &desc);

	// Call GetExceptionInfo() to fill in our EXCEPINFO struct with more
	// information.
	//
	// Note: The IActiveScriptError is supposed to zero out any fields of
	// our EXCEPINFO that it doesn't fill in. So we shouldn't have to do
	// that first. But you may need to uncomment the following line
	// if a script engine isn't properly written
//	ZeroMemory(&ei, sizeof(ei));
	scriptError->lpVtbl->GetExceptionInfo(scriptError, &ei);

	// Allocate a buffer to format the message
	len = (24*sizeof(WCHAR)) + sizeof(LineNumStr) + 2;	// leave room for the line number + "Line " + terminating nul
	if (ei.bstrSource) len += SysStringByteLen(ei.bstrSource) + 4;
	if (ei.bstrDescription) len += SysStringByteLen(ei.bstrDescription) + 4;
	if (desc) len += SysStringByteLen(desc) + 4;

	if ((msg = ptr = GlobalAlloc(GMEM_FIXED, len)))
	{
		// Format the message we'll display to the user
		if (ei.bstrSource)
		{
			len = SysStringLen(ei.bstrSource);
			CopyMemory(ptr, ei.bstrSource, len * sizeof(WCHAR));
			ptr += len;
			*(ptr)++ = '\r';
			*(ptr)++ = '\n';
		}

		CopyMemory(ptr, LineNumStr, sizeof(LineNumStr));
		ptr += (sizeof(LineNumStr)/sizeof(WCHAR));
		ptr = uNumToAscii(lineNumber + 1, ptr);

		if (ei.bstrDescription)
		{
			*(ptr)++ = ':';
			*(ptr)++ = ' ';
			len = SysStringLen(ei.bstrDescription);
			CopyMemory(ptr, ei.bstrDescription, len * sizeof(WCHAR));
			ptr += len;
		}

		if (desc)
		{
			*(ptr)++ = '\r';
			*(ptr)++ = '\n';
			len = SysStringLen(desc);
			CopyMemory(ptr, desc, len * sizeof(WCHAR));
			ptr += len;
		}

		*ptr = 0;
		PostMessage(MainWindow, WM_APP, (WPARAM)msg, 0);
	}

	// Free what we got from the IActiveScriptError functions
	SysFreeString(desc);
	SysFreeString(ei.bstrSource);
	SysFreeString(ei.bstrDescription);
	SysFreeString(ei.bstrHelpFile);

	return(S_OK);
}

// Called when the script engine wants to know what language ID our
// program is using.
static STDMETHODIMP GetLCID(MyRealIActiveScriptSite *this, LCID *lcid)
{
	*lcid = LOCALE_USER_DEFAULT;
	return(S_OK);
}

// Called when the script engine wishes to retrieve the version number
// (as a string) for the current document. We are expected to return a
// SysAllocString()'ed BSTR copy of this version string.
//
// Here's what I believe this is for. An engine may implement some
// IPersist object. When we AddScriptlet or ParseScriptText some
// script with the SCRIPTTEXT_ISPERSISTENT flag, then the script
// engine will save that script to disk for us, when we call the
// engine IPersist's Save() function. But maybe the engine wants to
// minimize unnecessary saving to disk. So, it fetches this version
// string and caches it when it does the first save to disk. Then,
// when we subsequently call IPersist Save again, the engine fetches
// this version string again, and compares it to the previous version
// string. If the same string, then the engine assumes the script doesn't
// need to be resaved. If different, then the engine assumes our
// document has changed and therefore it should resave the scripts.
// In other words, this is used simply as an "IsDirty" flag by IPersist
// Save to see if a persistant script needs to be resaved. I don't
// believe that engines without any IPersist object bother with this,
// and I have seen example engines _with_ an IPersist that don't
// bother with it.
static STDMETHODIMP GetDocVersionString(MyRealIActiveScriptSite *this, BSTR *version) 
{
	// We have no document versions
	*version = 0;

	// If an engine chokes on the above, try this instead:
//	if (!(*version = SysAllocString("")))
//		return(E_OUTOFMEMORY);
	
	return(S_OK);
}

// Called when the engine has completely finished running scripts and
// has returned to INITIALIZED state. In many engines, this is not
// called because an engine alone can't determine when we are going
// to stop running/adding scripts to it.
static STDMETHODIMP OnScriptTerminate(MyRealIActiveScriptSite *this, const VARIANT *pvr, const EXCEPINFO *pei)
{
	return(S_OK);
}

// Called when the script engine's state is changed (for example, by
// us calling the engine IActiveScript->SetScriptState). We're passed
// the new state.
static STDMETHODIMP OnStateChange(MyRealIActiveScriptSite *this, SCRIPTSTATE state)
{
	return(S_OK);
}

// Called right before the script engine executes/interprets each
// script added via the engine IActiveScriptParse->ParseScriptText()
// or AddScriptlet(). This is also called when our IApp object
// calls some function in the script.
static STDMETHODIMP OnEnterScript(MyRealIActiveScriptSite *this)
{
	return(S_OK);
}

// Called immediately after the script engine executes/interprets
// each script.
static STDMETHODIMP OnLeaveScript(MyRealIActiveScriptSite *this) 
{
	return(S_OK);
}





















//===========================================================================
//==================== IActiveScriptSiteWindow functions ====================
//===========================================================================

static STDMETHODIMP siteWnd_QueryInterface(IActiveScriptSiteWindow *this, REFIID riid, void **ppv)
{
	// Since our IActiveScriptSiteWindow is a sub-object embedded inside of our
	// MyRealIActiveScriptSite object (ie, our MyRealIActiveScriptSite has "multiple
	// interfaces" and our IActiveScriptSiteWindow happens to be a sub-object),
	// then we just delegate to our base object's (IActiveScriptSite's) QueryInterface
	// to do the real work. We do this by substituting our IActiveScriptSite object for
	// the "this" pointer. That's easy to do because both our IActiveScriptSiteWindow
	// and IActiveScriptSite objects are embedded inside of our one MyRealIActiveScriptSite.
	// So a little pointer arithmatic gets us what we want
	this = (IActiveScriptSiteWindow *)(((unsigned char *)this - offsetof(MyRealIActiveScriptSite, siteWnd)));
	return(QueryInterface((MyRealIActiveScriptSite *)this, riid, ppv));
}

static STDMETHODIMP_(ULONG) siteWnd_AddRef(IActiveScriptSiteWindow *this)
{
	this = (IActiveScriptSiteWindow *)(((unsigned char *)this - offsetof(MyRealIActiveScriptSite, siteWnd)));
	return(AddRef((MyRealIActiveScriptSite *)this));
}

static STDMETHODIMP_(ULONG) siteWnd_Release(IActiveScriptSiteWindow *this)
{
	this = (IActiveScriptSiteWindow *)(((unsigned char *)this - offsetof(MyRealIActiveScriptSite, siteWnd)));
	return(Release((MyRealIActiveScriptSite *)this));
}

// Called by the script engine when it wants to know what window it should
// use as the owner of any dialog box the engine presents.
static STDMETHODIMP GetSiteWindow(IActiveScriptSiteWindow *this, HWND *phwnd)
{
	// Give it our main app window
	*phwnd = MainWindow;
	return(S_OK);
}

// Called when the script engine wants us to enable/disable all of our open
// windows.
static STDMETHODIMP EnableModeless(IActiveScriptSiteWindow *this, BOOL enable)
{
	// We have only 1 open window -- our main window
	EnableWindow(MainWindow, enable);
	return(S_OK);
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions