- 浏览: 151233 次
- 性别:
- 来自: 北京
文章分类
最新评论
标准C编写COM(八)COM in plain C,Part8
原文:http://www.codeproject.com/Articles/17038/COM-in-plain-C-part-8
内容
- 简介
- 脚本代码持久化
- 脚本代码和“命名项”
- 调用脚本中的特定函数
- 查询/设置脚本中变量的值
- 查询/设置脚本中变量的值
简介
在前面的章节中,我们学会了如何创建Activex脚本宿主。虽然这些章节覆盖了编写一个脚本宿主的的大部分方面,但是,这里还有其它一些 你的脚本宿主也许想要使用的 更esoteric(深奥,机密)的特性。本章节将会详细的介绍其中一些机密特性。
脚本代码持久化
在先前的脚本宿主示例中,我们用runScript
函数打开脚本引擎,运行脚本,然后关闭引擎。这个函数中,脚本引擎被加载/分析,执行,然后释放(执行完成之后)。
但是,也许有时候,你希望添加一些脚本到引擎,并且让它们一直驻留在里面,即使这些脚本没有被执行。可能,你希望这些脚本能够被任何 能够被相同引擎的IActiveScript识别的 其它脚本所调用。事实上,或许你还想把这些脚本作为"ram-based macros"集合。ActiveX脚本引擎让这成为可能。但我们还需要从以下两方面改变我们的方法:
- 当我们把脚本作为“宏”添加的时候,我们需要为
ParseScriptText
函数指定SCRIPTTEXT_ISPERSISTENT标志。这告诉引擎保留内部已分析\加载过的脚本,即使在ParseScriptText返回之后。 - 我们不能在所有宏被使用之前释放引擎的IActiveScript对象。如果这样做,那些宏最终会被卸载掉。
最好的做法是 在引擎处于INITIALIZED状态时 添加这些宏,但要在引擎被设置为STARTED或CONNECTED状态之前。ParseScriptText
不会尝试运行这些脚本,而是对其语法分析,并在ParseScriptText
返回的时候,在内部把这些脚本保存起来。这些脚本会驻留在引擎中,直到我们释放了引擎的IActiveScript对象,即使在这中间,我们调用了其它脚本或者方法。
在ScriptHost7目录中,你会找到一个说明这一点的例子。我们添加一个VB脚本给引擎,并指定 SCRIPTTEXT_ISPERSISTENT 标志。为了简单,这个VB脚本被作为全局变量,像下面这样嵌入在我们的EXE中:
- wchar_tVBmacro[]=L"SubHelloWorld\r\nMsgBox\"Helloworld\"\r\nEndSub";
wchar_t VBmacro[] = L"Sub HelloWorld\r\nMsgBox \"Hello world\"\r\nEnd Sub";
上面的代码是一个VB的“Hello World”子程序。它只是的弹出一个消息框。
接下来,我们嵌入第二段VB脚本。这个VB脚本只是调用第一个加载的HelloWorld脚本程序。
- wchar_tVBscript[]=L"HelloWorld";
wchar_t VBscript[] = L"HelloWorld";
当载入第二段脚本时,我们不指定SCRIPTTEXT_ISPERSISTENT标志。
为了确保持久化的脚本能够工作,需要让runScript线程始终保持VB引擎的IActiveScript对象,直到程序终止。为了完成这一点,我们在程序开始的时候就启动线程(而不是在运行脚本时才启动线程)。这个线程一直保持有效,直到主程序结束。开始,引擎调用CoCreateInstance来获取VB引擎的 IActiveScript,然后调用 IActiveScript的QueryInterface 得到引擎的 IActiveScriptParse对象,再调用InitNew初始化引擎,最后调用SetScriptSite把我们的 IActiceScriptSite传给引擎。初始化部分跟我们前几章的示例一样。
runScript将会调用ParseScriptText
来加载我们的“VB 宏”到引擎中。这几乎和示例程序完全一样,除开指定了 SCRIPTTEXT_ISPERSISTENT 标志:
- activeScriptParse->lpVtbl->ParseScriptText(activeScriptParse,&VBmacro[0],
- 0,0,0,0,0,SCRIPTTEXT_ISPERSISTENT,0,0);
activeScriptParse->lpVtbl->ParseScriptText(activeScriptParse, &VBmacro[0], 0, 0, 0, 0, 0, SCRIPTTEXT_ISPERSISTENT, 0, 0);
添加这个脚本之后,runScript 线程就等待,直到主线程 设置我们创建的一个事件信号量 唤醒,然后运行第二段脚本(它会调用刚才我们装载的第一段脚本)。
主窗口有一个“Run Script”按钮。当用户点击时,主线程就设置事件信号量。
- //Letthescriptthreadknowthatwewantittorunascript
- SetEvent(NotifySignal[0]);
// Let the script thread know that we want it to run a script SetEvent(NotifySignal[0]);
运行线程被唤醒后就调用ParseScriptText
函数加载第二个脚本,然后通过调用SetScriptState
函数,设置VB脚本引擎的状态为SCRIPTSTATE_CONNECTED。 这导致第二段脚本运行, 第二段脚本调用 宏 中的 “Hello World”子程序 弹出一个消息框。当用户关闭消息框之后,第一段脚本运行结束,SetScriptState
函数返回。
此时,我们还没有释放(Rlease) IActiveScriptParse和IActiveScript对象,也没有关闭引擎。我们调用SetScriptState
把状态设置为SCRIPTSTATE_INITIALIZED。这样第二段脚本就被卸载,但宏脚本不会被卸载,因为它是持久化的。
运行线程又重新休眠了。等待用户再次点击“Run Script”按钮。在这个事件中,运行线程重复 加载/运行 第二段脚本的过程。但注意我们不需要重新加载宏脚本,它一直被保留在引擎中。
这就是运行线程中的“脚本循环”:
- for(;;)
- {
- //Waitformainthreadtosignalustorunascript.
- WaitForSingleObject(NotifySignal,INFINITE);
- //Havethescriptengineparseoursecondscriptandadditto
- //theinternallistofscriptstorun.NOTE:WedoNOTspecify
- //SCRIPTTEXT_ISPERSISTENTsothisscriptwillbeunloaded
- //whentheenginegoesbacktoINITIALIZEDstate.
- activeScriptParse->lpVtbl->ParseScriptText(activeScriptParse,
- &VBscript[0],0,0,0,0,0,0,0,0);
- //Runallofthescriptsthatweaddedtotheengine.
- EngineActiveScript->lpVtbl->SetScriptState(EngineActiveScript,
- SCRIPTSTATE_CONNECTED);
- //TheabovescripthasendedafterSetScriptStatereturns.Now
- //let'ssettheenginestatebacktoinitializedtounloadthis
- //script.VBmacro[]remainsstillloaded.
- EngineActiveScript->lpVtbl->SetScriptState(EngineActiveScript,
- SCRIPTSTATE_INITIALIZED);
- }
for (;;) { // Wait for main thread to signal us to run a script. WaitForSingleObject(NotifySignal, INFINITE); // Have the script engine parse our second script and add it to // the internal list of scripts to run. NOTE: We do NOT specify // SCRIPTTEXT_ISPERSISTENT so this script will be unloaded // when the engine goes back to INITIALIZED state. activeScriptParse->lpVtbl->ParseScriptText(activeScriptParse, &VBscript[0], 0, 0, 0, 0, 0, 0, 0, 0); // Run all of the scripts that we added to the engine. EngineActiveScript->lpVtbl->SetScriptState(EngineActiveScript, SCRIPTSTATE_CONNECTED); // The above script has ended after SetScriptState returns. Now // let's set the engine state back to initialized to unload this // script. VBmacro[] remains still loaded. EngineActiveScript->lpVtbl->SetScriptState(EngineActiveScript, SCRIPTSTATE_INITIALIZED); }
脚本代码和“命名项”
在上面的例子中,我们添加宏脚本给同样的“命名项”作为第二段脚本。(注意:我们没指定特定的命名项,所以引擎用它的缺省的全局项)。但在不同的“命名项”下加载脚本是可以的。
在前面的章节中,你应该记得我们可以 通过创建一个命名项(通过引擎IActiveScript对象的的AddNamedItem函数) 让脚本能够调用我们自己的的C函数。
但这不是命名项的唯一用处。我们还可以把创建的命名项分组,这就是我们现在的示例:
假设我们有2个c源文件分别为File1.c 和 File2.c。下面是 File1.c的内容:
- //File1.c
- staticvoidMyFunction(void)
- {
- printf("File1.c");
- }
- staticvoidFile1(void)
- {
- MyFunction();
- }
// File1.c static void MyFunction(void) { printf("File1.c"); } static void File1(void) { MyFunction(); }这是 File2.c 的内容:
- //File2.c
- staticvoidMyFunction(constchar*ptr)
- {
- printf(ptr);
- }
- staticvoidFile2(void)
- {
- MyFunction("File1.c");
- }
// File2.c static void MyFunction(const char *ptr) { printf(ptr); } static void File2(void) { MyFunction("File1.c"); }
还有一些东西需需要注意:
- 由于static关键字修饰,File1.c中的
MyFunction
和File2.c中的MyFunction
就不一样了。我们可以把两个源文件在一起编译和连接不会有问题(也就是说,不会有命名冲突)。 - 由于static关键字修饰,File1.c中的函数不能调用File2.c中的函数,反之亦然。
当我们创建一个命名项时(在我们要加载的脚本代码中),把它看成创建c源文件。为了创建一个命名项,我们调用引擎IActiveScipt的AddNamedItem
。假设我们有一个C语言实现的脚本引擎。首先,我们需要调用AddNamedItem两次,第一次,我们创建以File1.c作为名字的命名项;第二次我们创建以File2.c作为名字的命名项。这就在引擎中创建了2个“源文件”,然后我们将调用ParseScriptText把File1.c的内容加载给
File1.c命名项。为此,我们必须把命名项的名字作为第三个参数传给ParseScriptText。然后,我们把File2.c的内容加载给File2.c命名项。以下是我们的具体做法:
- //Here'sthecontentsofFile1.c
- wchar_tFile1[]=L"staticvoidMyFunction(void)
- {
- printf(\"File1.c\");
- }
- staticvoidFile1(void)
- {
- MyFunction();
- }";
- //Here'sthecontentsofFile2.c
- wchar_tFile1[]=L"staticvoidMyFunction(constchar*ptr)
- {
- printf(ptr);
- }
- staticvoidFile2(void)
- {
- MyFunction(\"File1.c\");
- }";
- //CreatetheFile1.cnameditem.Error-checkingomitted!
- EngineActiveScript->lpVtbl->AddNamedItem(EngineActiveScript,"File1.c",0);
- //CreatetheFile2.cnameditem.
- EngineActiveScript->lpVtbl->AddNamedItem(EngineActiveScript,"File2.c",0);
- //AddtheFile1.ccontentstotheFile1.cnamedobject
- activeScriptParse->lpVtbl->ParseScriptText(activeScriptParse,&File1[0],
- "File1.c",0,0,0,0,0,0,0);
- //AddtheFile2.ccontentstotheFile2.cnamedobject
- activeScriptParse->lpVtbl->ParseScriptText(activeScriptParse,&File2[0],
- "File2.c",0,0,0,0,0,0,0);
- 现在我们把VB“宏脚本”放到创建的命名项中。随便给这个项取名为:MyMacro。我们的具体做法如下:
- //Thenameofthenameditem
- wchar_tMyMacroObjectName[]=L"MyMacro";
- //CreatetheMyMacronameditem
- EngineActiveScript->lpVtbl->AddNamedItem(EngineActiveScript,&MyMacroObjectName[0],SCRIPTITEM_ISVISIBLE|SCRIPTITEM_ISPERSISTENT);
- //AddthecontentsofVBmacrototheMyMacronameditem
- activeScriptParse->lpVtbl->ParseScriptText(activeScriptParse,&VBmacro[0],
- &MyMacroObjectName[0],0,0,0,0,SCRIPTITEM_ISVISIBLE|SCRIPTTEXT_ISPERSISTENT,
- 0,0);
// Here's the contents of File1.c wchar_t File1[] = L"static void MyFunction(void) { printf(\"File1.c\"); } static void File1(void) { MyFunction(); }"; // Here's the contents of File2.c wchar_t File1[] = L"static void MyFunction(const char *ptr) { printf(ptr); } static void File2(void) { MyFunction(\"File1.c\"); }"; // Create the File1.c named item. Error-checking omitted! EngineActiveScript->lpVtbl->AddNamedItem(EngineActiveScript, "File1.c", 0); // Create the File2.c named item. EngineActiveScript->lpVtbl->AddNamedItem(EngineActiveScript, "File2.c", 0); // Add the File1.c contents to the File1.c named object activeScriptParse->lpVtbl->ParseScriptText(activeScriptParse, &File1[0], "File1.c", 0, 0, 0, 0, 0, 0, 0); // Add the File2.c contents to the File2.c named object activeScriptParse->lpVtbl->ParseScriptText(activeScriptParse, &File2[0], "File2.c", 0, 0, 0, 0, 0, 0, 0); 现在我们把VB“宏脚本”放到创建的命名项中。随便给这个项取名为:MyMacro。我们的具体做法如下: // The name of the named item wchar_t MyMacroObjectName[] = L"MyMacro"; // Create the MyMacro named item EngineActiveScript->lpVtbl->AddNamedItem(EngineActiveScript, &MyMacroObjectName[0], SCRIPTITEM_ISVISIBLE|SCRIPTITEM_ISPERSISTENT); // Add the contents of VBmacro to the MyMacro named item activeScriptParse->lpVtbl->ParseScriptText(activeScriptParse, &VBmacro[0], &MyMacroObjectName[0], 0, 0, 0, 0, SCRIPTITEM_ISVISIBLE|SCRIPTTEXT_ISPERSISTENT, 0, 0);
你会注意到,我们传给AddNamedItem的几个标志参数。我们指定了SCRIPTITEM_ISPERSISTENT,因为我们不想让引擎在 被我们重置为INITIALIZED状态时 删除这个命名项(和它的内容)。我们还设置了SCRIPTITEM_ISVISIBLE标志,因为我们想要这个命名项能够被缺省的全局项(即第二段脚本获取添加项的地方)访问。设置SCRIPTITEM_ISVISIBLE标志等价于删除 C语言引擎例子中函数 的static关键字。这会允许一个命名项的函数被其它命名项的函数调用。如果没有SCRIPTITEM_ISVISIBLE, 一个命名项的函数可以自己调用,但不能被其它任何命名项的函数调用。
我们必须修改第二个VB脚本。现在当它调用HelloWorld子程序时,需要引用命名项。在VBscript中,这是通过 把它的名称作为对象使用 来完成的:
- wchar_tVBscript[]=L"MyMacro.HelloWorld";
wchar_t VBscript[] = L"MyMacro.HelloWorld";
还有一件事情。当我们调用AddNamedItem创建“MyMacro”时,引擎会调用IActiveScriptSite的GetItemInfo
,并传递其名字“MyMacro”作为参数。我们需要为这个命名项 获取并返回一个IDispatch指针。这个IDispatch从哪儿来?我们通过 调用引擎IActiveScript对象的GetScriptDispatch
函数,传入命名项的名字 来得到它。这就是我们的IActiveScriptSite的GetItemInfo
函数:
- STDMETHODIMPGetItemInfo(MyRealIActiveScriptSite*this,LPCOLESTR
- objectName,DWORDdwReturnMask,IUnknown**objPtr,ITypeInfo**typeInfo)
- {
- if(dwReturnMask&SCRIPTINFO_IUNKNOWN)*objPtr=0;
- if(dwReturnMask&SCRIPTINFO_ITYPEINFO)*typeInfo=0;
- //Weassumethatthenameditemtheengineisaskingforisour
- //"MyMacro"nameditemwecreated.Weneedtoreturnthe
- //IDispatchforthisnameditem.Wheredowegetit?Fromtheengine.
- //Specifically,wecalltheengineIActiveScript'sGetScriptDispatch(),
- //passingobjectName(whichshouldbe"MyMacro").
- if(dwReturnMask&SCRIPTINFO_IUNKNOWN)
- return(EngineActiveScript->lpVtbl->GetScriptDispatch(EngineActiveScript,
- objectName,objPtr));
- return(E_FAIL);
- }
STDMETHODIMP GetItemInfo(MyRealIActiveScriptSite *this, LPCOLESTR objectName, DWORD dwReturnMask, IUnknown **objPtr, ITypeInfo **typeInfo) { if (dwReturnMask & SCRIPTINFO_IUNKNOWN) *objPtr = 0; if (dwReturnMask & SCRIPTINFO_ITYPEINFO) *typeInfo = 0; // We assume that the named item the engine is asking for is our // "MyMacro" named item we created. We need to return the // IDispatch for this named item. Where do we get it? From the engine. // Specifically, we call the engine IActiveScript's GetScriptDispatch(), // passing objectName (which should be "MyMacro"). if (dwReturnMask & SCRIPTINFO_IUNKNOWN) return(EngineActiveScript->lpVtbl->GetScriptDispatch(EngineActiveScript, objectName, objPtr)); return(E_FAIL); }
做了上面的修改,现在宏脚本就有了一个命名项。这有什么好处呢?首先,我们的第二个脚本中就能够也有一个“Hello World”子程序,不会和MyMacro的“Hello World”子程序冲突。所以,我们现在可以排除宏脚本和第二段脚本代码之间 子程序/函数 的命名冲突。此外,如果有更多的宏脚本,我们都可以放到它们各自的命名项中。这样,宏脚本就能有同名的 子程序/函数,在它们之间却不会发生名字冲突。脚本引擎知道那个 子程序/函数 被调用,因为命名项的名称指出了 子程序/函数 所属的命名项。
以下为译者注
*********************************************************
例程和函数的区别
-----------------------------
例程:
Private Sub abc(a As Integer, b As Integer, c As Integer)
'your code....
'c = a + b
End Sub
函数:
Private Function c (a As Integer, b As Integer) As Integer
'your code....
'c = a + b
End Function
-----------------------------
*********************************************************
综上所述,使用命名项能够避免 添加子程序/函数,全局变量添加到引擎中时 发生命名冲突。
调用脚本中的特定函数
在上面的例子中,通过调用引擎的GetScriptDispatch
,获取特定命名项的IDispatch,我们只是简单的把IDispatch返回给引擎。
但是,除此之外,我们自己还能通过这个IDispatch直接调用 (在特定命名项中的)VB 子例程/函数。为了调用 子例程/函数,我们需要调用IDispatch的GetIDsOfNames
和Invoke
函数。这和我们在IExampleApp3示例中,当我们使用IDispatch的Invoke来调用com对象中的函数时,做的很类似。你也许应该重新仔细阅读那个例子,以便记起来起来在里面我们是如何做的。 现在,我们要直接调用MyMacros命名项中的HelloWorld子例程。首先我们要通过引擎IActiveScript对象的GetScriptDispatch
函数获取命名项的IDispatch。然后调用IDispatch的GetIDsOfNames
得到
引擎用于标识我们想要调用的函数的唯一序列号 DISPID。
- //NOTE:Error-checkingomitted!
- IDispatch*objPtr;
- DISPIDdispid;
- OLECHAR*funcName;
- DISPPARAMSdspp;
- VARIANTret;
- //GettheIDispatchfor"MyMacro"nameditem
- EngineActiveScript->lpVtbl->GetScriptDispatch(EngineActiveScript,
- "MyMacro",&objPtr);
- //NowgettheDISPIDforthe"HelloWorld"sub
- funcName=(OLECHAR*)L"HelloWorld";
- objPtr->lpVtbl->GetIDsOfNames(objPtr,&IID_NULL,&funcName,1,
- LOCALE_USER_DEFAULT,&dispid);
- //CallHelloWorld.
- //SinceHelloWorldhasnoargspassedtoit,wedon'thavetodo
- //anygrotesqueinitializationofDISPPARAMS.
- ZeroMemory(&dspp,sizeof(DISPPARAMS));
- VariantInit(&ret);
- objPtr->lpVtbl->Invoke(objPtr,dispid,&IID_NULL,LOCALE_USER_DEFAULT,
- DISPATCH_METHOD,&dspp,&ret,0,0);
- VariantClear(&ret);
- //ReleasetheIDispatchnowthatwemadethecall
- objPtr->lpVtbl->Release(objPtr);
// NOTE: Error-checking omitted! IDispatch *objPtr; DISPID dispid; OLECHAR *funcName; DISPPARAMS dspp; VARIANT ret; // Get the IDispatch for "MyMacro" named item EngineActiveScript->lpVtbl->GetScriptDispatch(EngineActiveScript, "MyMacro", &objPtr); // Now get the DISPID for the "HelloWorld" sub funcName = (OLECHAR *)L"HelloWorld"; objPtr->lpVtbl->GetIDsOfNames(objPtr, &IID_NULL, &funcName, 1, LOCALE_USER_DEFAULT, &dispid); // Call HelloWorld. // Since HelloWorld has no args passed to it, we don't have to do // any grotesque initialization of DISPPARAMS. ZeroMemory(&dspp, sizeof(DISPPARAMS)); VariantInit(&ret); objPtr->lpVtbl->Invoke(objPtr, dispid, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &dspp, &ret, 0, 0); VariantClear(&ret); // Release the IDispatch now that we made the call objPtr->lpVtbl->Release(objPtr);
在ScriptHost8中,是添加下面的VB脚本(包含main子程序)到vb引擎中的例子:
- wchar_tVBscript[]=L"Submain\r\nMsgBox\"Helloworld\"\r\nEndSub";
wchar_t VBscript[] = L"Sub main\r\nMsgBox \"Hello world\"\r\nEnd Sub";然后我们直接调用这个 main 程序。你要注意的一件事是我们在调用ParseScriptText时没创建/指定任何特定的命名项。因此这段脚本代码被作为缺省“全局命名项”被加入。所以,我们需要从全局命名项中获取它的IDispatch。我们如何来做呢?我们传一个0给GetScriptDispatch作为名字。这是一个指定的值,告诉GetScriptDispatch返回全局命名项的IDispatch。
查询/设置脚本中变量的值
为了查询或设置某个(在指定命名项中的)变量,我们要做的事情和上面几乎完全一样。唯一不同在于Invoke的调用,当需要获取值时,指定DISPATCH_PROPERTYGET标志;当需要设置值时,我们设置DISPATCH_PROPERTYPUT标志。这就是一个 设置“MyMacro”命名项中变量“MyVariable”的值的 例子:
- //NOTE:Error-checkingomitted!
- IDispatch*objPtr;
- DISPIDdispid,dispPropPut;
- OLECHAR*varName;
- DISPPARAMSdspp;
- VARIANTarg;
- //GettheIDispatchfor"MyMacro"nameditem
- EngineActiveScript->lpVtbl->GetScriptDispatch(EngineActiveScript,
- "MyMacro",&objPtr);
- //NowgettheDISPIDforthe"MyVariable"variable(ie,property)
- varName=(OLECHAR*)L"MyVariable";
- objPtr->lpVtbl->GetIDsOfNames(objPtr,&IID_NULL,&varName,1,
- LOCALE_USER_DEFAULT,&dispid);
- //Setthevalueto10.
- VariantInit(&arg);
- ZeroMemory(&dspp,sizeof(DISPPARAMS));
- dspp.cArgs=dspp.cNamedArgs=1;
- dispPropPut=DISPID_PROPERTYPUT;
- dspp.rgdispidNamedArgs=&dispPropPut;
- dspp.rgvarg=&arg;
- arg.vt=VT_I4;
- arg.lVal=10;
- objPtr->lpVtbl->Invoke(objPtr,dispid,&IID_NULL,LOCALE_USER_DEFAULT,
- DISPATCH_PROPERTYPUT,&dspp,0,0,0);
- VariantClear(&arg);
- //ReleasetheIDispatchnowthatwemadethecall
- objPtr->lpVtbl->Release(objPtr);
// NOTE: Error-checking omitted! IDispatch *objPtr; DISPID dispid, dispPropPut; OLECHAR *varName; DISPPARAMS dspp; VARIANT arg; // Get the IDispatch for "MyMacro" named item EngineActiveScript->lpVtbl->GetScriptDispatch(EngineActiveScript, "MyMacro", &objPtr); // Now get the DISPID for the "MyVariable" variable (ie, property) varName = (OLECHAR *)L"MyVariable"; objPtr->lpVtbl->GetIDsOfNames(objPtr, &IID_NULL, &varName, 1, LOCALE_USER_DEFAULT, &dispid); // Set the value to 10. VariantInit(&arg); ZeroMemory(&dspp, sizeof(DISPPARAMS)); dspp.cArgs = dspp.cNamedArgs = 1; dispPropPut = DISPID_PROPERTYPUT; dspp.rgdispidNamedArgs = &dispPropPut; dspp.rgvarg = &arg; arg.vt = VT_I4; arg.lVal = 10; objPtr->lpVtbl->Invoke(objPtr, dispid, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYPUT, &dspp, 0, 0, 0); VariantClear(&arg); // Release the IDispatch now that we made the call objPtr->lpVtbl->Release(objPtr);
在ScriptHost9就是一个这样的示例,我们先设置MyVariable的值,然后调用mian子程序显示这个变量。
不同语言的交互
一种语言编写的脚本也可以调用另一种语言编写的脚本。例如,假设我们有下面的VB函数,显示一个消息框:
- SubSayHello
- MsgBox"HelloWorld"
- EndSub
Sub SayHello MsgBox "Hello World" End Sub
那么就假设我们用下面的jscript函数来调用上面的的Vbscript函数:
- functionmain()
- {
- SayHello();
- }
function main() { SayHello(); }
首先,由于我们将会使用2种不同语言的脚本,jscript和vbscript,所以我们需要调用2次CoCreateInstance
;一次得到jscript引擎的IActiveScript,另一次得到vbscript引擎的IActiveScript。当然,我们要把这2个指针分别保存到2个变量中(分别为:JActiveScript
和VBActiveScript
)。
我们还需要得到每个引擎的IActiveScriptParse。同时我们还需要调用每个引擎的SetScriptSite
,把IActivesScriptSite传给引擎(我们可以为每个引擎分别传递不同的IActivesScriptSite,但是在这里,我们传给每个引擎同样的IActivesScriptSite,因为我们不会同时运行2种语言的脚本,只有在jscript引擎调用vbscript函数时才会用到vb引擎)。
换言之,runScript
必须完成 要使用脚本引擎必需初始化的 工作,但是每个引擎只需要初始化一次。
然后,我们需要调用jscript引擎的ParseScriptText
添加上面的jscript代码到jscript引擎中,同时需要调用vbscript引擎的ParseScriptText
添加上面的vbscript代码到vbscript引擎中。我们会把这些代码分别加到2个引擎的全局命名项中。
为了方便jscript调用vbscript,我们需要在jscript引擎中创建一个命名项用来和vbscript交互。这需要在添加脚本到引擎中之前来做,我们随便给这个命名项取名为“VB”。
- JActiveScript->lpVtbl->AddNamedItem(JActiveScript,L"VB",
- SCRIPTITEM_GLOBALMEMBERS|SCRIPTITEM_ISVISIBLE);
JActiveScript->lpVtbl->AddNamedItem(JActiveScript, L"VB", SCRIPTITEM_GLOBALMEMBERS|SCRIPTITEM_ISVISIBLE);
让我们来看看当jscript引擎运行上面的jscript代码时发生了什么。引擎检查jscript中的我们载入的所有jscript函数,没有发现"SayHello"的jscript函数。因为我们添加了一些命名项到jscript引擎中(设置GetIDsOfNames标志),引擎就搞对自己说:"呃...也许SayHello函数在某个命名项中。我需要得到这个命名项对应的IDispatch,通过调用它的GetIDsOfNames函数查询SayHello的DISPID,如果IDispatch成功的返回了DISPID,我就调用IDispatch的Invoke来调用SayHello函数"。
但是引擎如何得到命名项的IDispatch呢?目前为止,你应该知道通过调用我们IActiveScriptSite的GetItemInfo
。这种情况下,jscript引擎会为命名项名字的参数传一个"VB"。这就是看起来有点莫名其妙的地方。当我们的GetItemInfo找到被指定的项时,我们调用的vbscript引擎GetScriptDispatch
来得到vbscript引擎的全局命名项,这正是我们要返回给jscript引擎的东西。
是的,你没有看错。当jscript引擎请求“VB”命名项的IDispatch时,我们实际上返回了VBscript引擎的全局命名项的IDispatch。为什么呢?因为我们vbscript的SayHello 函数被添加到vb引擎的全局命名项中,而不是在"VB"命名项中。换句话说,我们在jscript中把"VB"命名项作为一个代理(placeholder)使用。jscript引擎不需要知道"VB"命名项其实返回了vbscript引擎的全局命名项的IDispatch。
那么,jscript引擎会调用vbscript引擎全局命名项的IDsipatch的GetIDsOfNames
,当然,VB引擎会返回SayHello函数的DISPID。当jscript调用IDispatch的Inovke时,最终进入vb引擎中让vb引擎运行VB的SayHello函数。
这里就是我们的IActiveScriptSite的GetItemInfo
:
- STDMETHODIMPGetItemInfo(MyRealIActiveScriptSite*this,LPCOLESTR
- objectName,DWORDdwReturnMask,IUnknown**objPtr,ITypeInfo**typeInfo)
- {
- HRESULThr;
- hr=E_FAIL;
- if(dwReturnMask&SCRIPTINFO_ITYPEINFO)*typeInfo=0;
- if(dwReturnMask&SCRIPTINFO_IUNKNOWN)
- {
- *objPtr=0;
- //Iftheengineisaskingforour"VB"nameditemwecreated,
- //thenweknowthisistheJScriptenginecalling.Weneedto
- //returntheIDispatchforVBScript's"globalnameditem".
- if(!lstrcmpW(objectName,L"VB"))
- {
- hr=VBActiveScript->lpVtbl->GetScriptDispatch(VBActiveScript,
- 0,objPtr);
- }
- }
- return(hr);
- }
STDMETHODIMP GetItemInfo(MyRealIActiveScriptSite *this, LPCOLESTR objectName, DWORD dwReturnMask, IUnknown **objPtr, ITypeInfo **typeInfo) { HRESULT hr; hr = E_FAIL; if (dwReturnMask & SCRIPTINFO_ITYPEINFO) *typeInfo = 0; if (dwReturnMask & SCRIPTINFO_IUNKNOWN) { *objPtr = 0; // If the engine is asking for our "VB" named item we created, // then we know this is the JScript engine calling. We need to // return the IDispatch for VBScript's "global named item". if (!lstrcmpW(objectName, L"VB")) { hr = VBActiveScript->lpVtbl->GetScriptDispatch(VBActiveScript, 0, objPtr); } } return(hr); }
ScriptHost10中,是jscript调用vbscript的例子。
顺便提一句,你也许对SCRIPTITEM_GLOBALMEMBERS标志有点奇怪。先回想一下我们前面处理一个命名项时,脚本必须像一个对象名那样引用项的名字,例如:
VB.SayHello()
当用SCRIPTITEM_GLOBALMEMBERS标志创建项时,指出了对象名是可选的。例如,上面的代码可以工作,或者还可以这样用:
SayHello()
所以我们做的只是让jscript 在调用vb脚本的SayHello函数时 就像在调用另一个 本地(local) jscript函数。换言之,它或多或少是一种 隐藏命名项麻烦细节的 理论意义上的 捷径。
但这个好处是要付出代价的。就像全局项那样,这些使用SCRIPTITEM_GLOBALMEMBERS标记的项之间可能会出现名字冲突。
原文:http://www.codeproject.com/Articles/17038/COM-in-plain-C-part-8
内容
- 简介
- 脚本代码持久化
- 脚本代码和“命名项”
- 调用脚本中的特定函数
- 查询/设置脚本中变量的值
- 查询/设置脚本中变量的值
简介
在前面的章节中,我们学会了如何创建Activex脚本宿主。虽然这些章节覆盖了编写一个脚本宿主的的大部分方面,但是,这里还有其它一些 你的脚本宿主也许想要使用的 更esoteric(深奥,机密)的特性。本章节将会详细的介绍其中一些机密特性。
脚本代码持久化
在先前的脚本宿主示例中,我们用runScript
函数打开脚本引擎,运行脚本,然后关闭引擎。这个函数中,脚本引擎被加载/分析,执行,然后释放(执行完成之后)。
但是,也许有时候,你希望添加一些脚本到引擎,并且让它们一直驻留在里面,即使这些脚本没有被执行。可能,你希望这些脚本能够被任何 能够被相同引擎的IActiveScript识别的 其它脚本所调用。事实上,或许你还想把这些脚本作为"ram-based macros"集合。ActiveX脚本引擎让这成为可能。但我们还需要从以下两方面改变我们的方法:
- 当我们把脚本作为“宏”添加的时候,我们需要为
ParseScriptText
函数指定SCRIPTTEXT_ISPERSISTENT标志。这告诉引擎保留内部已分析\加载过的脚本,即使在ParseScriptText返回之后。 - 我们不能在所有宏被使用之前释放引擎的IActiveScript对象。如果这样做,那些宏最终会被卸载掉。
最好的做法是 在引擎处于INITIALIZED状态时 添加这些宏,但要在引擎被设置为STARTED或CONNECTED状态之前。ParseScriptText
不会尝试运行这些脚本,而是对其语法分析,并在ParseScriptText
返回的时候,在内部把这些脚本保存起来。这些脚本会驻留在引擎中,直到我们释放了引擎的IActiveScript对象,即使在这中间,我们调用了其它脚本或者方法。
在ScriptHost7目录中,你会找到一个说明这一点的例子。我们添加一个VB脚本给引擎,并指定 SCRIPTTEXT_ISPERSISTENT 标志。为了简单,这个VB脚本被作为全局变量,像下面这样嵌入在我们的EXE中:
- wchar_tVBmacro[]=L"SubHelloWorld\r\nMsgBox\"Helloworld\"\r\nEndSub";
wchar_t VBmacro[] = L"Sub HelloWorld\r\nMsgBox \"Hello world\"\r\nEnd Sub";
上面的代码是一个VB的“Hello World”子程序。它只是的弹出一个消息框。
接下来,我们嵌入第二段VB脚本。这个VB脚本只是调用第一个加载的HelloWorld脚本程序。
- wchar_tVBscript[]=L"HelloWorld";
wchar_t VBscript[] = L"HelloWorld";
当载入第二段脚本时,我们不指定SCRIPTTEXT_ISPERSISTENT标志。
为了确保持久化的脚本能够工作,需要让runScript线程始终保持VB引擎的IActiveScript对象,直到程序终止。为了完成这一点,我们在程序开始的时候就启动线程(而不是在运行脚本时才启动线程)。这个线程一直保持有效,直到主程序结束。开始,引擎调用CoCreateInstance来获取VB引擎的 IActiveScript,然后调用 IActiveScript的QueryInterface 得到引擎的 IActiveScriptParse对象,再调用InitNew初始化引擎,最后调用SetScriptSite把我们的 IActiceScriptSite传给引擎。初始化部分跟我们前几章的示例一样。
runScript将会调用ParseScriptText
来加载我们的“VB 宏”到引擎中。这几乎和示例程序完全一样,除开指定了 SCRIPTTEXT_ISPERSISTENT 标志:
- activeScriptParse->lpVtbl->ParseScriptText(activeScriptParse,&VBmacro[0],
- 0,0,0,0,0,SCRIPTTEXT_ISPERSISTENT,0,0);
activeScriptParse->lpVtbl->ParseScriptText(activeScriptParse, &VBmacro[0], 0, 0, 0, 0, 0, SCRIPTTEXT_ISPERSISTENT, 0, 0);
添加这个脚本之后,runScript 线程就等待,直到主线程 设置我们创建的一个事件信号量 唤醒,然后运行第二段脚本(它会调用刚才我们装载的第一段脚本)。
主窗口有一个“Run Script”按钮。当用户点击时,主线程就设置事件信号量。
- //Letthescriptthreadknowthatwewantittorunascript
- SetEvent(NotifySignal[0]);
// Let the script thread know that we want it to run a script SetEvent(NotifySignal[0]);
运行线程被唤醒后就调用ParseScriptText
函数加载第二个脚本,然后通过调用SetScriptState
函数,设置VB脚本引擎的状态为SCRIPTSTATE_CONNECTED。 这导致第二段脚本运行, 第二段脚本调用 宏 中的 “Hello World”子程序 弹出一个消息框。当用户关闭消息框之后,第一段脚本运行结束,SetScriptState
函数返回。
此时,我们还没有释放(Rlease) IActiveScriptParse和IActiveScript对象,也没有关闭引擎。我们调用SetScriptState
把状态设置为SCRIPTSTATE_INITIALIZED。这样第二段脚本就被卸载,但宏脚本不会被卸载,因为它是持久化的。
运行线程又重新休眠了。等待用户再次点击“Run Script”按钮。在这个事件中,运行线程重复 加载/运行 第二段脚本的过程。但注意我们不需要重新加载宏脚本,它一直被保留在引擎中。
这就是运行线程中的“脚本循环”:
- for(;;)
- {
- //Waitformainthreadtosignalustorunascript.
- WaitForSingleObject(NotifySignal,INFINITE);
- //Havethescriptengineparseoursecondscriptandadditto
- //theinternallistofscriptstorun.NOTE:WedoNOTspecify
- //SCRIPTTEXT_ISPERSISTENTsothisscriptwillbeunloaded
- //whentheenginegoesbacktoINITIALIZEDstate.
- activeScriptParse->lpVtbl->ParseScriptText(activeScriptParse,
- &VBscript[0],0,0,0,0,0,0,0,0);
- //Runallofthescriptsthatweaddedtotheengine.
- EngineActiveScript->lpVtbl->SetScriptState(EngineActiveScript,
- SCRIPTSTATE_CONNECTED);
- //TheabovescripthasendedafterSetScriptStatereturns.Now
- //let'ssettheenginestatebacktoinitializedtounloadthis
- //script.VBmacro[]remainsstillloaded.
- EngineActiveScript->lpVtbl->SetScriptState(EngineActiveScript,
- SCRIPTSTATE_INITIALIZED);
- }
for (;;) { // Wait for main thread to signal us to run a script. WaitForSingleObject(NotifySignal, INFINITE); // Have the script engine parse our second script and add it to // the internal list of scripts to run. NOTE: We do NOT specify // SCRIPTTEXT_ISPERSISTENT so this script will be unloaded // when the engine goes back to INITIALIZED state. activeScriptParse->lpVtbl->ParseScriptText(activeScriptParse, &VBscript[0], 0, 0, 0, 0, 0, 0, 0, 0); // Run all of the scripts that we added to the engine. EngineActiveScript->lpVtbl->SetScriptState(EngineActiveScript, SCRIPTSTATE_CONNECTED); // The above script has ended after SetScriptState returns. Now // let's set the engine state back to initialized to unload this // script. VBmacro[] remains still loaded. EngineActiveScript->lpVtbl->SetScriptState(EngineActiveScript, SCRIPTSTATE_INITIALIZED); }
脚本代码和“命名项”
在上面的例子中,我们添加宏脚本给同样的“命名项”作为第二段脚本。(注意:我们没指定特定的命名项,所以引擎用它的缺省的全局项)。但在不同的“命名项”下加载脚本是可以的。
在前面的章节中,你应该记得我们可以 通过创建一个命名项(通过引擎IActiveScript对象的的AddNamedItem函数) 让脚本能够调用我们自己的的C函数。
但这不是命名项的唯一用处。我们还可以把创建的命名项分组,这就是我们现在的示例:
假设我们有2个c源文件分别为File1.c 和 File2.c。下面是 File1.c的内容:
- //File1.c
- staticvoidMyFunction(void)
- {
- printf("File1.c");
- }
- staticvoidFile1(void)
- {
- MyFunction();
- }
// File1.c static void MyFunction(void) { printf("File1.c"); } static void File1(void) { MyFunction(); }这是 File2.c 的内容:
- //File2.c
- staticvoidMyFunction(constchar*ptr)
- {
- printf(ptr);
- }
- staticvoidFile2(void)
- {
- MyFunction("File1.c");
- }
// File2.c static void MyFunction(const char *ptr) { printf(ptr); } static void File2(void) { MyFunction("File1.c"); }
还有一些东西需需要注意:
- 由于static关键字修饰,File1.c中的
MyFunction
和File2.c中的MyFunction
就不一样了。我们可以把两个源文件在一起编译和连接不会有问题(也就是说,不会有命名冲突)。 - 由于static关键字修饰,File1.c中的函数不能调用File2.c中的函数,反之亦然。
当我们创建一个命名项时(在我们要加载的脚本代码中),把它看成创建c源文件。为了创建一个命名项,我们调用引擎IActiveScipt的AddNamedItem
。假设我们有一个C语言实现的脚本引擎。首先,我们需要调用AddNamedItem两次,第一次,我们创建以File1.c作为名字的命名项;第二次我们创建以File2.c作为名字的命名项。这就在引擎中创建了2个“源文件”,然后我们将调用ParseScriptText把File1.c的内容加载给
File1.c命名项。为此,我们必须把命名项的名字作为第三个参数传给ParseScriptText。然后,我们把File2.c的内容加载给File2.c命名项。以下是我们的具体做法:
- //Here'sthecontentsofFile1.c
- wchar_tFile1[]=L"staticvoidMyFunction(void)
- {
- printf(\"File1.c\");
- }
- staticvoidFile1(void)
- {
- MyFunction();
- }";
- //Here'sthecontentsofFile2.c
- wchar_tFile1[]=L"staticvoidMyFunction(constchar*ptr)
- {
- printf(ptr);
- }
- staticvoidFile2(void)
- {
- MyFunction(\"File1.c\");
- }";
- //CreatetheFile1.cnameditem.Error-checkingomitted!
- EngineActiveScript->lpVtbl->AddNamedItem(EngineActiveScript,"File1.c",0);
- //CreatetheFile2.cnameditem.
- EngineActiveScript->lpVtbl->AddNamedItem(EngineActiveScript,"File2.c",0);
- //AddtheFile1.ccontentstotheFile1.cnamedobject
- activeScriptParse->lpVtbl->ParseScriptText(activeScriptParse,&File1[0],
- "File1.c",0,0,0,0,0,0,0);
- //AddtheFile2.ccontentstotheFile2.cnamedobject
- activeScriptParse->lpVtbl->ParseScriptText(activeScriptParse,&File2[0],
- "File2.c",0,0,0,0,0,0,0);
- 现在我们把VB“宏脚本”放到创建的命名项中。随便给这个项取名为:MyMacro。我们的具体做法如下:
- //Thenameofthenameditem
- wchar_tMyMacroObjectName[]=L"MyMacro";
- //CreatetheMyMacronameditem
- EngineActiveScript->lpVtbl->AddNamedItem(EngineActiveScript,&MyMacroObjectName[0],SCRIPTITEM_ISVISIBLE|SCRIPTITEM_ISPERSISTENT);
- //AddthecontentsofVBmacrototheMyMacronameditem
- activeScriptParse->lpVtbl->ParseScriptText(activeScriptParse,&VBmacro[0],
- &MyMacroObjectName[0],0,0,0,0,SCRIPTITEM_ISVISIBLE|SCRIPTTEXT_ISPERSISTENT,
- 0,0);
// Here's the contents of File1.c wchar_t File1[] = L"static void MyFunction(void) { printf(\"File1.c\"); } static void File1(void) { MyFunction(); }"; // Here's the contents of File2.c wchar_t File1[] = L"static void MyFunction(const char *ptr) { printf(ptr); } static void File2(void) { MyFunction(\"File1.c\"); }"; // Create the File1.c named item. Error-checking omitted! EngineActiveScript->lpVtbl->AddNamedItem(EngineActiveScript, "File1.c", 0); // Create the File2.c named item. EngineActiveScript->lpVtbl->AddNamedItem(EngineActiveScript, "File2.c", 0); // Add the File1.c contents to the File1.c named object activeScriptParse->lpVtbl->ParseScriptText(activeScriptParse, &File1[0], "File1.c", 0, 0, 0, 0, 0, 0, 0); // Add the File2.c contents to the File2.c named object activeScriptParse->lpVtbl->ParseScriptText(activeScriptParse, &File2[0], "File2.c", 0, 0, 0, 0, 0, 0, 0); 现在我们把VB“宏脚本”放到创建的命名项中。随便给这个项取名为:MyMacro。我们的具体做法如下: // The name of the named item wchar_t MyMacroObjectName[] = L"MyMacro"; // Create the MyMacro named item EngineActiveScript->lpVtbl->AddNamedItem(EngineActiveScript, &MyMacroObjectName[0], SCRIPTITEM_ISVISIBLE|SCRIPTITEM_ISPERSISTENT); // Add the contents of VBmacro to the MyMacro named item activeScriptParse->lpVtbl->ParseScriptText(activeScriptParse, &VBmacro[0], &MyMacroObjectName[0], 0, 0, 0, 0, SCRIPTITEM_ISVISIBLE|SCRIPTTEXT_ISPERSISTENT, 0, 0);
你会注意到,我们传给AddNamedItem的几个标志参数。我们指定了SCRIPTITEM_ISPERSISTENT,因为我们不想让引擎在 被我们重置为INITIALIZED状态时 删除这个命名项(和它的内容)。我们还设置了SCRIPTITEM_ISVISIBLE标志,因为我们想要这个命名项能够被缺省的全局项(即第二段脚本获取添加项的地方)访问。设置SCRIPTITEM_ISVISIBLE标志等价于删除 C语言引擎例子中函数 的static关键字。这会允许一个命名项的函数被其它命名项的函数调用。如果没有SCRIPTITEM_ISVISIBLE, 一个命名项的函数可以自己调用,但不能被其它任何命名项的函数调用。
我们必须修改第二个VB脚本。现在当它调用HelloWorld子程序时,需要引用命名项。在VBscript中,这是通过 把它的名称作为对象使用 来完成的:
- wchar_tVBscript[]=L"MyMacro.HelloWorld";
wchar_t VBscript[] = L"MyMacro.HelloWorld";
还有一件事情。当我们调用AddNamedItem创建“MyMacro”时,引擎会调用IActiveScriptSite的GetItemInfo
,并传递其名字“MyMacro”作为参数。我们需要为这个命名项 获取并返回一个IDispatch指针。这个IDispatch从哪儿来?我们通过 调用引擎IActiveScript对象的GetScriptDispatch
函数,传入命名项的名字 来得到它。这就是我们的IActiveScriptSite的GetItemInfo
函数:
- STDMETHODIMPGetItemInfo(MyRealIActiveScriptSite*this,LPCOLESTR
- objectName,DWORDdwReturnMask,IUnknown**objPtr,ITypeInfo**typeInfo)
- {
- if(dwReturnMask&SCRIPTINFO_IUNKNOWN)*objPtr=0;
- if(dwReturnMask&SCRIPTINFO_ITYPEINFO)*typeInfo=0;
- //Weassumethatthenameditemtheengineisaskingforisour
- //"MyMacro"nameditemwecreated.Weneedtoreturnthe
- //IDispatchforthisnameditem.Wheredowegetit?Fromtheengine.
- //Specifically,wecalltheengineIActiveScript'sGetScriptDispatch(),
- //passingobjectName(whichshouldbe"MyMacro").
- if(dwReturnMask&SCRIPTINFO_IUNKNOWN)
- return(EngineActiveScript->lpVtbl->GetScriptDispatch(EngineActiveScript,
- objectName,objPtr));
- return(E_FAIL);
- }
STDMETHODIMP GetItemInfo(MyRealIActiveScriptSite *this, LPCOLESTR objectName, DWORD dwReturnMask, IUnknown **objPtr, ITypeInfo **typeInfo) { if (dwReturnMask & SCRIPTINFO_IUNKNOWN) *objPtr = 0; if (dwReturnMask & SCRIPTINFO_ITYPEINFO) *typeInfo = 0; // We assume that the named item the engine is asking for is our // "MyMacro" named item we created. We need to return the // IDispatch for this named item. Where do we get it? From the engine. // Specifically, we call the engine IActiveScript's GetScriptDispatch(), // passing objectName (which should be "MyMacro"). if (dwReturnMask & SCRIPTINFO_IUNKNOWN) return(EngineActiveScript->lpVtbl->GetScriptDispatch(EngineActiveScript, objectName, objPtr)); return(E_FAIL); }
做了上面的修改,现在宏脚本就有了一个命名项。这有什么好处呢?首先,我们的第二个脚本中就能够也有一个“Hello World”子程序,不会和MyMacro的“Hello World”子程序冲突。所以,我们现在可以排除宏脚本和第二段脚本代码之间 子程序/函数 的命名冲突。此外,如果有更多的宏脚本,我们都可以放到它们各自的命名项中。这样,宏脚本就能有同名的 子程序/函数,在它们之间却不会发生名字冲突。脚本引擎知道那个 子程序/函数 被调用,因为命名项的名称指出了 子程序/函数 所属的命名项。
以下为译者注
*********************************************************
例程和函数的区别
-----------------------------
例程:
Private Sub abc(a As Integer, b As Integer, c As Integer)
'your code....
'c = a + b
End Sub
函数:
Private Function c (a As Integer, b As Integer) As Integer
'your code....
'c = a + b
End Function
-----------------------------
*********************************************************
综上所述,使用命名项能够避免 添加子程序/函数,全局变量添加到引擎中时 发生命名冲突。
调用脚本中的特定函数
在上面的例子中,通过调用引擎的GetScriptDispatch
,获取特定命名项的IDispatch,我们只是简单的把IDispatch返回给引擎。
但是,除此之外,我们自己还能通过这个IDispatch直接调用 (在特定命名项中的)VB 子例程/函数。为了调用 子例程/函数,我们需要调用IDispatch的GetIDsOfNames
和Invoke
函数。这和我们在IExampleApp3示例中,当我们使用IDispatch的Invoke来调用com对象中的函数时,做的很类似。你也许应该重新仔细阅读那个例子,以便记起来起来在里面我们是如何做的。 现在,我们要直接调用MyMacros命名项中的HelloWorld子例程。首先我们要通过引擎IActiveScript对象的GetScriptDispatch
函数获取命名项的IDispatch。然后调用IDispatch的GetIDsOfNames
得到
引擎用于标识我们想要调用的函数的唯一序列号 DISPID。
- //NOTE:Error-checkingomitted!
- IDispatch*objPtr;
- DISPIDdispid;
- OLECHAR*funcName;
- DISPPARAMSdspp;
- VARIANTret;
- //GettheIDispatchfor"MyMacro"nameditem
- EngineActiveScript->lpVtbl->GetScriptDispatch(EngineActiveScript,
- "MyMacro",&objPtr);
- //NowgettheDISPIDforthe"HelloWorld"sub
- funcName=(OLECHAR*)L"HelloWorld";
- objPtr->lpVtbl->GetIDsOfNames(objPtr,&IID_NULL,&funcName,1,
- LOCALE_USER_DEFAULT,&dispid);
- //CallHelloWorld.
- //SinceHelloWorldhasnoargspassedtoit,wedon'thavetodo
- //anygrotesqueinitializationofDISPPARAMS.
- ZeroMemory(&dspp,sizeof(DISPPARAMS));
- VariantInit(&ret);
- objPtr->lpVtbl->Invoke(objPtr,dispid,&IID_NULL,LOCALE_USER_DEFAULT,
- DISPATCH_METHOD,&dspp,&ret,0,0);
- VariantClear(&ret);
- //ReleasetheIDispatchnowthatwemadethecall
- objPtr->lpVtbl->Release(objPtr);
// NOTE: Error-checking omitted! IDispatch *objPtr; DISPID dispid; OLECHAR *funcName; DISPPARAMS dspp; VARIANT ret; // Get the IDispatch for "MyMacro" named item EngineActiveScript->lpVtbl->GetScriptDispatch(EngineActiveScript, "MyMacro", &objPtr); // Now get the DISPID for the "HelloWorld" sub funcName = (OLECHAR *)L"HelloWorld"; objPtr->lpVtbl->GetIDsOfNames(objPtr, &IID_NULL, &funcName, 1, LOCALE_USER_DEFAULT, &dispid); // Call HelloWorld. // Since HelloWorld has no args passed to it, we don't have to do // any grotesque initialization of DISPPARAMS. ZeroMemory(&dspp, sizeof(DISPPARAMS)); VariantInit(&ret); objPtr->lpVtbl->Invoke(objPtr, dispid, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &dspp, &ret, 0, 0); VariantClear(&ret); // Release the IDispatch now that we made the call objPtr->lpVtbl->Release(objPtr);
在ScriptHost8中,是添加下面的VB脚本(包含main子程序)到vb引擎中的例子:
- wchar_tVBscript[]=L"Submain\r\nMsgBox\"Helloworld\"\r\nEndSub";
wchar_t VBscript[] = L"Sub main\r\nMsgBox \"Hello world\"\r\nEnd Sub";然后我们直接调用这个 main 程序。你要注意的一件事是我们在调用ParseScriptText时没创建/指定任何特定的命名项。因此这段脚本代码被作为缺省“全局命名项”被加入。所以,我们需要从全局命名项中获取它的IDispatch。我们如何来做呢?我们传一个0给GetScriptDispatch作为名字。这是一个指定的值,告诉GetScriptDispatch返回全局命名项的IDispatch。
查询/设置脚本中变量的值
为了查询或设置某个(在指定命名项中的)变量,我们要做的事情和上面几乎完全一样。唯一不同在于Invoke的调用,当需要获取值时,指定DISPATCH_PROPERTYGET标志;当需要设置值时,我们设置DISPATCH_PROPERTYPUT标志。这就是一个 设置“MyMacro”命名项中变量“MyVariable”的值的 例子:
- //NOTE:Error-checkingomitted!
- IDispatch*objPtr;
- DISPIDdispid,dispPropPut;
- OLECHAR*varName;
- DISPPARAMSdspp;
- VARIANTarg;
- //GettheIDispatchfor"MyMacro"nameditem
- EngineActiveScript->lpVtbl->GetScriptDispatch(EngineActiveScript,
- "MyMacro",&objPtr);
- //NowgettheDISPIDforthe"MyVariable"variable(ie,property)
- varName=(OLECHAR*)L"MyVariable";
- objPtr->lpVtbl->GetIDsOfNames(objPtr,&IID_NULL,&varName,1,
- LOCALE_USER_DEFAULT,&dispid);
- //Setthevalueto10.
- VariantInit(&arg);
- ZeroMemory(&dspp,sizeof(DISPPARAMS));
- dspp.cArgs=dspp.cNamedArgs=1;
- dispPropPut=DISPID_PROPERTYPUT;
- dspp.rgdispidNamedArgs=&dispPropPut;
- dspp.rgvarg=&arg;
- arg.vt=VT_I4;
- arg.lVal=10;
- objPtr->lpVtbl->Invoke(objPtr,dispid,&IID_NULL,LOCALE_USER_DEFAULT,
- DISPATCH_PROPERTYPUT,&dspp,0,0,0);
- VariantClear(&arg);
- //ReleasetheIDispatchnowthatwemadethecall
- objPtr->lpVtbl->Release(objPtr);
// NOTE: Error-checking omitted! IDispatch *objPtr; DISPID dispid, dispPropPut; OLECHAR *varName; DISPPARAMS dspp; VARIANT arg; // Get the IDispatch for "MyMacro" named item EngineActiveScript->lpVtbl->GetScriptDispatch(EngineActiveScript, "MyMacro", &objPtr); // Now get the DISPID for the "MyVariable" variable (ie, property) varName = (OLECHAR *)L"MyVariable"; objPtr->lpVtbl->GetIDsOfNames(objPtr, &IID_NULL, &varName, 1, LOCALE_USER_DEFAULT, &dispid); // Set the value to 10. VariantInit(&arg); ZeroMemory(&dspp, sizeof(DISPPARAMS)); dspp.cArgs = dspp.cNamedArgs = 1; dispPropPut = DISPID_PROPERTYPUT; dspp.rgdispidNamedArgs = &dispPropPut; dspp.rgvarg = &arg; arg.vt = VT_I4; arg.lVal = 10; objPtr->lpVtbl->Invoke(objPtr, dispid, &IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYPUT, &dspp, 0, 0, 0); VariantClear(&arg); // Release the IDispatch now that we made the call objPtr->lpVtbl->Release(objPtr);
在ScriptHost9就是一个这样的示例,我们先设置MyVariable的值,然后调用mian子程序显示这个变量。
不同语言的交互
一种语言编写的脚本也可以调用另一种语言编写的脚本。例如,假设我们有下面的VB函数,显示一个消息框:
- SubSayHello
- MsgBox"HelloWorld"
- EndSub
Sub SayHello MsgBox "Hello World" End Sub
那么就假设我们用下面的jscript函数来调用上面的的Vbscript函数:
- functionmain()
- {
- SayHello();
- }
function main() { SayHello(); }
首先,由于我们将会使用2种不同语言的脚本,jscript和vbscript,所以我们需要调用2次CoCreateInstance
;一次得到jscript引擎的IActiveScript,另一次得到vbscript引擎的IActiveScript。当然,我们要把这2个指针分别保存到2个变量中(分别为:JActiveScript
和VBActiveScript
)。
我们还需要得到每个引擎的IActiveScriptParse。同时我们还需要调用每个引擎的SetScriptSite
,把IActivesScriptSite传给引擎(我们可以为每个引擎分别传递不同的IActivesScriptSite,但是在这里,我们传给每个引擎同样的IActivesScriptSite,因为我们不会同时运行2种语言的脚本,只有在jscript引擎调用vbscript函数时才会用到vb引擎)。
换言之,runScript
必须完成 要使用脚本引擎必需初始化的 工作,但是每个引擎只需要初始化一次。
然后,我们需要调用jscript引擎的ParseScriptText
添加上面的jscript代码到jscript引擎中,同时需要调用vbscript引擎的ParseScriptText
添加上面的vbscript代码到vbscript引擎中。我们会把这些代码分别加到2个引擎的全局命名项中。
为了方便jscript调用vbscript,我们需要在jscript引擎中创建一个命名项用来和vbscript交互。这需要在添加脚本到引擎中之前来做,我们随便给这个命名项取名为“VB”。
- JActiveScript->lpVtbl->AddNamedItem(JActiveScript,L"VB",
- SCRIPTITEM_GLOBALMEMBERS|SCRIPTITEM_ISVISIBLE);
JActiveScript->lpVtbl->AddNamedItem(JActiveScript, L"VB", SCRIPTITEM_GLOBALMEMBERS|SCRIPTITEM_ISVISIBLE);
让我们来看看当jscript引擎运行上面的jscript代码时发生了什么。引擎检查jscript中的我们载入的所有jscript函数,没有发现"SayHello"的jscript函数。因为我们添加了一些命名项到jscript引擎中(设置GetIDsOfNames标志),引擎就搞对自己说:"呃...也许SayHello函数在某个命名项中。我需要得到这个命名项对应的IDispatch,通过调用它的GetIDsOfNames函数查询SayHello的DISPID,如果IDispatch成功的返回了DISPID,我就调用IDispatch的Invoke来调用SayHello函数"。
但是引擎如何得到命名项的IDispatch呢?目前为止,你应该知道通过调用我们IActiveScriptSite的GetItemInfo
。这种情况下,jscript引擎会为命名项名字的参数传一个"VB"。这就是看起来有点莫名其妙的地方。当我们的GetItemInfo找到被指定的项时,我们调用的vbscript引擎GetScriptDispatch
来得到vbscript引擎的全局命名项,这正是我们要返回给jscript引擎的东西。
是的,你没有看错。当jscript引擎请求“VB”命名项的IDispatch时,我们实际上返回了VBscript引擎的全局命名项的IDispatch。为什么呢?因为我们vbscript的SayHello 函数被添加到vb引擎的全局命名项中,而不是在"VB"命名项中。换句话说,我们在jscript中把"VB"命名项作为一个代理(placeholder)使用。jscript引擎不需要知道"VB"命名项其实返回了vbscript引擎的全局命名项的IDispatch。
那么,jscript引擎会调用vbscript引擎全局命名项的IDsipatch的GetIDsOfNames
,当然,VB引擎会返回SayHello函数的DISPID。当jscript调用IDispatch的Inovke时,最终进入vb引擎中让vb引擎运行VB的SayHello函数。
这里就是我们的IActiveScriptSite的GetItemInfo
:
- STDMETHODIMPGetItemInfo(MyRealIActiveScriptSite*this,LPCOLESTR
- objectName,DWORDdwReturnMask,IUnknown**objPtr,ITypeInfo**typeInfo)
- {
- HRESULThr;
- hr=E_FAIL;
- if(dwReturnMask&SCRIPTINFO_ITYPEINFO)*typeInfo=0;
- if(dwReturnMask&SCRIPTINFO_IUNKNOWN)
- {
- *objPtr=0;
- //Iftheengineisaskingforour"VB"nameditemwecreated,
- //thenweknowthisistheJScriptenginecalling.Weneedto
- //returntheIDispatchforVBScript's"globalnameditem".
- if(!lstrcmpW(objectName,L"VB"))
- {
- hr=VBActiveScript->lpVtbl->GetScriptDispatch(VBActiveScript,
- 0,objPtr);
- }
- }
- return(hr);
- }
STDMETHODIMP GetItemInfo(MyRealIActiveScriptSite *this, LPCOLESTR objectName, DWORD dwReturnMask, IUnknown **objPtr, ITypeInfo **typeInfo) { HRESULT hr; hr = E_FAIL; if (dwReturnMask & SCRIPTINFO_ITYPEINFO) *typeInfo = 0; if (dwReturnMask & SCRIPTINFO_IUNKNOWN) { *objPtr = 0; // If the engine is asking for our "VB" named item we created, // then we know this is the JScript engine calling. We need to // return the IDispatch for VBScript's "global named item". if (!lstrcmpW(objectName, L"VB")) { hr = VBActiveScript->lpVtbl->GetScriptDispatch(VBActiveScript, 0, objPtr); } } return(hr); }
ScriptHost10中,是jscript调用vbscript的例子。
顺便提一句,你也许对SCRIPTITEM_GLOBALMEMBERS标志有点奇怪。先回想一下我们前面处理一个命名项时,脚本必须像一个对象名那样引用项的名字,例如:
VB.SayHello()
当用SCRIPTITEM_GLOBALMEMBERS标志创建项时,指出了对象名是可选的。例如,上面的代码可以工作,或者还可以这样用:
SayHello()
所以我们做的只是让jscript 在调用vb脚本的SayHello函数时 就像在调用另一个 本地(local) jscript函数。换言之,它或多或少是一种 隐藏命名项麻烦细节的 理论意义上的 捷径。
但这个好处是要付出代价的。就像全局项那样,这些使用SCRIPTITEM_GLOBALMEMBERS标记的项之间可能会出现名字冲突。
相关推荐
在本文中,我们将探讨如何使用标准C语言编写COM组件,特别是如何创建一个集合对象,以便于应用程序处理一系列相关元素。 首先,集合对象在COM中通常用来表示一组相关的对象或数据,比如上述例子中的PCI硬件板卡列表...
### 使用标准C语言实现COM接口:深入探讨脚本代码持久化与变量操作 #### 脚本代码持久化 在标准C中实现COM接口时,脚本代码持久化是一项高级特性,允许脚本代码在执行环境内存中长期存在,即使在其他脚本执行或...
COM in plain C - 使用标准 C 编写 COM 组件(第二部分) COM(Component Object Model,组件对象模型)是微软推广的一种软件组件架构,它允许不同的编程语言和组件之间进行交互和重用。在第一部分中,我们学习了...
### 使用标准C编写COM组件(一) #### 一、引言 在计算机编程领域,特别是Windows平台上,组件对象模型(Component Object Model, COM)是一种重要的技术标准,用于实现跨语言和跨进程对象间的通信。虽然大多数COM...
而"COM-in-plain-C-Part-7.pdf"可能是该系列教程的文本部分,详细解释了每一步的技术细节和注意事项。 总之,"普通C中的COM,第7部分"涉及了如何在C++中实现一个简单的COM对象,让它能够被ActiveX脚本调用。这个...
for part in e.get_payload(): print(part.get_content_type()) # 输出:text/plain, text/html ``` 在这个例子中,尽管`Message`对象不是严格意义上的“鸭子”,但它提供了足够多的方法来让我们认为它就像一个...
- **Plain-Csharp-implementation-of-WPF-Concepts-Part-A.pdf**:这可能是教程的文本版,详细介绍纯C#实现WPF概念的第一部分,特别是AProps和绑定。 - **CODE.zip**:这个文件很可能包含了示例代码,供读者参考和...
14.2.2. 'Plain-old' JSPs versus JSTL 'Plain-old' JSP与JSTL 14.2.3. 帮助简化开发的额外的标签 14.3. Tiles 14.3.1. 需要的资源 14.3.2. 如何集成Tiles 14.4. Velocity和FreeMarker 14.4.1. 需要的资源 ...
14.2.2. 'Plain-old' JSPs versus JSTL 'Plain-old' JSP与JSTL 14.2.3. 帮助简化开发的额外的标签 14.3. Tiles 14.3.1. 需要的资源 14.3.2. 如何集成Tiles 14.4. Velocity和FreeMarker 14.4.1. 需要的资源 ...
14.2.2. 'Plain-old' JSPs versus JSTL 'Plain-old' JSP与JSTL 14.2.3. 帮助简化开发的额外的标签 14.3. Tiles 14.3.1. 需要的资源 14.3.2. 如何集成Tiles 14.4. Velocity和FreeMarker 14.4.1. 需要的资源 ...
14.2.2. 'Plain-old' JSPs versus JSTL 'Plain-old' JSP与JSTL 14.2.3. 帮助简化开发的额外的标签 14.3. Tiles 14.3.1. 需要的资源 14.3.2. 如何集成Tiles 14.3.2.1. InternalResourceViewResolver 14.3.2.2. ...