Register to post in forums, or Log in to your existing account
 

This forum is locked: you cannot post, reply to, or edit topics.  This topic is locked: you cannot edit posts or make replies.     Home » Forums » General zApp Discussion
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Fri Aug 20, 2004 11:05 pm   

URGENT: Need advice on how to implement Zeus scripting
 
OK, I need some help now. After hitting the brick wall with the Microsoft Scripting Engine stuff, it looks like I need to rethink how scripts are handled in Zeus.

So, I'm looking for opinions from people. Here is the situation I want you to think about:

First, here is the Zeus ZML code for a particular window:
Code:

<WINDOW Name='Compose'>
  <BUTTON Name='Send' Action='SendMessage'/>
  <EDIT Name='MsgTo'/>
  <EDIT Name='MsgSubject'/>
  <MEMO Name='MsgBody'/>
</WINDOW>

Obviously this isn't everything, but it gives the basic idea for a window for composing email messages. The key bit is the Send button which calls the "SendMessage" action. So, we need an action definition:
Code:

<ACTION Name='SendMessage'>
  Core.SendMail( MsgTo.Text, MsgSubject.Text, MsgBody.Text)
</ACTION>


So, here's the tricky question. Imagine that you have 2 Compose windows on the screen (Compose_1 and Compose_2). The Send button on each window calls the global SendMessage action. But when the SendMessage action is executed, it refers to the fields "MsgTo", "MsgSubject", "MsgBody". But how does it know which window to look at? In other words, when you click the Send button in the window Compose_2, how would it know to get the "MsgTo" object from Compose_2 rather than Compose_1?

This is the problem that I currently stuck on with Zeus/eMobius. I'm going to go and think about this more myself, but if you have an idea how to implement this, let me know.

As far as VBScript is concern, the "MsgTo.Text" reference is a reference to the "Text" property of the "MsgTo" object. And apparently in VBScript, once the MsgTo object is defined, it can never be overwritten again. So somehow, the MsgTo object needs to dynamically link to either the object on the Compose_1 or the object on the Compose_2 form.

Ack, what a mess. I'm not really expecting enough people to understand this problem well enough to really help, but I hoped that my writing out the details of the current problem that I'd think of something, or that a suggestion from someone might spark an idea. Right now I'm completely stumped.

The only thing I've been able to think of is to bypass COM object entirely and use a global routine called "GetObject" or something like that. Then I could do:

core.GetObject("MsgTo").Text

and the GetObject routine would look up the current object with the name "MsgTo" and return it. I think this will easily work, but it makes the scripting syntax in Zeus really awful. Also, this is slow since it would have to look up controls via their name in order to access them each time. I'd much rather than access an object directly.

I can't believe that this entire mess is just due to the fact the Microsoft caches their object references and doesn't provide any way to clear the cache. What a pain!
Reply with quote
slicertool
Magician


Joined: 09 Oct 2003
Posts: 459
Location: USA

PostPosted: Fri Aug 20, 2004 11:30 pm   
 
Shouldn't there be a way for the button object to tell SendMessage where it came from in order to define the scope into that particular form? Either that or SendMessage to pull whatever the active window is in order to find which window to look at.

I've never played much with MDI, but this seems it would be something where the theory/concept behind it is probably laid out somewhere on the web.
_________________
Ichthus on SWmud: http://www.swmud.org/
Reply with quote
Rainchild
Wizard


Joined: 10 Oct 2000
Posts: 1551
Location: Australia

PostPosted: Sat Aug 21, 2004 3:16 am   
 
Look at it in an object oriented perspective... if you define a class (sorry, C++):

Code:
class Foo
{
  void MyFunc( );
  TEdit MyEditBox;
};

void Foo :: MyFunc( )
{
  MessageBox( MyEditBox.Text );
}

Foo Foo_1;
Foo Foo_2;

Foo_1.MyEditBox.Text = "This is Foo 1";
Foo_2.MyEditBox.Text = "This is Foo 2";


You only have the one function but it is called with a 'this' scope which is checked first.

How you can get it working inside the MS Script control might have to be a bit of a hack, because I'm guessing that it won't have a clue how to do a 'this' pointer... and it may be hard to automatically check the 'this' scope before checking the global scope. Maybe you already have scopes defined with the whole 'jscript' and 'vbscript' pointers, so you might be able to set up class/object/window scopes: 'compose_1' and 'compose_2' then when executing the script look for variables inside 'compose_1' or 'compose_2' whichever was the sender.

