Tcl scripting

Posted by Metaxy on Fri 09 May 2003 06:38 PM — 29 posts, 94,350 views.

#0
How about Tcl scripting for MUSHclient?

Tcl, the Tool Command Language, is the leading language for embedding into applications to automate tasks (precisely what MUclient scripting is). It is used by NBC for video scheduling, by Oracle and Sybase for testing, by NASA in the Mars Pathfinder probe, by AOL for their generated web content, and in countless applications that use scripting. It is thread-safe, has a very simple syntax, and has built-in functionality for disabling all "non-scripting" type functionality (like file manipulation, etc.) with one line of code. VMoo and TkMOO already have this functionality, to great success.

If you added an option to "trust" a script and allow it to use Tk (the accompaying graphics toolkit) and file i/o, you could even allow TkMOO plugins to run under MUclient. Hell, if you did this I'd personally write the adapter code to make it work - I've gotten authorization to write an adapter for his plugin code for my own client from the author, and presumably he'd be OK with one for MC too.

Here's the URL: http://tcl.activestate.com/
Greece #1
I like new language support... Beats the heck out of all other clients... But does that mean that i'll have to learn TCL now too? :p
#2
Only if you want to :P
Australia Forum Administrator #3
I'll take a stab at it. :)

Can you help me learn enough Tcl to test it? For instance, how do I make a function that calls a COM object? Here is an example from Jscript:


function OnHelp (sName, sLine, wildcards)
  {
  world.Note (World.GetPluginInfo (World.GetPluginID, 3));
  }
Australia Forum Administrator #4
And array iteration? Like this from Python:


#
#  interate through array 
#

for cmd in world.GetInternalCommandsList: world.Note (cmd) 
#5
Well, if you're implementing the MUSHclient commands yourself, you would most likely render the various objects (world etc.) as namespaces.

[code]proc OnHelp {sName sLine wildcards} {
{
World::Note [World::GetPluginInfo [World.GetPluginID] 3]];
}[/code]

and

[code]
#
# interate through array
#
foreach cmd [World::GetInternalCommandsList] {World::Note $cmd}[/code]

However, if a built-in COM interface (rather than Tcl's very easy native embedding system) is necessary for whatever reason , you're going to need to use the Optcl library for COM at http://www.sys.uea.ac.uk/~fuzz/optcl/default.html.
#6
I'm sorry, I didn't check "forum codes." Just ignore the [code] and [/code] bits.
#7
Also, make note that the semicolon in Tcl is not only optional if you provide a newline but recommended to be excluded.
Australia Forum Administrator #8
You can edit your posts you know. :)

However if you are going to use square brackets and forum codes, you should really use MUSHclient's notepad to "escape forum codes" so that things like [b] don't get rendered as bold (that is, in the code).

I am having trouble getting it to register the script engine, which version of tcl do you recommend I install? I tried ActiveTcl 8.3.5.0 however it didn't register the script engine, and I am in the dark a bit about how to get it all working. For instance, trying to open the register.tcl file in Wish (should I be doing this?) gives messages about:

% 0x80029c4a {Error loading type library/DLL.}

Trying regsvr32 on the tclscript.dll gives messages about "the DllRegisterServer entry point was not found".
#9
Try ActiveTcl 8.4.whatever. It has many enhancements and is more stable.
Australia Forum Administrator #10
OK, I installed 8.4.2.0 which seemed to go OK, and registered the script engine, which appears in the Registry.

However when trying to initialise the script engine I now get an access violation.

This is exactly the same code that successfully opens VBscript, Jscript, Perlscript, and Python.

I'm not going to spend too much time on it now, unless you can point me to some place that says what I am supposed to do differently with Tcl compared to the others. The error itself seems to be in Tcom.
#11
I'm talking on the newsgroup with one of the language developers.

"Now we need to see some code, to see what is being attempted, as well as specific errors, to see where the specific problem is."
Australia Forum Administrator #12
I can understand that, that is what I would say.

