Scripter Library




Click here for larger image

Environment: Windows NT/2000, Windows 9x, Visual C++ SP3

Scripter Library is a code library based on Active Scripting technology (Microsoft).
The main purpose of this code is NOT to show how to use Active Scripting
technology as there are a lot of samples in the Internet. When I was creating
this library I had as its object to write a library based on Active Scripting
that would allow to use Visual C++ functions and variables from within the VBScript
code. I wanted to combine Visual C++ and VBScript code and the main purpose
of my library was to invoke C++ functions and to use C++ variables from VBScript
code. So here is my library and it does it!

The reason for developing this library was simple: I often used Microsoft
technologies based on COM and ActiveX in my VC++ programs. Much of them are
still popular: ADO, Microsoft Office Automation, Outlook Automation, CDO and
the like are in wide use. So I had to use much of them in my VC projects. The
main problem was that help samples for much of them were written in VBScript
and I had to translate VBScript code into VC++. I need to say its a fatigue!
I thought it would be far better to write the code at once in two languages
. So the idea of using Active Scripting suggests itself. It would be nice to
write parts of code which use those Microsoft technologies in VBScript. And
immediately the great problem arose: I had to pass different parameters there
and back and I had to call back VC++ code from some places of VBScript code
to pass intermediate results.

What Scripter Library is:

  • A set of COM objects
  • Additional VC++ code (a set of classes) that can be used by MFC and
    ATL programs. ( In case of MFC code ATL support must be added into program.
    See how to do it below.)

What Scripter Library does:

  • Runs VBScript code from VC++ programs.
  • Support for “variables” added into VBScript code which are connected
    with VC++ objects. Thus “variables” allows to intercept all operations with
    them, includind assignment operations. If “variable” is used in left side of
    assignment statement, the value that assigns to it passes to appropriate VC++
    function. Scripter Library special function gets control in the moment of operation
    and gets the value as a parameter of the function. If the “variable” is used
    in left side of assignment statement or just used as a parameter of a function
    or a part of expression, Scripter Library gets control again and it is possible
    to provide custom value for such a “variable”.
  • Support for custom “functions” in VBScript code which are connected
    with VC++ code. When such a function is invoked from VBScript code, VC++ gets
    control and can use parameters passes to VBScript function. It can handle parameters
    and form return value that can be passed back to VBScript code. Moreover, “functions”
    support varying arguments number. You can pass to the “function” any argument
    number you want. The type of the parameters doesnt matter. Its like func(…)
    in C++ (Variable Number of Arguments). It is possible to get all the parameters
    from your VC++ code and handle them.
  • Support for special function “CreateObject(COM Object Name)” that
    takes as a parameter COM Class Name and creates a COM Class instance for further
    using in VBScript code. Its equivalent to native VBScript function CreateObject
    and substitutes it.
  • Raise exceptions from within a VC code of “functions” and “variables”
    which are handled by Active Scripting. (It usually brings up a message box with
    error message and dreadful big red exclamation mark)

Note: if I later use “functions” and “variables” in inverted commas,
Ill mean “functions” and “variables” which I mentioned above.

See the short sample of VBScript code ( its taken from AtlDriveB )
which uses Scripter Library.

' In this script file you can see main possibilities of 
' VB script working with my Scripter Library 
' So here they are:

' Get Variable
A = MyA
MsgBox MyA
' Put Variable
B = 5
MyB = B
MyB = "Hello VBWorld"

'Exchange between then
MyB = MyA
MyB = CStr(MyA)

' Get Return Value From Function
C = MyFunct()

' Pass varying arguments number to my function 
MyFunction "String", 55, CStr(MyA)

' Exception within my C++ code
ExceptionFunc()

See, MyA, MyB, MyFunction and ExceptionFunc() are connected with VC++
code and does custom actions. Look at how that implemented:

HRESULT CMyCustomHelper::GetMyA(CComVariant& cValue,
                                EXCEPINFO* pexcept)
{
 cValue = 5L;
 return S_OK;
}

HRESULT CMyCustomHelper::PutMyB(const CComVariant&
                                cValue,
                                EXCEPINFO* pexcept)
{
 switch(cValue.vt)
 {
  case VT_BSTR:
   bszString = cValue.bstrVal;
  break;

  case VT_I2:
  ...
 }

 return S_OK;
}

