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


Comments

  • This is way too cool not to use...

    Posted by Legacy on 08/23/2001 12:00am

    Originally posted by: mecutio

    Okay,
    first of all if your not using COM, you should be. If your reading this page you probably should be.
    I've started using this code to do scripting for a games engine (implementing lots of COM(read LOVE).
    As testament, here are the before and afters
    before
    exe size -> 196 k (release)
    classes -> 115 + structs
    fps -> 90 -> 95
    Tris -> 30 000 (texels) (multi textured, per pixel lighting etc...

    After
    exe size -> 48 k (release)
    classes -> 10
    fps -> 135 -> 145
    Tris -> 40 000 (texels) (multi textured, per pixel lighting etc...
    (not to be totally biased, I improved brush dlls and changed the mesh render algo's)


    mecutio

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • Hundreds of millions of users have adopted public cloud storage solutions to satisfy their Private Online File Sharing and Collaboration (OFS) needs. With new headlines on cloud privacy issues appearing almost daily, the need to explore private alternatives has never been stronger. Join ESG Senior Analyst Terri McClure and Connected Data in this on-demand webinar to take a look at the business drivers behind OFS adoption, how organizations can benefit from on-premise deployments, and emerging private OFS …

Most Popular Programming Stories

More for Developers

RSS Feeds