I'll make up a test case tomorrow.
Australia Forum Administrator #13
I have done a minimal test file that, in a single file, demonstrates the problem. Copy between the lines and paste into (say) test.cpp and compile under Microsoft Visual C++.




#include <stdio.h>
#include <objbase.h>
#include <activscp.h>

//#define VBSCRIPT
//#define JSCRIPT
//#define PERLSCRIPT
//#define PYTHON
#define TCL

#ifdef VBSCRIPT
  #define LANGUAGE L"vbscript"
  #define TEST_CODE L"msgbox \"hello, world\", 0, \"test\" "
#endif   //  VBSCRIPT

#ifdef JSCRIPT
  #define LANGUAGE L"jscript"
  #define TEST_CODE L"WshShell = new ActiveXObject(\"WScript.Shell\");\n"  \
                    L"WshShell.Popup(\"Hello, world\", 0, \"test\");\n"
#endif  // JSCRIPT

#ifdef PERLSCRIPT
  #define LANGUAGE L"perlscript"
  #define TEST_CODE L"$WshShell = Win32::OLE->new(\"WScript.Shell\");\n"  \
                    L"$WshShell->Popup(\"Hello, world\", 0, \"test\");\n"
#endif  // PERLSCRIPT

#ifdef PYTHON
  #define LANGUAGE L"python"
  #define TEST_CODE L"import win32com.client\n" \
                    L"WshShell = win32com.client.Dispatch(\"WScript.Shell\")\n"  \
                    L"WshShell.Popup(\"Hello, world\", 0, \"test\")\n"
#endif // PYTHON

#ifdef TCL
  #define LANGUAGE L"tclscript"
  #define TEST_CODE L"a = 42\n"
#endif  // TCL

#define TRACE(arg) printf ("%s", arg)
#define TRACE1(arg, arg2) printf (arg, arg2)

class CActiveScriptSite :   public IActiveScriptSite,
                            public IActiveScriptSiteWindow {
    LONG m_cRef;
    IDispatch * m_pDispWorld;

public:
    CActiveScriptSite(IDispatch * pDispWorld) 
      : m_cRef(0), m_pDispWorld (pDispWorld)  
          { }

    ~CActiveScriptSite(void) 
      { 
      if (m_pDispWorld) 
        m_pDispWorld->Release (); 
      }

// IUnknown methods
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv) 
      {
      TRACE ("CActiveScriptSite: QueryInterface\n");

      if (riid == IID_IUnknown||riid == IID_IActiveScriptSite)
        *ppv = (IActiveScriptSite*)this;

      else if (riid == IID_IActiveScriptSiteWindow)
        *ppv = (IActiveScriptSiteWindow*)this;
      else 
        return (*ppv = 0), E_OUTOFMEMORY;

      ((IUnknown*)*ppv)->AddRef();
      return S_OK;
      }
    
    STDMETHODIMP_(ULONG) AddRef()
      { 
      TRACE ("CActiveScriptSite: AddRef\n");
      return InterlockedIncrement(&m_cRef); 
      }
    
    STDMETHODIMP_(ULONG) Release() 
      {
      TRACE ("CActiveScriptSite: Release\n");
      if (InterlockedDecrement(&m_cRef))
        return m_cRef;
      delete this;
      return 0;
      }
    
    // IActiveScriptSite methods
    STDMETHODIMP GetItemInfo(LPCOLESTR pstrName, DWORD dwReturnMask,
      IUnknown **ppiunkItem, ITypeInfo **ppti) 
      {
      HRESULT hr = E_FAIL;

      if (dwReturnMask & SCRIPTINFO_IUNKNOWN)
        *ppiunkItem = 0;

      if (dwReturnMask & SCRIPTINFO_ITYPEINFO)
        *ppti = 0;

      return hr;
      }
    
    STDMETHODIMP OnScriptError(IActiveScriptError *pscripterror);
    
    STDMETHODIMP GetLCID(LCID *plcid)
      { 
      TRACE ("CActiveScriptSite: GetLCID\n");
      *plcid = 9; 
      return S_OK; 
      }
    
    STDMETHODIMP GetDocVersionString(BSTR *pbstrVersion) 
      { 
      TRACE ("CActiveScriptSite: GetDocVersionString\n");
      *pbstrVersion = SysAllocString(L"1.0"); 
      return S_OK; 
      }
    
    STDMETHODIMP OnScriptTerminate(const VARIANT *pvr, const EXCEPINFO *pei)
      { 
      TRACE ("CActiveScriptSite: OnScriptTerminate\n");
      return S_OK; 
      }
    
    STDMETHODIMP OnStateChange(SCRIPTSTATE ssScriptState)
      { 
      TRACE1 ("CActiveScriptSite: OnStateChange: %i\n", ssScriptState);
      return S_OK; 
      }
    
    STDMETHODIMP OnEnterScript(void)
      { 
      TRACE ("CActiveScriptSite: OnEnterScript\n");
      return S_OK; 
      }
    
    STDMETHODIMP OnLeaveScript(void) 
      { 
      TRACE ("CActiveScriptSite: OnLeaveScript\n");
      return S_OK; 
      }
    
    // IActiveScriptSiteWindow methods    
    STDMETHODIMP GetWindow(HWND *phwnd)
      { 
      TRACE ("CActiveScriptSite: GetWindow\n");
      *phwnd = GetDesktopWindow(); 
      return S_OK; 
      }
    
    STDMETHODIMP EnableModeless(BOOL)
      { 
      TRACE ("CActiveScriptSite: EnableModeless\n");
      return S_OK; 
      }
};