HRESULT CMyCustomHelper::MyFunct(int iArgNumber,
                                 CComVariant* pParameters,
                                 CComVariant& cRetVal,
                                 EXCEPINFO* pexcept)
{
 cRetVal = _T("Result string");
 return S_OK;
}

HRESULT CMyCustomHelper::MyFunction(int iArgNumber,
                                    CComVariant* pParameters,
                                    CComVariant& cRetVal,
                                    EXCEPINFO* pexcept)
{
 ...
 return S_OK;
}

HRESULT CMyCustomHelper::ExceptionFunc(int iArgNumber,
                                       CComVariant* pParameters,
                                       CComVariant& cRetVal,
                                       EXCEPINFO* pexcept)
{
 return MakeExceptionError(pexcept,
                           _T("My Exception Text"),
                           _T("Error in ExceptionFunc"));
}

For each custom function and variable there is a corresponding VC++
function. It looks simple, isnt it?
It is!
OK. Lets dig deeply into my library. At first Ill show you MY ORIGINAL
ideas on basis of which all that shown above is possible. Then Ill give you
the idea how to use my library.

Lets get started.

To understant how it works we need get into the theory of Active Scripting
and recall what Named Items are. Named Items are dispatch objects,
you have to set them up so that the script engine knows about them. The collection
of all the named dispatch objects that the script engine knows about is called
the “Script Namespace.” Items are added to the script namespace through
the IActiveScript::AddNamedItem method. We need implement an IDispatch
for the object and support type-information through ITypeInfo for the object.
When the script engine needs to resolve a reference to a named item, it uses
the IActiveScriptSite::GetItemInfo method to request an IUnknown pointer (which
it queries mainly for IDispatch) and an ITypeInfo pointer. The scripting engine
knows only about the name of the itemit’s the scripting host’s responsibility
to keep track of the interface pointer and type information of a named item.

So “functions” and “variables” both are Named Items. It means
that they are my own COM objects. And Ive done that. The main idea is that
all of my COM objects have to implement IDispatch interface.

Itll be logically if I start with “functions”. Look, how I use them
in VBScript code:

' Pass varying arguments number to my function
MyFunction "String", 55, CStr(MyA)
ExceptionFunc()

Note, that VB syntax allows to use parenthesis only for function with
no arguments, like ExceptionFunc(). For functions with arguments you dont have
to use parenthesis. Once “functions” are Named Items, Active Scripting has no
idea what to do with a string like a MyFunction “String”, 55, and it invokes
IDispatchs Invoke method. My implementation of IDispatch is IMultyParamFunct
in CMultyParamFunct class. I implement Invoke like this:

