|
Excellent article. One problem associated with events, when using VBScript. In ActiveScriptHost.cpp, CreateEngine()function, the following line:
hr = m_pAxsScript->SetScriptState(SCRIPTSTATE_STARTED);
should be changed to:
hr = m_pAxsScript->SetScriptState(SCRIPTSTATE_CONNECTED);
For whatever reason, the "SCRIPTSTATE_STARTED" will allow events to work in JScript, but not VBScript. The "SCRIPTSTATE_CONNECTED" allows events to work in both. I have not tried tested PerlScript yet.
Ken
|
|
|
|
|
Hi,
after I had a lot of troubles adding script support to my MFC App, I thought I could post my experiences here for a stock MFC APP.
Create the MFC Stock APP
standard MFC app
- Automation support
- SDI (FormBased)
- View1 / Doc
Add ScriptSupport
add ActiveScriptHost.cpp/.h
add CmdTargetPlus.cpp/.h from the MFCAxs Sample (Knowledge Base Nr. 168214)
change BaseClass for CActiveScriptHost from CCmdTarget to CCmdTargetPlus
important, change all references in ActiveScriptHost.cpp/.h to CCmdTargetPlus
Add your ScriptClass to the App
- new Class CScriptHost via ClassWizzard
-> BaseClass is CCmdTarget
-> check Automation flag
-> after creation replace all occurences of CCmdTarget with CActiveScriptHost
- add support for OLECREATE
add to ScriptHost.h
DECLARE_OLETYPELIB(CScriptHost)
add to ScriptHost.cpp (take the tlid and version number from your .odl file)
static const GUID _tlid =
{ 0x47F2CFD3, 0xDD2E, 0x4362, { 0xA0, 0x13, 0x43, 0x88, 0xA3, 0x23, 0x64, 0xD7 } };
const WORD _wVerMajor = 1;
const WORD _wVerMinor = 0;
IMPLEMENT_OLETYPELIB(CScriptHost, _tlid, _wVerMajor, _wVerMinor)
Add Event Support
static const IID IID_IScriptHostEvents =
{ 0x12ba4982, 0x9d0b, 0x4717, { 0xb4, 0x41, 0x7c, 0x91, 0x4c, 0x70, 0x10, 0xbe } };
static const GUID CLSID_ScriptHost =
{ 0x4A3F638B, 0xCD9E, 0x4AFD, { 0xAB, 0xFD, 0xA7, 0x93, 0xef, 0x90, 0x90, 0x10 } };
CScriptHost::CScriptHost()
{
m_piidEvents = &IID_IScriptHostEvents;
m_piidPrimary = &IID_IScriptHost;
}
HRESULT CScriptHost::GetClassID(LPCLSID pclsid)
{
*pclsid = CLSID_ScriptHost;
return NOERROR;
}
BEGIN_EVENT_MAP(CScriptHost, CActiveScriptHost)
END_EVENT_MAP()
add the event interface to your .odl file
[ uuid(12BA4982-9D0B-4717-B441-7C914C7010BE) ] dispinterface IScriptHostEvents
{
properties:
methods:
};
add the eventinterface to your coclass in *.odl
[ uuid(4A3F638B-CD9E-4AFD-ABFD-A793EF909010) ]
coclass ScriptHost
{
[default] dispinterface IScriptHost;
>> [default, source] dispinterface IScriptHostEvents;
};
add the typelibrary to the resources (that was the one I forgot about)
add to the "3 TEXTINCLUDE DISCARDABLE " Section
3 TEXTINCLUDE DISCARDABLE
BEGIN
....
"#endif\r\n"
"1 TYPELIB DISCARDABLE ""MFCScriptHost.tlb""\r\n"
"\0"
END
and to the end
#ifndef APSTUDIO_INVOKED
....
#include "l.deu\afxprint.rc"
#endif
1 TYPELIB DISCARDABLE "MFCScriptHost.tlb"
#endif // not APSTUDIO_INVOKED
add the Automation Method "void Echo (LPCSTR str)" to CScriptHost via Wizzard
void CScriptHost::Echo(LPCTSTR str)
{
AfxMessageBox( str, MB_OK);
}
add member "CScriptHost m_ScriptHost" to CMainframe
new command handler CMainFrame::OnRunScript () with
void CMainFrame::OnRunScript()
{
m_ScriptHost.CreateEngine (L"JavaScript");
m_ScriptHost.AddScriptCode (L"ScriptHost.Echo (\"Hello World\");");
}
Event Handling
- add event "void Test(void)" via ClassWizzard ActiveX events
- in ActiveScriptHost.cpp (VBScript asks via SCRIPTINFO_ITYPEINFO for the ClassID)
in XActiveScriptSite::GetItemInfo change
pThis->GetDispatchIID( &clsid );
to
pThis->GetClassID( &clsid );
in CActiveScriptHost::CreateEngine comment out
in CActiveScriptHost::AddScriptCode comment in
hr = m_pAxsScript->SetScriptState(SCRIPTSTATE_CONNECTED);
HRESULT_EXCEPTION::CheckError( hr );
- in Mainframe::OnRunScript add script
m_ScriptHost.AddScriptCode (
L"function ScriptHost::Test ()\n"
L"{\n"
L" ScriptHost.Echo (\"OnTest\");"
L"};\n"
L"\n"
L"ScriptHost.Echo (\"Hello World\");");
- add command handler to CMainframe to fire an event
void CMainFrame::OnFireEvent()
{
m_ScriptHost.FireTest ();
}
now you can start the script via the ScriptRun function and if you fire the Event you get hopefully the callback into your script
Dirk
|
|
|
|
|
You probably did something else wrong, you should not need to do all that. The steps provided here are what you need to follow. You should not need that CmdTargetPlus class or are you using it because of the IScriptHostEvents interface? (just for your info: you can do the same with CCmdTarget ).
Thanks for the post...
ÿFor the bread of God is he who comes down from heaven and gives life to the world. - John 6:33
|
|
|
|
|
Hi,
you are right, I forgot to mention the words "a complete walkthrough" somewhere in the header and I forgot to mention, what I wanted to achieve.
In the first run I simply added your class, and it worked like a charm. After I added a few dispatch funcions to the ScriptProxy I tried to wait for a specific application event. In the first naive approach I did some busy waiting and polling for the flag, but recognized very fast, that (1) my App is blocked during execution of the script and (2) the WScript object is not available to call WScript.Sleep().
After some digging in the internet, I started to add event support to my ScriptProxy and followed the references you give on this page. One way led to the MFCAxs sample from Microsoft and I introduced the CCmdTargetPlus class as the base class for your ActiveScriptHost. In the end I'm now able to send application events to the script but I had a few troubles in the middle, that I wanted to share here. This troubles were exactly:
1.) I missed one replacmenet of CCmdTarget to CCmdTargetPlus in your ActiveScriptHost.cpp file
2.) I didn't know how to add events to my CScriptProxy with the class wizzard. Therefor I copied this EVENT_MAP stuff into the header and implementation file
3.) I missed, and this was the important one, to add the typelibrary to the executable via the resources. So the lookup of my CScriptHosts coclass ClassID always failed.
4.) I needed to modify your CActiveScriptHost::GetItemInfo function to return the TypeInfo of the coclass object and not the one from the dispatch interface for the root object. (needed this only for the VBScript engine, since the JavaScript does not ask for the TypeInfo via this way)
5.) From some obscure reasons my script did not respond to events if I do the transition to the SCRIPTSTATE_STARTED state in the CreateEngine function. I had to set the SCRIPTSTATE_CONNECTED state in the AddScriptCode section.
In the end I have event support in the CScriptHost class and can call from my mainframe:
m_ScriptHost->OnProcessingReady ();
and in the script:
Sub ScriptHost_OnProcessingReady ()
ScriptHost.Echo "Ready"
End Sub
I you have any ideas how to improve all this steps (you said I can do this via the CCmdTarget object itself) I would like to hear your ideas. If you want I can send you my sample project, where I tried all those modifications.
BTW.: how did you generate these CCommandButton, Richedit and WebBrowser IDispatch wrapper classes?
Dirk
|
|
|
|
|
Thanks Luedi, I will try to find the time to update the article and show how it can be done but it's good to read you find ways to overcome your difficulties...good work!
To generate these wrappers classes, go to:
Project->Add To Project->Components and Controls...[Registered ActiveX Controls]
select: Microsoft Forms 2.0 CommandButton, Microsoft Rich Text Control, version 6.0, Microsoft Web Browser
ÿFor the bread of God is he who comes down from heaven and gives life to the world. - John 6:33
|
|
|
|
|
Hi
good Article.I have problems implementing events in amy app.Here i create COM objects for displying buttons in a window.There can be more than one object(button).In my IDL file i created button class from IDispatch and incuded events too.
Also i will be creating objects dynamically.
My question is when a button is clicked how will i know which button is clicked.
because in VBScript we have button_OnClick() procedure.Pls help..i am struggling.Also i am new to COM.Pls help..
Thnks
Ram
|
|
|
|
|
luedi wrote:
2.) I didn't know how to add events to my CScriptProxy with the class wizzard. Therefor I copied this EVENT_MAP stuff into the header and implementation file
Did you ever figure out how to add events to the CScriptProxy via the ClassWizard? I'm staring at the code trying to figure out what he's done different from me, and just can't figure it out. The only way I've been able to figure out the "Add Events" is by creating an ActiveX Classwizard project...Which obviously doesn't have to be done.
Thanks for any help!
|
|
|
|
|
To add event in MFC project:
VC6
1. Start ClassWizard (Ctrl+W)
2. Go to "ActiveX Events" tab
VC7 (.NET)
1. Go to "ClassView"
2. Search for your "IHostEvent" interface, right-click and select "Add Method"
ÿFor the bread of God is he who comes down from heaven and gives life to the world. - John 6:33
|
|
|
|
|
Thanks for the reply Ernest.. I should have been a little more specific. I am able to add events in the given code (for the CHost_Proxy class..so I can add to the ones already there), but in my own projects, or if I were to create a new class in the MFCScriptHost project, the "Add Event" button is greyed out. The auotmation tab is fine (Add Method and Add Property) can be selected. I'm using VC6.
I would have thought declaring the class base as CCmdTarget is what allows the "Add Event" button to be eneabled for that class, but for whatever reason I just can't get that Add Event button to be accessable.
|
|
|
|
|
Hello Randy:
Try adding the following in your object.h
And put this in your object.cpp
BEGIN_EVENT_MAP(CYourObject, CCmdTarget)
END_EVENT_MAP()
ÿFor the bread of God is he who comes down from heaven and gives life to the world. - John 6:33
|
|
|
|
|
Thanks again, that did it. I just want to add for anybody else who may run into this problem, you need to add DECLARE_EVENT_MAP() after the //}}AFX_EVENT... So it will look like this in the header file:
//{{AFX_EVENT(CYourObject)
//}}AFX_EVENT
DECLARE_EVENT_MAP()
and
BEGIN_EVENT_MAP(CYourObject, CCmdTarget)
//{{AFX_EVENT_MAP(CYourObject)
//}}AFX_EVENT_MAPEND_EVENT_MAP()
In the CPP file.
Why MFC wouldn't automatically put those lines in is beyond me, but I don't pretend to understand it=).
|
|
|
|
|
switch to VS .NET!
|
|
|
|
|
Ernest Laurentin wrote:
switch to VS .NET!
Would love to=)
I managed to get my MFC issues resolved.. That's half the battle. It seems that when I create my own project, I can get everything to compile, but nothing happens when I click my fireOnRun button. I have managed to trace it down to the call to m_Host.CreateEngine. I am getting no error messages, but the call to
// If this happens, the scripting engine is probably not properly registered
hr = CoCreateInstance(clsid, NULL, CLSCTX_ALL, IID_IActiveScript, (void **)&m_pAxsScript);
HRESULT_EXCEPTION::CheckError( hr ); // will throw an exception if failed
is failing.. I know this because I was throwing in AfxMessageBox("Made It"); all over the place..just to see what was succeeding and what wasn't. I put the message box right before the CoCreateInstance, it showed up. I put it directly after the "HRESULT_EXCEPTION"... no message box. So obviously when the program gets to this point, it fails.
For the sake of trouble, I decided to copy EVERYTHING the same between the working project and my new one (The only alteration being to rename CMfcScriptHostDlg with my own app name).. Same problem using the identical code.
After many hours of pure frustration and re-creating the project over and over, I decided to create the project with the same name as your original..again, same problem. Only after deleting the actual MfcScriptHostDlg.h and replacing it with your own did it work... This is the most insane problem I have ever encountered.
At any rate, what do you suppose would cause a failure in the call:
hr = CoCreateInstance(clsid, NULL, CLSCTX_ALL, IID_IActiveScript, (void **)&m_pAxsScript);
HRESULT_EXCEPTION::CheckError( hr ); // will throw an exception if failed
I'm not seeing how this has anything to do with the code in the other classes? Hense my confusion as to why copying/pasting the code directly would fix the problem ONLY after I created a new project with the same name.. CreateEngine(L"JavaScript") is being called the same way.
I'm sure there are others out there who have encountered this (if when you click the button it does nothing, try what I did to see if that's where the scripting is failing). Would appreciate any help as always=)
|
|
|
|
|
Scratch that last comment(Wish I could delete it).. For anybody who is having trouble with the fireEvent, you need to add:
CoInitialize(NULL); to your <projectname.cpp> file. Another thing MFC doesn't do for unknown reasons!...
Just add CoInitialize(NULL); above the AfxEnableControlContainer();
Yet another programming problem that I have spent over 12 hours on for 1 freak'n line of code!
|
|
|
|
|
I was about to post just that! I'm glad you figured it out!
MFC cannot initialize COM for you.
Good luck!
ÿFor the bread of God is he who comes down from heaven and gives life to the world. - John 6:33
|
|
|
|
|
Hi,
as a complete beginner to the scripting technology I asked myself how to get access to the "WScript" object from your provided scripting host. The msdn says about this object:
"It never needs to be instantiated before invoking its properties and methods, and it is always available from any script file"
Thanks for enlightenment.
Dirk
|
|
|
|
|
Hi,
although this article is quite old I just have the need to add the scripting support to an existing app (MSVC 6).
Is there any way to get access to this standard-functions of the winows scripting host? For example WScript.Sleep, etc.?
Thanks in advance!
Martin
|
|
|
|
|
Use CreateActiveX to create the WScript, the call Sleep from the dispatch handle.
MSVC6? ouch!
|
|
|
|
|
First off, thanks for the article - it's really whetted my apetite to look more into scripting! Quick question though...
I converted your sample to use VBScript instead of JavaScript, then tried the simple script :
MsgBox "Hello"
This causes a "Permission denied" error - as does calling InputBox . Other functions, such as Abs seem to work without throwing an error. I tried this in the MFCAxs sample, and it worked fine. Any idea where the difference lies? I guess as both MsgBox and InputBox fail, it is something to do with allowing the script to have "visual content".
Thanks for your help, and thanks once again for the article!
|
|
|
|
|
Thanks Martyn,
MsgBox and InputBox are not supported directly by this demo but their implementation is straightforward. In fact, MsgBox will do the same as Display (that is already supported). You are right about displaying visual content but this is more a feature with the activex version (msscript.ocx).
I am glad it was useful to you, take a look at ATL version, it is easier to use...
ÿFor the bread of God is he who comes down from heaven and gives life to the world. - John 6:33
|
|
|
|
|
How can I disable MsgBoxes from being displayed for my objects?
The only way I can think of doing this is to hook the QI for IActiveScriptSiteWindow. If you return S_OK as well as a ptr to that interface, the MsgBox function will work. If you return S_FALSE and no
interface ptr then MsgBox will not work. One thing to be aware of is the scripting engine may hold on to the QI for IActiveScriptSiteWindow during the duration of the script execution so you may not be able to dynamically change the behavior at runtime (which I think would be bad practice anyway
to return different results for the same QI on the same object instance).
Source: Ryan Jansen, 3/15/2000, microsoft.public.scripting.hosting
--------------------------------------
e-mail: billhao@hotmail.com
MSN Messenger: billhao@hotmail.com
http://www.minivoice.com
|
|
|
|
|
I am currently working on a application that supports a script language. This language has had numerous modifications and does not suffice anymore. I started to look around for a generic solution and after looking at TCL first I found about Active Script Hosting. I now want to implement my own Active Script Host but do have some questions about the supported languages and possibilities.
The application I'm working on has to be able to send data via several protocols, so I have to be able to easily assign (byte) buffers to my own objects e.g.:
Define cmd : Command
cmd.buffer = 00, 01, 02, 03, 04, 05, ...
Is it possible to do a similar thing in one of the support languages? Is It also possible to include other files into a vbscript/jscript file?
|
|
|
|
|
When I try to use VBScript instead of javascript, I get an error (Unhandled exception in ... Access Violation) originating from
m_ScriptProxy.AddScriptItem( L"btnGo", pUnkControl);
Should it work with VBScript?, or what else could be wrong?
GNOR
|
|
|
|
|
You are right, the fix is simple though.
In XActiveScriptSite::GetItemInfo , add statement before AddRef:
if (*ppti)
(*ppti)->AddRef();
Other than that, in OnInitDialog , you need to replace
m_ScriptProxy.CreateEngine( L"VBScript" );
ÿVOTD: 8 "For it is by grace you have been saved, through faith-and this not from yourselves, it is the gift of God- 9 not by works, so that no one can boast." - Eph 2:8-9
|
|
|
|
|
|