As a side note, it might not hurt to use a GUID as part of the names for objects if you don't already do so, eg 'compose_83f28192-bc23-1c82-8fe1-4587cd1af814'.

If you can't hack the script engine to support scope like that, maybe a script engine per window is the only way to go?
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Sat Aug 21, 2004 5:51 am   
 
Slicertool: using the "current window" would be the same as my core.GetObject example. You can already do
Code:

CurrentWindow.Controls("MsgSubject").Text

for example. That doesn't really help. Because each window can have different controls with different control names, there is no way to access the MsgSubject control without looking it up by name.

Rainchild: I think that's what I've got already. Every Zeus component is already a custom class. I don't see in your example how the Send button ends up figuring out which "foo" to deal with. And yes, internally each object still has a unique name. I don't use GUIDs but it still generates unique names.

And you can't have a separate scripting engine per window either. Otherwise there is no way to have global actions, which are kind of a central idea in Zeus.

As an update, I found a 3rd party component that implements an active scripting host like I was doing. They have a way to handle "user defined variables" that seem to get queried each time they are used. The only problem is that it also seems to reset the script each time also, so I'm not sure they really got around the problem either. But I've emailed them to see what they have learned and what kind of support they provide.

The nice thing about this particular component is that it builds upon stuff that I was already doing, but has it all done already. Also, they have implemented a Delphi Pascal scripting language within the component as well. This engine doesn't depend upon the MS Scripting Control. They also implemented an active script debugger, which would save me some time. So, it seems like these guys might have more experience with this than I do and maybe they know a trick.
Reply with quote
Vijilante
SubAdmin


Joined: 18 Nov 2001
Posts: 5182

PostPosted: Sat Aug 21, 2004 12:21 pm   
 
I think you need to extend the language to provide parameter passing. Just a rough thought would be:
<WINDOW Name='Compose'>
<BUTTON Name='Send' Action='SendMessage(Window.Name)'/>
<EDIT Name='MsgTo'/>
<EDIT Name='MsgSubject'/>
<MEMO Name='MsgBody'/>
</WINDOW>
<ACTION Name='SendMessage(string:WinName)'>
Core.SendMail( WinName.MsgTo.Text, WinName.MsgSubject.Text, WinName.MsgBody.Text)
</ACTION>
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Sat Aug 21, 2004 4:42 pm   
 
Vijilante: No, that doesn't work. If WinName is a string, the syntax:
WinName.MsgTo.Text
is not valid in vbscript. The only way this is valid is if WinName is a COM object. And then we are back to the same problem of making MsgTo a property of that COM object, etc, etc.

But, I'll give KUDOS on this one to SlicerTool! The Microsoft Blog site that you referenced in the other post put this on the right track. The Microsoft developer actually returned my email this morning and pointed me in the right direction for a "trick".

So, here's the tricky solution (warning: low level COM stuff ahead!)

First, he acknowledged the problem at hand and admitted there was no easy solution. But he outlined a trick. The scripting engine resolves names in several ways. When it sees an identifier name, first it looks in it's name table (from IActiveScript.AddNamedItem) to see if it's an item added by the scripting host. If so, then it checks to see if the value is cached, and if not, it calls GetItemInfo in the scripting host to resolve the name, and then adds it to it's cache. This is what we've been working with so far, and it's this cache that is causing the problem.

But using AddNamedItem, there is a flag called GLOBALMEMBERS. This flag tells the scripting engine that you want all of the MEMBERS of an object to be available at a global level. This is used to extend the functions of the scripting language using your own library of functions. For example, if you have a COM object called "StrLib" and it has a method called "concat", then normally you would have to access this function via VBscript using the syntax StrLib.concat. But if you add the StrLib object using the GLOBALMEMBERS flag, then you can access concat via VBscript simply using "concat" rather than "StrLib.concat".

So, after checking the name table, it then checks each name that was added with the GLOBALMEMBERS flag to see if the identifier is a property or method of that object.