STDMETHODIMP CMultyParamFunct::Invoke(DISPID dispidMember,
                                      REFIID riid,
                                      LCID lcid,
                                      WORD wFlags,
                                      DISPPARAMS* pdispparams,
                                      VARIANT* pvarResult,
                                      EXCEPINFO* pexcepinfo,
                                      UINT* puArgErr)
{
 HRESULT hRes = S_FALSE;

 if (pexcepinfo)
  memset((void*)pexcepinfo,0,sizeof(EXCEPINFO));

 if(dispidMember != 0)
 {
  USES_CONVERSION;

  LPOLESTR lpolestr[255];
  swprintf((LPOLESTR)lpolestr,
           A2W(_T("Insupported DISPID\nFailed to "
           "Invoke\nDISPID = 0x%x\n")));

  return MakeExceptionError(pexcepinfo,
                            _T("IMultyParamFunct"),
                            (LPOLESTR)lpolestr,
                            puArgErr);
 }
 else
 {
  if((wFlags != DISPATCH_METHOD)
  && (wFlags != (DISPATCH_METHOD|DISPATCH_PROPERTYGET)))
  {
   return MakeExceptionError(pexcepinfo,
    _T("IMultyParamFunct"),
    _T("(wFlags != DISPATCH_METHOD)
    && (wFlags != (DISPATCH_METHOD|DISPATCH_PROPERTYGET))"));
  }

  if(!pdispparams)
  {
   return MakeExceptionError(pexcepinfo,
    _T("IMultyParamFunct"),
    _T("A required parameter was omitted"),
    puArgErr);
  }
  else if(!pvarResult)
  {
   return MakeExceptionError(pexcepinfo,
    _T("IMultyParamFunct"),
    _T("pvarResult == NULL"),
    puArgErr);
  }
  else
  {
   hRes = Fire_ImInvoked(pdispparams,
                         pvarResult,
                         pexcepinfo);
  }
 }

 return hRes;
}

CMultyParamFunct::Invoke right away checks the condition (dispidMember
== 0). If Invoke meets the conditions it continues. So, only zero DISPID’s
are allowed. This is because I work only with default properties and
default functions (see below).

In CMultyParamFunct::Invoke I at first check to see whether everything
is OK. Its when ((wFlags ==DISPATCH_METHOD) || (wFlags == (DISPATCH_METHOD|DISPATCH_PROPERTYGET))

According to MSDN flags meanings are:
DISPATCH_METHOD:
The member is invoked as a method. If a property has the same name,
both this and the DISPATCH_PROPERTYGET flag may be set.
DISPATCH_PROPERTYGET:
The member is retrieved as a property or data member.

After all other verificatios I at last invoke Fire_ImInvoked
with parameters passed to CMultyParamFunct::Invoke. Fire_ImInvoked
fires an event. Event is sent to connection point sink (see below).

Lets pay attention to default properties and default functions. The
method or property that has the COM index of zero is called the default property
or default function. VBScript enables a programmer to not use the
regular method/property syntax when calling the default value; you can leave
the syntactical call to the method/property off altogether. You can specify
a default property for COM component so the host application can get or set
the property’s value without explicitly naming the property. For example, if
you have exposed a property called myName and marked it as the default, you
can work with it in either of these ways in VBScript:

Set component = CreateObject("Component.MyComponent")
n = component.myName ' Gets property explicitly.
n = component   ' Gets value of myName as default.
Set component2 = CreateObject("Component.MyComponent2")

' Invokes the default method of component2
n = component2("Param")

To specify a default property, include an attribute assigning 
' a special dispatch identifier (a dispid) to the method.
This technique can be used to assign either a default property or a 
' default method, but not both. There can be only one element in the 
' script component with the dispid of zero.

Thus, all my “functions” and “variables” are used so that they use
the syntax of default properties. But if you look at idl file, you wont find
any:

[
 object,
 uuid(436A1147-8656-11D4-BE3B-000000000000),
 dual,
 helpstring("IMultyParamFunct Interface"),
 pointer_default(unique)
]
interface IMultyParamFunct : IDispatch
{
};
[
 object,
 uuid(92519695-8CC2-11D4-BE3F-000000000000),
 dual,
 helpstring("IVariable Interface"),
 pointer_default(unique)
]
interface IVariable : IDispatch
{
};

Can you see some methods in IVariable and IMultyParamFunct ? Im not.
🙂 No methods with 0 dispid, but nevertheless the syntax of default methods
and properties is used here. It is impossible to understand, huh? Ill try to
explain whats going on. As you know there are two types of calling of COM methods:
through the ID binding and VTBL binding. VTBL binding is a process
that allows an ActiveX client to call a method or property accessor function
directly without using the IDispatch interface. VTBL binding is faster
than both ID binding and late binding because the access is direct. And ID
binding
is the ability to bind member names to dispatch identifiers (DISPIDs)
at compile time (for example, by obtaining the IDs from a type library). This
approach eliminates the need for calls to IDispatch::GetIDsOfNames, and
results in improved performance over late-bound calls. Well, Active Scripting
uses ONLY late binding when works with with
objects. It binds member names to dispatch identifiers (IDs) at run time throught
IDispatch interface. Lets look deeper at them.

HRESULT GetIDsOfNames(
  REFIID  riid,
  OLECHAR FAR* FAR*  rgszNames,
  unsigned int  cNames,
  LCID   lcid,
  DISPID FAR*  rgDispId
); 

Maps a single member and an optional set of argument names to a corresponding
set of integer DISPIDs, which can be used on subsequent calls to IDispatch::Invoke.
The dispatch function DispGetIDsOfNames provides a standard implementation of
GetIDsOfNames.
When Active Scripting meets the line like this (see above notions
of syntax):

n = component   ' Gets value of myName as default.

… and ‘component’ is a Named Item then it will call components
IDispatch methods. IDispatchs GetIDsOfNames will not be called at
first as Active Scripting knows about ID of the method or property. Its ID
is 0 as all default properties have 0 ID number. And Active Scripter
doesnt care whether there is default method in idl file or not. It call IDispatch::Invoke
method despite everything and IDispatch::Invoke must decide to give the go-ahead
to the 0 ID or not. So I do this myself. But before Ill remind to
you what IDispatch::Invoke is.

HRESULT IDispatch::Invoke(
 DISPID  dispIdMember,
 REFIID  riid,
 LCID  lcid,
 WORD  wFlags,
 DISPPARAMS FAR*  pDispParams,
 VARIANT FAR*  pVarResult,
 EXCEPINFO FAR*  pExcepInfo,
 unsigned int FAR*  puArgErr
);
 

IDispatch::Invoke provides access
to properties and methods exposed by an object. Generally, you should not implement
Invoke directly. But I do! In an ActiveX client, IDispatch::Invoke
should be used to get and set the values of properties, or to call a method
of an COM object. The dispIdMember argument identifies the member to
invoke. The DISPIDs that identify members are defined by the implementor of
the object.

In my case when Active Scripting meets the line like this:

n = component   ' Gets value of component as default.

…it invokes IDispatch::Invoke with wFlags == DISPATCH_PROPERTYGET or
wFlags == DISPATCH_PROPERTYPUT or wFlags == DISPATCH_PROPERTYPUTREF or wFlags
== DISPATCH_METHOD|DISPATCH_PROPERTYGET.

And when Active Scripting meets the line like this

' Invokes the default method of component
n = component2("Param")

wFlags == DISPATCH_METHOD or wFlags == DISPATCH_METHOD|DISPATCH_PROPERTYGET.

See above the meanings of these flags.

Here its an implementation of CVariable::Invoke:

STDMETHODIMP CVariable::Invoke(DISPID dispidMember,
                               REFIID riid,
                               LCID lcid,
                               WORD wFlags,
                               DISPPARAMS* pdispparams,
                               VARIANT* pvarResult,
                               EXCEPINFO* pexcepinfo,
                               UINT* puArgErr)
{
 HRESULT hRes = S_FALSE;

 if(pexcepinfo)
  memset((void*)pexcepinfo,0,sizeof(EXCEPINFO));

 if(dispidMember != 0)
 {
  USES_CONVERSION;
  LPOLESTR lpolestr[255];
  swprintf((LPOLESTR)lpolestr,
           A2W(_T("Insupported DISPID\nFailed to "
           "Invoke\nDISPID = 0x%x\n")),
           dispidMember);

  return MakeExceptionError(pexcepinfo,
                            _T("IVariable"),
                            (LPOLESTR)lpolestr,
                            puArgErr);
 }
 else
 {
  if((wFlags != DISPATCH_PROPERTYGET)
  && (wFlags != DISPATCH_PROPERTYPUT)
  && (wFlags != DISPATCH_PROPERTYPUTREF)
  && (wFlags != (DISPATCH_METHOD|DISPATCH_PROPERTYGET)))
  {
   return MakeExceptionError(pexcepinfo,
    _T("IVariable"),
    _T("wFlags != DISPATCH_METHOD
    or DISPATCH_PROPERTYPUT
    or DISPATCH_PROPERTYPUTREF
    or DISPATCH_METHOD|DISPATCH_PROPERTYGET"));
  }

  if(!pdispparams)
  {
   return MakeExceptionError(pexcepinfo,
                             _T("IVariable"),
                             _T("A required parameter was omitted"),
                             puArgErr);
  }
  else if(!pvarResult
       && (wFlags == DISPATCH_PROPERTYGET))
  {
   return MakeExceptionError(pexcepinfo,
                             _T("IVariable"),
                             _T("pvarResult == NULL"),
                             puArgErr);
  }
  else
  {
   hRes = Fire_ImInUse(pdispparams,
                       wFlags,
                       pvarResult,
                       pexcepinfo);
  }
 }

 return hRes;
}

So, CVariable::Invoke, CMultyParamFunct::Invoke and CCreateObject::Invoke
have a lot of common in their realization. Besides checking of wFlags parameter
and reasonableness of other parameters all of my Invoke implementations call
(except the CCreateObject::Invoke which creates an instance of COM class just
in the function body) Fire_ImInvoked(pdispparams, pvarResult, pexcepinfo);

But wed better look attentively at idl file. Here is an excerpt from it:

[
 object,
 dual,
 uuid(436A1149-8656-11D4-BE3B-000000000000),
 helpstring("_IMultyParamFunctEvents Interface"),
  pointer_default(unique)
]
interface _IMultyParamFunctEvents : IDispatch
{
 import "oaidl.idl";
 [id(1), helpstring("method ImInvoked")]
  HRESULT ImInvoked([in] DISPPARAMS* pDispParams,
                    [out] EXCEPINFO* pExcepInfo,
                    [out, retval] VARIANT* pVarResult);
};
[
 object,
 dual,
 uuid(92519697-8CC2-11D4-BE3F-000000000000),
 helpstring("_IVariableEvents Interface"),
 pointer_default(unique)
]
interface _IVariableEvents : IDispatch
{
 [id(1), helpstring("method ImInUse")]
  HRESULT ImInUse([in] DISPPARAMS* pdispparams,
                  [in] WORD wFlags,
                  [out] EXCEPINFO* pexcepinfo,
                  [out, retval] VARIANT* pvarResult);
};

Speaking informally, _IVariableEvents and _IMultyParamFunctEvents
are the templates. They both show how to implement ImInvoked and ImInUse outside
of scripter.dll. So AtlDrive implements these interfaces and CVariable::Invoke,
CMultyParamFunct::Invoke and calls them. That technique is named “Connection
Points”.

A little lyrical digression to remind to you what connection points
are:

COM has a model, called connection points with a corresponding mechanism
that allows objects to expose their capabilities to call specific interfaces.
A connection has two parts: the object calling the interface, called the source,
and the object implementing the interface, called the sink. A connection point
is the interface exposed by the source. By exposing a connection point, a source
allows sinks to establish connections to itself (the source). Through the connection
point mechanism (the IConnectionPoint interface), a pointer to the sink
interface is passed to the source object. This pointer provides the source with
access to the sinks implementation of a set of member functions. For example,
to fire an event implemented by the sink, the source can call the appropriate
method of the sinks implementation.

And I use connection points to pass parameters from COM objects (from
scripter.dll) to objects in AtlDrive. The sinks are in CCustomValueCP and CCustomFunctCP.
Just look:

STDMETHODIMP CCustomFunctCP::ImInvoked(DISPPARAMS *pDispParams,
                                       EXCEPINFO *pExcepInfo,
                                       VARIANT *pVarResult)
{
 if(m_pCustomFunct)
 {
  int iArgsNumber = pDispParams->cArgs;
  CComVariant* pvarArgs = (CComVariant*)pDispParams->rgvarg;
  CComVariant* pRetVal = (CComVariant*)pVarResult;
  return m_pCustomFunct->CustomActions(iArgsNumber,pvarArgs,*pRetVal,
  pExcepInfo);
 }

 return S_FALSE;
}

STDMETHODIMP CCustomValueCP::ImInUse(DISPPARAMS *pDispParams,
                                     WORD wFlags,
                                     EXCEPINFO *pExcepInfo,
                                     VARIANT *pVarResult)
{
 if(m_pCustomValue)
 {
  HRESULT hRes;

  switch(wFlags)
  {
   case DISPATCH_PROPERTYGET|DISPATCH_METHOD:
   case DISPATCH_PROPERTYGET:
   {
   CComVariant* cComVar = (CComVariant*)(pVarResult);
   hRes = m_pCustomValue->GetValue(*cComVar,pExcepInfo);
   break;
   }
   case DISPATCH_PROPERTYPUT:
   case DISPATCH_PROPERTYPUTREF:
   {
   CComVariant* cComVar = (CComVariant*)(&(pDispParams->rgvarg[0]));
   hRes = m_pCustomValue->PutValue(*cComVar,pExcepInfo);
   break;
   }

   default:
    return MakeExceptionError(pExcepInfo,
     _T("CCustomValueCP::ImInUse"),
     _T("Property Only Supports PUT,PUTREF and GET"));
  }

  return hRes;
 }

 return S_FALSE;
}

Both of them convert parameters and pass them to custom processing
which is implemented in CHelperValue::PutValue/GetValue and CHelperFunct::CustomActions
respectively. Have passed through the row of subsidiary classes at last we come
to specific classes which do custom work. Look at definition of one of them
(from AtlDrive):

class CMyAdoHelper : public CSHelper
{
public:
 CMyAdoHelper();
 virtual ~CMyAdoHelper();
public:
 VARIABLE_GET_SECTION()
 GET_VAR_DECLARE("adOpenKeyset",GetadOpenKeyset)
 GET_VAR_DECLARE("adLockOptimistic",GetadLockOptimistic)
 GET_VAR_DECLARE("adCmdTable",GetadCmdTable)
 GET_VAR_DECLARE("SQLQuery",GetSQLQuery)
 GET_VAR_DECLARE("ConnectionString",GetConnectionString)

 FUNCT_SECTION()
 CUSTFUNCT_DECLARE("MyPrint",DoMyPrint)
};

You can see some special macroses like GET_VAR_DECLARE and CUSTFUNCT_DECLARE
to handle “functions” and “variables”. Look at implementation of some of them:

HRESULT CMyAdoHelper::GetadOpenKeyset(CComVariant& cValue,
                                      EXCEPINFO* pexcept)
{
 // adOpenKeyset = 1,
 cValue = 1L;

 return S_OK;
}

HRESULT CMyAdoHelper::DoMyPrint(int iArgNumber,
                                CComVariant* pParameters,
                                CComVariant& cRetVal,
                                EXCEPINFO* pexcept)
{
 ...
 MessageBox(NULL,bszMyStr,_T("MyPrint"),MB_OK|MB_TASKMODAL);

 return S_OK;
}

Now look inside of Scripts/ado.src at lines relating
to CMyAdoHelper::GetadOpenKeyset and CMyAdoHelper::DoMyPrint functions:

rstMyTable.CursorType = adOpenKeyset
MyPrint val1 , val2

When line rstMyTable.CursorType = adOpenKeyset from
VBScript takes control, coupled function CMyAdoHelper::GetadOpenKeyset
from VC++ invokes quering adOpenKeysets value. Correspondingly,
when line MyPrint val1 , val2 from VBScript takes control, coupled
function CMyAdoHelper::DoMyPrint from VC++ is invoked and gets val1 ,
val2 as its parameters.

A few macroses make connection between “variable” or “function”
names and functions in class to invoke. These macroses are implemented like
message maps in MFC:

BEGIN_VARIABLE_GET(CMyAdoHelper)
 GET_VARIABLE("adOpenKeyset",GetadOpenKeyset)
 GET_VARIABLE("adLockOptimistic",GetadLockOptimistic)
 GET_VARIABLE("adCmdTable",GetadCmdTable)
 GET_VARIABLE("SQLQuery",GetSQLQuery)
 GET_VARIABLE("ConnectionString",GetConnectionString)
END_VARIABLE_GET()

BEGIN_FUNCT(CMyAdoHelper)
 CUST_FUNCTION("MyPrint",DoMyPrint)
END_FUNCT()

CMyAdoHelper is a class name which contains functions GetadOpenKeyset,GetadLockOptimistic,GetadCmdTable,
GetSQLQuery,GetConnectionString, DoMyPrint . GET_VARIABLE, PUT_VARIABLE
and CUST_FUNCTION macroses implement association
between names and functions in class.

And special macroses are designed to declare functions in class and
to declare a presence of “variable” and “function” sections. Again, you
see the name in quotes and function name after the comma.

VARIABLE_GET_SECTION()
 GET_VAR_DECLARE("adOpenKeyset",GetadOpenKeyset)
 GET_VAR_DECLARE("adLockOptimistic",GetadLockOptimistic)
 GET_VAR_DECLARE("adCmdTable",GetadCmdTable)
 GET_VAR_DECLARE("SQLQuery",GetSQLQuery)
 GET_VAR_DECLARE("ConnectionString",GetConnectionString)
 FUNCT_SECTION()
CUSTFUNCT_DECLARE("MyPrint",DoMyPrint)

I can say in addition that macroses are implemented with use of pointers
to member functions. Pointers to member functions are used to implement member
function callbacks, which can reside in any class due to the use of class.

Lets take a closer look at pointers to member functions. Take, for
example, the following CFoo class:

class CFoo {
public:
 void f(int) ;
};

Lets call CFoo::f through a pointer. Below, I have defined
MemFntPtr as a pointer to a CFoo member function taking an integer
and returning void:

typedef void (CFoo::*MemFntPtr)(int) ;

Then I declare a pointer to a member function and initialize it with
the address of CFoo::f:

MemFuncPtr ptr = &CFoo::f ;

The following code fragment shows how to call CFoo::f:

CFoo aFoo ;
CFoo* pFoo = &aFoo ;

// Call f directly.
aFoo.f(5) ;

// Call f via an object pointer.
pFoo->f(5) ;

// Call f using member function pointer.
(aFoo.*ptr)(5) ;

// Call f using member function pointer 
//  and an object pointer.
(pFoo->*ptr)(5) ;

Class member pointers are very powerful facilities for implementing
callbacks in classes.

Scripter Library Users Guide

To use scripter library you have to register scripter.dll by running
regsvr32 scripter.dll. There are some differences of using Scripter Library
in MFC and ATL projects.


Here is a list of files to include into project to use Scripter Library.
These files are the same for all type of projects. (MFC or ATL)

  • AtlPreDrive.cpp
  • AtlPreDrive.h
  • CustomFunct.cpp
  • CustomFunct.h
  • CustomFunctCP.cpp
  • CustomFunctCP.h
  • CustomValue.cpp
  • CustomValue.h
  • CustomValueCP.cpp
  • CustomValueCP.h
  • ScriptHost.cpp
  • ScriptHost.h
  • SHelper.cpp
  • SHelper.h
  • Tools.cpp
  • Tools.h
  • Scripter.h

And here is a snippet that shows how to use Scripter Library.

CComObject<CScriptHost>* pcSHost;
CComObject<CScriptHost>::CreateInstance(&pcSHost);
(*pcSHost).AddRef();
/*
Here is created an COM object of CScriptHost class.
We need to call AddRef(); before using.
CScriptHost contains Active Scripting engine support.
You can Attach and Detach CSHelpers descendants so
you can do with only one object of CScriptHost,
attaching and detaching CSHelpers descendants.
*/
CMyCustomHelper cMyHelper;
/*
 CMyCustomHelper is derived from CSHelper that is a base class
 Which lets us to perform custom actions for "functions"
 and "variables"
*/

if(!cMyHelper.CreateInternals()) return 0;
if(!pcSHost->Attach(&cMyHelper)) return 0;

LPSTR szScriptText[500];
GetDlgItemText(IDC_EDITSCRIPT,(LPSTR)szScriptText,500);

// This one sets the script text 
pcSHost->SetStript((LPSTR)szScriptText);
// Start Script Program
pcSHost->Run();
// Stop Script Program
pcSHost->Stop();

pcSHost->Detach();
// Detach cMyHelper
(*pcSHost).Release();
/*
Release COM object
*/

Using of Scripter Library in MFC projects has a few distinctions from
the shown above used in pure ATL projects.
The first one is global preprocessor definition you have to add: _MFC_PROJ.

The second one is some additional lines in CWinApp derived class:

class CAtlDriveApp : public CWinApp
{
...
private:
 BOOL m_bATLInited;
private:
 BOOL InitATL();
 void UninitATL();
};

BOOL CAtlDriveApp::InitInstance()
{
 if (!InitATL()) return FALSE;
...
}

int CAtlDriveApp::ExitInstance()
{
 // TODO: Add your specialized code here and/or 
 // call the base class
 UninitATL();
 OleUninitialize();

 return CWinApp::ExitInstance();
}

BOOL CAtlDriveApp::InitATL()
{
 m_bATLInited = TRUE;
#if _WIN32_WINNT >= 0x0400
 HRESULT hRes = CoInitializeEx(NULL, COINIT_MULTITHREADED);
#else
 HRESULT hRes = CoInitialize(NULL);
#endif
 if (FAILED(hRes))
 {
  m_bATLInited = FALSE;
  return FALSE;
 }
 _Module.Init(ObjectMap, AfxGetInstanceHandle());
 _Module.dwThreadID = GetCurrentThreadId();
 return TRUE;
}

void CAtlDriveApp::UninitATL()
{
 _Module.RevokeClassObjects();
 _Module.Term();
 CoUninitialize();
}

Later on you can use Scripter Library classes like in ATL project.

I hope that the amount of information Ive given you is quite enough to understand
all the ideas of my scripter library and let you successfully use it in your
projects to suit your own ends.

Downloads contain three directories: AtlDrive, AtlDriveB and Scripter. AtlDrive
is a demo MFC program that uses Scripter Library and was described in my article.
AtlDriveB is demo ATL project. Scripter is a COM object set that work witt
library.

Before starting AtlDrive.exe register Scripter.dll.

UML Diagrams

To show you the integral picture of Scripter Library Ive built some UML
diagrams with Rational Rose reverse-engineering process.

Main Use Case Diagram
Scenario (Collaboration Diagram)
Scenario (Sequence Diagram)
Object Model (Collaboration Diagram)
Use Case Realization (Scripter Class Diagram)
General Scheme (Common Class Diagram)
Component Diagram

Downloads

Download all files (source + binaries) – 312 Kb

Download source code only- 107 Kb

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read