用标准C编写COM(八)COM in plain C,Part8
2013-09-28 16:47
465 查看
原文:http://www.codeproject.com/Articles/17038/COM-in-plain-C-part-8
下载例程-419Kb
内容
简介
脚本代码持久化
脚本代码和“命名项”
调用脚本中的特定函数
查询/设置脚本中变量的值
查询/设置脚本中变量的值
但是,也许有时候,你希望添加一些脚本到引擎,并且让它们一直驻留在里面,即使这些脚本没有被执行。可能,你希望这些脚本能够被任何 能够被相同引擎的IActiveScript识别的 其它脚本所调用。事实上,或许你还想把这些脚本作为"ram-based macros"集合。ActiveX脚本引擎让这成为可能。但我们还需要从以下两方面改变我们的方法:
当我们把脚本作为“宏”添加的时候,我们需要为
我们不能在所有宏被使用之前释放引擎的IActiveScript对象。如果这样做,那些宏最终会被卸载掉。
最好的做法是 在引擎处于INITIALIZED状态时 添加这些宏,但要在引擎被设置为STARTED或CONNECTED状态之前。
在ScriptHost7目录中,你会找到一个说明这一点的例子。我们添加一个VB脚本给引擎,并指定 SCRIPTTEXT_ISPERSISTENT 标志。为了简单,这个VB脚本被作为全局变量,像下面这样嵌入在我们的EXE中:
[cpp] view
plaincopyprint?
wchar_t VBmacro[] = L"Sub HelloWorld\r\nMsgBox \"Hello world\"\r\nEnd Sub";
上面的代码是一个VB的“Hello World”子程序。它只是的弹出一个消息框。
接下来,我们嵌入第二段VB脚本。这个VB脚本只是调用第一个加载的HelloWorld脚本程序。
[cpp] view
plaincopyprint?
wchar_t VBscript[] = L"HelloWorld";
当载入第二段脚本时,我们不指定SCRIPTTEXT_ISPERSISTENT标志。
为了确保持久化的脚本能够工作,需要让runScript线程始终保持VB引擎的IActiveScript对象,直到程序终止。为了完成这一点,我们在程序开始的时候就启动线程(而不是在运行脚本时才启动线程)。这个线程一直保持有效,直到主程序结束。开始,引擎调用CoCreateInstance来获取VB引擎的 IActiveScript,然后调用 IActiveScript的QueryInterface 得到引擎的 IActiveScriptParse对象,再调用InitNew初始化引擎,最后调用SetScriptSite把我们的
IActiceScriptSite传给引擎。初始化部分跟我们前几章的示例一样。
runScript将会调用
[cpp] view
plaincopyprint?
activeScriptParse->lpVtbl->ParseScriptText(activeScriptParse, &VBmacro[0],
0, 0, 0, 0, 0, SCRIPTTEXT_ISPERSISTENT, 0, 0);
添加这个脚本之后,runScript 线程就等待,直到主线程 设置我们创建的一个事件信号量 唤醒,然后运行第二段脚本(它会调用刚才我们装载的第一段脚本)。
主窗口有一个“Run Script”按钮。当用户点击时,主线程就设置事件信号量。
[cpp] view
plaincopyprint?
// Let the script thread know that we want it to run a script
SetEvent(NotifySignal[0]);
运行线程被唤醒后就调用
此时,我们还没有释放(Rlease) IActiveScriptParse和IActiveScript对象,也没有关闭引擎。我们调用
运行线程又重新休眠了。等待用户再次点击“Run Script”按钮。在这个事件中,运行线程重复 加载/运行 第二段脚本的过程。但注意我们不需要重新加载宏脚本,它一直被保留在引擎中。
这就是运行线程中的“脚本循环”:
[cpp] view
plaincopyprint?
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的内容:
[cpp] view
plaincopyprint?
// File1.c
static void MyFunction(void)
{
printf("File1.c");
}
static void File1(void)
{
MyFunction();
}
这是 File2.c 的内容:
[cpp] view
plaincopyprint?
// File2.c
static void MyFunction(const char *ptr)
{
printf(ptr);
}
static void File2(void)
{
MyFunction("File1.c");
}
还有一些东西需需要注意:
由于static关键字修饰,File1.c中的
由于static关键字修饰,File1.c中的函数不能调用File2.c中的函数,反之亦然。
当我们创建一个命名项时(在我们要加载的脚本代码中),把它看成创建c源文件。为了创建一个命名项,我们调用引擎IActiveScipt的
File1.c命名项。为此,我们必须把命名项的名字作为第三个参数传给ParseScriptText。然后,我们把File2.c的内容加载给File2.c命名项。以下是我们的具体做法:
你会注意到,我们传给AddNamedItem的几个标志参数。我们指定了SCRIPTITEM_ISPERSISTENT,因为我们不想让引擎在 被我们重置为INITIALIZED状态时 删除这个命名项(和它的内容)。我们还设置了SCRIPTITEM_ISVISIBLE标志,因为我们想要这个命名项能够被缺省的全局项(即第二段脚本获取添加项的地方)访问。设置SCRIPTITEM_ISVISIBLE标志等价于删除 C语言引擎例子中函数 的static关键字。这会允许一个命名项的函数被其它命名项的函数调用。如果没有SCRIPTITEM_ISVISIBLE,
一个命名项的函数可以自己调用,但不能被其它任何命名项的函数调用。
我们必须修改第二个VB脚本。现在当它调用HelloWorld子程序时,需要引用命名项。在VBscript中,这是通过 把它的名称作为对象使用 来完成的:
[cpp] view
plaincopyprint?
wchar_t VBscript[] = L"MyMacro.HelloWorld";
还有一件事情。当我们调用AddNamedItem创建“MyMacro”时,引擎会调用IActiveScriptSite的
做了上面的修改,现在宏脚本就有了一个命名项。这有什么好处呢?首先,我们的第二个脚本中就能够也有一个“Hello World”子程序,不会和MyMacro的“Hello World”子程序冲突。所以,我们现在可以排除宏脚本和第二段脚本代码之间 子程序/函数 的命名冲突。此外,如果有更多的宏脚本,我们都可以放到它们各自的命名项中。这样,宏脚本就能有同名的 子程序/函数,在它们之间却不会发生名字冲突。脚本引擎知道那个 子程序/函数 被调用,因为命名项的名称指出了 子程序/函数 所属的命名项。
综上所述,使用命名项能够避免 添加子程序/函数,全局变量添加到引擎中时 发生命名冲突。
但是,除此之外,我们自己还能通过这个IDispatch直接调用 (在特定命名项中的)VB 子例程/函数。为了调用 子例程/函数,我们需要调用IDispatch的
引擎用于标识我们想要调用的函数的唯一序列号 DISPID。
[cpp] view
plaincopyprint?
// 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引擎中的例子:
[cpp] view
plaincopyprint?
wchar_t VBscript[] = L"Sub main\r\nMsgBox \"Hello world\"\r\nEnd Sub";
然后我们直接调用这个 main 程序。你要注意的一件事是我们在调用ParseScriptText时没创建/指定任何特定的命名项。因此这段脚本代码被作为缺省“全局命名项”被加入。所以,我们需要从全局命名项中获取它的IDispatch。我们如何来做呢?我们传一个0给GetScriptDispatch作为名字。这是一个指定的值,告诉GetScriptDispatch返回全局命名项的IDispatch。
[cpp] view
plaincopyprint?
// 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] view
plaincopyprint?
Sub SayHello
MsgBox "Hello World"
End Sub
那么就假设我们用下面的jscript函数来调用上面的的Vbscript函数:
[vb] view
plaincopyprint?
function main()
{
SayHello();
}
首先,由于我们将会使用2种不同语言的脚本,jscript和vbscript,所以我们需要调用2次
我们还需要得到每个引擎的IActiveScriptParse。同时我们还需要调用每个引擎的
换言之,
然后,我们需要调用jscript引擎的
为了方便jscript调用vbscript,我们需要在jscript引擎中创建一个命名项用来和vbscript交互。这需要在添加脚本到引擎中之前来做,我们随便给这个命名项取名为“VB”。
[vb] view
plaincopyprint?
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的
是的,你没有看错。当jscript引擎请求“VB”命名项的IDispatch时,我们实际上返回了VBscript引擎的全局命名项的IDispatch。为什么呢?因为我们vbscript的SayHello 函数被添加到vb引擎的全局命名项中,而不是在"VB"命名项中。换句话说,我们在jscript中把"VB"命名项作为一个代理(placeholder)使用。jscript引擎不需要知道"VB"命名项其实返回了vbscript引擎的全局命名项的IDispatch。
那么,jscript引擎会调用vbscript引擎全局命名项的IDsipatch的
这里就是我们的IActiveScriptSite的
[cpp] view
plaincopyprint?
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标志有点奇怪。先回想一下我们前面处理一个命名项时,脚本必须像一个对象名那样引用项的名字,例如:
当用SCRIPTITEM_GLOBALMEMBERS标志创建项时,指出了对象名是可选的。例如,上面的代码可以工作,或者还可以这样用:
所以我们做的只是让jscript 在调用vb脚本的SayHello函数时 就像在调用另一个 本地(local) jscript函数。换言之,它或多或少是一种 隐藏命名项麻烦细节的 理论意义上的 捷径。
但这个好处是要付出代价的。就像全局项那样,这些使用SCRIPTITEM_GLOBALMEMBERS标记的项之间可能会出现名字冲突。
下载例程-419Kb
内容
简介
脚本代码持久化
脚本代码和“命名项”
调用脚本中的特定函数
查询/设置脚本中变量的值
查询/设置脚本中变量的值
简介
在前面的章节中,我们学会了如何创建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中:
[cpp] view
plaincopyprint?
wchar_t VBmacro[] = L"Sub HelloWorld\r\nMsgBox \"Hello world\"\r\nEnd Sub";
上面的代码是一个VB的“Hello World”子程序。它只是的弹出一个消息框。
接下来,我们嵌入第二段VB脚本。这个VB脚本只是调用第一个加载的HelloWorld脚本程序。
[cpp] view
plaincopyprint?
wchar_t VBscript[] = L"HelloWorld";
当载入第二段脚本时,我们不指定SCRIPTTEXT_ISPERSISTENT标志。
为了确保持久化的脚本能够工作,需要让runScript线程始终保持VB引擎的IActiveScript对象,直到程序终止。为了完成这一点,我们在程序开始的时候就启动线程(而不是在运行脚本时才启动线程)。这个线程一直保持有效,直到主程序结束。开始,引擎调用CoCreateInstance来获取VB引擎的 IActiveScript,然后调用 IActiveScript的QueryInterface 得到引擎的 IActiveScriptParse对象,再调用InitNew初始化引擎,最后调用SetScriptSite把我们的
IActiceScriptSite传给引擎。初始化部分跟我们前几章的示例一样。
runScript将会调用
ParseScriptText来加载我们的“VB 宏”到引擎中。这几乎和示例程序完全一样,除开指定了 SCRIPTTEXT_ISPERSISTENT 标志:
[cpp] view
plaincopyprint?
activeScriptParse->lpVtbl->ParseScriptText(activeScriptParse, &VBmacro[0],
0, 0, 0, 0, 0, SCRIPTTEXT_ISPERSISTENT, 0, 0);
添加这个脚本之后,runScript 线程就等待,直到主线程 设置我们创建的一个事件信号量 唤醒,然后运行第二段脚本(它会调用刚才我们装载的第一段脚本)。
主窗口有一个“Run Script”按钮。当用户点击时,主线程就设置事件信号量。
[cpp] view
plaincopyprint?
// 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”按钮。在这个事件中,运行线程重复 加载/运行 第二段脚本的过程。但注意我们不需要重新加载宏脚本,它一直被保留在引擎中。
这就是运行线程中的“脚本循环”:
[cpp] view
plaincopyprint?
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的内容:
[cpp] view
plaincopyprint?
// File1.c
static void MyFunction(void)
{
printf("File1.c");
}
static void File1(void)
{
MyFunction();
}
这是 File2.c 的内容:
[cpp] view
plaincopyprint?
// 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命名项。以下是我们的具体做法:
[cpp] view plaincopyprint?// 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中,这是通过 把它的名称作为对象使用 来完成的:
[cpp] view
plaincopyprint?
wchar_t VBscript[] = L"MyMacro.HelloWorld";
还有一件事情。当我们调用AddNamedItem创建“MyMacro”时,引擎会调用IActiveScriptSite的
GetItemInfo,并传递其名字“MyMacro”作为参数。我们需要为这个命名项 获取并返回一个IDispatch指针。这个IDispatch从哪儿来?我们通过 调用引擎IActiveScript对象的
GetScriptDispatch函数,传入命名项的名字 来得到它。这就是我们的IActiveScriptSite的
GetItemInfo函数:
[cpp] view plaincopyprint?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。
[cpp] view
plaincopyprint?
// 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引擎中的例子:
[cpp] view
plaincopyprint?
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”的值的 例子:[cpp] view
plaincopyprint?
// 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函数,显示一个消息框:[vb] view
plaincopyprint?
Sub SayHello
MsgBox "Hello World"
End Sub
那么就假设我们用下面的jscript函数来调用上面的的Vbscript函数:
[vb] view
plaincopyprint?
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”。
[vb] view
plaincopyprint?
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:
[cpp] view
plaincopyprint?
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 in plain C,Part7
- 用标准C编写COM(六)COM in plain C,Part6
- 用标准C编写COM(五)COM in plain C,Part5
- 用标准C编写COM(四)COM in plain C,Part4
- 用标准C编写COM(三)COM in plain C,Part3
- 用标准C编写COM(一)COM in plain C,Part1
- rails中ActiveModel::ForbiddenAttributesError的解决方案(新)
- ssh localhost执行时报错:Agent admitted failure to sign using the key
- vitualbox安装报错installation failed!error
- Mail 邮件发送
- HDU-4750 Count The Pairs 最小生成树,并查集
- mfcs100d.lib(dllmodul.obj) : error LNK2005: _DllMain@12 already defined in MSVCRTD.lib(dllmain.obj)
- mfcs100d.lib(dllmodul.obj) : error LNK2005: _DllMain@12 already defined in MSVCRTD.lib(dllmain.obj)
- Nobody Can Forget The Air Jordan Shoes
- OnEraseBkGnd() 与OnPaint()的关系,以及解决闪烁的问题
- POJ 1273 Drainage Ditches
- 关于[No grammar constraints (DTD…
- Tri_integral Autumn Training 3 训练总结
- OpenStack Availability Zone和Aggregate Hosts理解
- Reconnaissance