Now, normally COM objects are compiled with fixed interfaces. In the above example, you create the StrLib COM object at design time, add the method "concat" to it's interface and let whatever compiler you are using make this into a COM interface. If you look at how COM objects implement their interfaces, you'll learn that there are two important low-level methods that must be implemented for each COM object: GetIDsOfNames and Invoke.

The GetIDsOfNames method takes the string name of a method or property and returns a unique numeric ID for that name, called a "dispid". The Invoke method of a COM object takes a DispID and "invokes" it...that is, if it's a method, call the method. If it's a property, then either Get or Set the property.

Now, if you look at the code generated by your compiler (Delphi in this case) when a COM object is created, you'll see that it implements these two methods for your COM object based upon the interface you defined for the object.

Buy who says these routines need to be static?

The trick is to create our own "dynamic" COM object. In our COM object, we implement GetIDsOfNames and Invoke ourself. In GetIDsOfNames, we look up the string name passed to us to see if it's a Zeus object name. If it is found, we return a unique ID for this name.

Now, in Invoke, we take the DispID unique ID and find out what Zeus object this corresponds to. Then we return the CURRENT VALUE of this object as the result of Invoke.

We create a global COM object using this "dynamic" implementation, and we add this global object to the scripting engine using the GLOBALMEMBERS flag. It doesn't matter what the name of this object is...like the "StrLib" example above, we never refer to it directly in the script.

So, essentially, in the script we are doing something like this:

GlobalObject.MsgTo.Text = 'whatever'

and the GlobalObject is our dynamic COM object. When it is asked to resolve the "MsgTo" property, it looks this up in the Zeus object table and returns an DispID. Then Invoke uses this DispID to return the current value of the Zeus MsgTo object.

Since GlobalObject is added to the script with GLOBALMEMBERS, we do not have to use the "GlobalObject" part...so we just do:

MsgTo.Text = 'whatever'

in our script and now it all works!!!

So, we achieved the objective of simple syntax. Behind the scenes it is generating a dynamic COM object and doing a lot of work to resolve this syntax. With stuff like hash tables, I should be able to get good performance out of this.

Thanks for listening to all of this, and thanks for trying to make suggestions. I knew it was a lot to ask since this is all very complicated. Adding parameter passing and stuff like that, as I mentioned previously, was already do-able, but ended up with extra scripting syntax that I did not want. I wanted to try and keep this really simple and hide the complexity from the scripting user.

Using this same "dynamic" COM object method, I should also be able to go ahead and create dynamic COM objects for each window in Zeus. So, instead of saying:

WindowName.Controls("controlname")

like you can do on the current version, I can make "WindowName" a dynamic COM object and let you just do:

WindowName.controlname

like you'd expect to work. This will let you access specific windows when you need to do that.

Well, I'm glad that issue got resolved. I was really getting worried about all of this. Without the simplicity that I had envisioned, Zeus could have easily become a very complex language, and that defeats the purpose of end-user modification. Now I can finish implementing all of this and get back to the fun stuff.
Reply with quote
The Raven
Magician


Joined: 13 Oct 2000
Posts: 463

PostPosted: Wed Sep 08, 2004 7:35 pm   Scope is the answer
 
You need to have a clear way of defining scope, and references need to be (by default) limited in scope. You have too many global routines, global names, with the SAME name. You could have 3 compose windows open, and currently the way you are programming they are all global, so the control on each compose window has no idea WHICH compose window it is a member of.

You need to limit scope. I think the easiest way would be to limit scope by window... references should always refer to the current child window, then the main application window, and then global objects. So a 'Send' button would automatically refer to the MsgBox on the current window.

Unfortunately, you'll need to handle this scope yourself... I don't think you'll be able to rely on the scripting engine to do it for you. And I'm sure this will bring up tons of other problems. But you can't program applications the size and complexity you are planning without SOME kind of limitations on variable and object scope.
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Wed Sep 08, 2004 10:32 pm   
 
Sorry, I should have posted the resolution to this thread. The problem was explained and solved in another thread, but I don't remember which one.

But basically, yes, I had to write my own COM routines and keep track of scope myself. All of this all works just fine now, so there is no longer any problem.
Reply with quote
Display posts from previous:   
This forum is locked: you cannot post, reply to, or edit topics.   This topic is locked: you cannot edit posts or make replies.     Home » Forums » General zApp Discussion All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
© 2009 Zugg Software. Hosted on Wolfpaw.net