// 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);
}