STDMETHODIMP CActiveScriptSite::OnScriptError(IActiveScriptError *pscripterror) 
  {
  DWORD dwCookie;
  LONG nChar;
  ULONG nLine;
  BSTR bstr = 0;
  EXCEPINFO ei; 
  ZeroMemory(&ei, sizeof(ei));

  TRACE ("CActiveScriptSite: OnScriptError\n");
  
  pscripterror->GetExceptionInfo(&ei);
  pscripterror->GetSourcePosition(&dwCookie, &nLine, &nChar);
  pscripterror->GetSourceLineText(&bstr);
    
  printf ("Error occurred at line %ld character %ld\n", nLine, nChar);
  printf ("Error source: %S\n", ei.bstrSource);
  printf ("Error description: %S\n", ei.bstrDescription);
  printf ("Error help file: %S\n", ei.bstrHelpFile);
  
  SysFreeString(bstr);
  SysFreeString(ei.bstrSource);
  SysFreeString(ei.bstrDescription);
  SysFreeString(ei.bstrHelpFile);
  
  return S_OK;
  }   // end of CActiveScriptSite::OnScriptError

bool ShowError (const HRESULT hr, const char * sMsg)
  {
DWORD status = 0;
char *formattedmsg;

  if (hr == S_OK)
    return false;

  
  if (!FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM |
                    FORMAT_MESSAGE_ALLOCATE_BUFFER | 
                    FORMAT_MESSAGE_IGNORE_INSERTS,
                    NULL, 
                    hr, 
                    LANG_NEUTRAL, 
                    (LPTSTR) &formattedmsg, 
                    0, 
                    NULL))
   printf ("<<Error %08X>>\n\nWhen doing: %s\n", hr, sMsg);
 else
  {
   printf ("Error %08X occurred when doing %s:\n\n%s\n", hr, sMsg,formattedmsg);
   LocalFree (formattedmsg);
  }

  return true;

  } // end of ShowError

int main (void)
  {

  HRESULT hr;

  CLSID clsid;

  IActiveScript       * pIActiveScript = NULL;        // script interface
  IActiveScriptParse  * pIActiveScriptParse = NULL;   // parser
  CActiveScriptSite   * pSite = NULL;                 // our local site (world object)
//  IDispatch           * ppDispatch = NULL;            // script engine dispatch pointer

  // initialise COM
  hr = CoInitialize (NULL);
  if (ShowError (hr, "CoInitialize"))
    return 1;

  // find class ID of scripting language
  hr = CLSIDFromProgID(LANGUAGE, &clsid);
  if (ShowError (hr, "CLSIDFromProgID"))
    return 1;

  // create an instance of the script engine
  TRACE ("Creating instance of script engine ...\n");
  hr = ::CoCreateInstance(clsid,  
          NULL, 
          CLSCTX_ALL,    
          IID_IActiveScript,
          reinterpret_cast<void**>(&pIActiveScript));
  if (ShowError (hr, "CoCreateInstance"))
    return 1;

  // get the script parser interface
  TRACE ("Getting script engine parse interface...\n");
  hr = pIActiveScript->QueryInterface(
          IID_IActiveScriptParse, 
          reinterpret_cast<void**>(&pIActiveScriptParse));
  if (ShowError (hr, "QueryInterface: IID_IActiveScriptParse"))
    return 1;

  // initialise it
  TRACE ("Initialise parse interface...\n");
  hr = pIActiveScriptParse->InitNew ();
  if (ShowError (hr, "InitNew"))
    return 1;

  // create host site object
  TRACE ("Create host site object...\n");
  pSite = new CActiveScriptSite (NULL);   // should be dispatch pointer
  pSite->AddRef ();

  TRACE ("Set site...\n");
  hr = pIActiveScript->SetScriptSite (pSite);
  if (ShowError (hr, "SetScriptSite" ))
    return 1;

  // start script engine
  TRACE ("Start engine...\n");
  hr = pIActiveScript->SetScriptState (SCRIPTSTATE_STARTED);
  if (ShowError (hr, "SetScriptState: SCRIPTSTATE_STARTED" ))
    return 1;

  // connect it
  TRACE ("Connect engine...\n");
  hr = pIActiveScript->SetScriptState (SCRIPTSTATE_CONNECTED);
  if (ShowError (hr, "SetScriptState: SCRIPTSTATE_CONNECTED" ))
    return 1;

  // execute our test script
  TRACE ("Test script...\n");
  EXCEPINFO ei; 
  hr = pIActiveScriptParse->ParseScriptText
                               (TEST_CODE, 0, 0, 0, 0, 0, 
                               SCRIPTTEXT_ISPERSISTENT |
                                SCRIPTTEXT_ISVISIBLE,
                               NULL, 
                               &ei);
  if (ShowError (hr, "ParseScriptText" ))
    return 1;

  TRACE ("Release everything...\n");

  // release engine

  pIActiveScript->SetScriptState(SCRIPTSTATE_DISCONNECTED);
  pIActiveScript->Close ();
  pIActiveScript->Release ();

  // release parser

  pIActiveScriptParse->Release ();

  // finished with site
  pSite->Release ();

  // show we got to the end
  printf ("Completed OK\n");
  return 0;
  } // end of main




By changing the defines (line 4 on) you can test different script engines. Uncomment #define PERLSCRIPT for instance, and comment out #define TCL.

In *every* other case (you can try them) the script engine initialises, and runs, displaying a message box. Here is the example from Perlscript, also from ActiveState:


Creating instance of script engine ...
Redirecting output to win32trace remote collector
Getting script engine parse interface...
Initialise parse interface...
Create host site object...
CActiveScriptSite: AddRef
Set site...
CActiveScriptSite: AddRef
CActiveScriptSite: QueryInterface
CActiveScriptSite: QueryInterface
CActiveScriptSite: GetLCID
CActiveScriptSite: OnStateChange: 5
Start engine...
CActiveScriptSite: OnStateChange: 1
Connect engine...
CActiveScriptSite: OnStateChange: 2
CActiveScriptSite: OnStateChange: 2
Test script...
CActiveScriptSite: OnEnterScript
CActiveScriptSite: OnLeaveScript
CActiveScriptSite: OnEnterScript
CActiveScriptSite: OnLeaveScript
Release everything...
CActiveScriptSite: OnStateChange: 3
CActiveScriptSite: OnStateChange: 5
CActiveScriptSite: OnStateChange: 4
CActiveScriptSite: Release
CActiveScriptSite: Release
Completed OK


However for TCL I get this:


Creating instance of script engine ...
Getting script engine parse interface...
Initialise parse interface...
Error 8000FFFF occurred when doing InitNew:

Catastrophic failure
#14
a = 42 is not valid Tcl code. Try replacing it with 'set a 42', which is how to express it in the language. If it still fails identically, tell me.
Australia Forum Administrator #15
OK, fine, however it will still crash.

It crashes on "Initialise parse interface..." which is sequentially somewhat before where it attemps to do "Test script...".
Australia Forum Administrator #16
I tested it, and it crashes as I expected. I would expect BTW that on a syntax error I would get a nice error message, after all syntax errors would be common enough, and should not cause "catastrophic failure" or an access violation.

Meanwhile, perhaps you can interpret the "message box" idea into Tcl for me. How would I create a COM object (like in the examples in the other languages) or put up a message box?
#17
Quote:
In article <dbb6dfe6.0305132012.402608d3@posting.google.com>,
Alex <stopm@attbi.com> wrote:
>Everything after this line is Nick's words.
>
>I have done a minimal test file that, in a single file, demonstrates
>the problem. Copy between the lines and paste into (say) test.cpp and
>compile under Microsoft Visual C++.
You didn't mention what version of tcom you are using. The Tcl Active
Script engine in the tcom distribution is still in an experimental
state. The tcom-3.9b8.zip beta version from the
http://www.vex.net/~cthuang/tcom/ Web site includes an update to the
Tcl Active Script engine.
#18
Any luck with the other version?
Australia Forum Administrator #19
The test program doesn't crash, however you haven't replied to my earlier question about how to do, say, a message box, so all I can say is that it doesn't crash, not that it does anything very spectacular.

However MUSHclient still does crash, bearing in mind that the exact same code works fine for the other 4 scripting languages.

I think it is a bit obscure to have to download tcl from ActiveState and then add in bits and pieces from other sites, I doubt everyone will want to do that.

I'll take another look in a few days.
#20
Here you go:
set WshShell [tcom::ref createobject WScript.Shell]
$WshShell popup "Hello, world" 0 "test"
Amended on Fri 06 Jun 2003 05:56 PM by Metaxy
#21
I'm gonna bump this thread, because there appear to be new versions of TCom since a year ago:

http://www.vex.net/~cthuang/tcom/

I think it's long enough that mentioning it again wouldn't be that annoying ;)
Australia Forum Administrator #22
I'll take a look.
Australia Forum Administrator #23
So have you tried TCL with the latest MUSHclient? TCL is an option in the scripts drop-down menu, so you could try it and let me know how it goes.
#24
I installed TCOM and registered TclScript. When I try it with a script with just a comment, no actions (the most bug-proof test of all ;), I get

"Error -2147467263 occurred when getting script engine dispatch pointer:

Not implemented"
Australia Forum Administrator #25
Sounds like it is not fully implemented. The exact same code works for other languages, here is the part in question ...


// set state to started

  if (ShowError (m_IActiveScript->SetScriptState (SCRIPTSTATE_STARTED),
    "starting script engine"))
    return true;

// connect outbound objects (ie. world)

  if (ShowError (m_IActiveScript->SetScriptState (SCRIPTSTATE_CONNECTED),
    "connecting script engine"))
    return true;

// get script engine dispatch pointer

  if (ShowError (m_IActiveScript->GetScriptDispatch (0, &m_pDispatch),
    "getting script engine dispatch pointer"))
    return true;


It got through everything (starting, connecting) until getting the dispatch pointer (which is needed to call scripts). That was the last thing it had to do. The same code works for Perl, Python, VBscript, JScript.

Sounds like TCL is not ready yet. :)
#26
Do you get the same behavior when you test it yourself? Or did you just put it in and leave me to try it without any tests? ;)
Australia Forum Administrator #27
I left it for you. :)

I am reluctant to keep installing all these additional script engines. Do you think I'll get different results?
#28
Configuring Tcl seems to be a pain, I'm learning COM stuff and I might try it myself and tell you how if I figure it out. Feel free to take it out for now ;)

If you mean other languages, I think it's pretty reliable on average. Though there aren't that many commonly used languages that you haven't already done <g>