标准C编写COM(八)COM in plain C,Part8

原文:http://www.codeproject.com/Articles/17038/COM-in-plain-C-part-8

下载例程-419Kb

内容

  • 简介
  • 脚本代码持久化
  • 脚本代码和“命名项”
  • 调用脚本中的特定函数
  • 查询/设置脚本中变量的值
  • 查询/设置脚本中变量的值


简介

在前面的章节中,我们学会了如何创建Activex脚本宿主。虽然这些章节覆盖了编写一个脚本宿主的的大部分方面,但是,这里还有其它一些 你的脚本宿主也许想要使用的 更esoteric(深奥,机密)的特性。本章节将会详细的介绍其中一些机密特性。


 

脚本代码持久化


在先前的脚本宿主示例中,我们用runScript函数打开脚本引擎,运行脚本,然后关闭引擎。这个函数中,脚本引擎被加载/分析,执行,然后释放(执行完成之后)。


但是,也许有时候,你希望添加一些脚本到引擎,并且让它们一直驻留在里面,即使这些脚本没有被执行。可能,你希望这些脚本能够被任何 能够被相同引擎的IActiveScript识别的 其它脚本所调用。事实上,或许你还想把这些脚本作为"ram-based macros"集合。ActiveX脚本引擎让这成为可能。但我们还需要从以下两方面改变我们的方法:

  1. 当我们把脚本作为“宏”添加的时候,我们需要为ParseScriptText函数指定SCRIPTTEXT_ISPERSISTENT标志。这告诉引擎保留内部已分析\加载过的脚本,即使在ParseScriptText返回之后。

  2. 我们不能在所有宏被使用之前释放引擎的IActiveScript对象。如果这样做,那些宏最终会被卸载掉。

最好的做法是 在引擎处于INITIALIZED状态时 添加这些宏,但要在引擎被设置为STARTED或CONNECTED状态之前。ParseScriptText不会尝试运行这些脚本,而是对其语法分析,并在ParseScriptText返回的时候,在内部把这些脚本保存起来。这些脚本会驻留在引擎中,直到我们释放了引擎的IActiveScript对象,即使在这中间,我们调用了其它脚本或者方法。


ScriptHost7目录中,你会找到一个说明这一点的例子。我们添加一个VB脚本给引擎,并指定 SCRIPTTEXT_ISPERSISTENT 标志。为了简单,这个VB脚本被作为全局变量,像下面这样嵌入在我们的EXE中:

  1. wchar_t VBmacro[] = L"Sub HelloWorld\r\nMsgBox \"Hello world\"\r\nEnd Sub";  
wchar_t VBmacro[] = L"Sub HelloWorld\r\nMsgBox \"Hello world\"\r\nEnd Sub";

上面的代码是一个VB的“Hello World”子程序。它只是的弹出一个消息框。


接下来,我们嵌入第二段VB脚本。这个VB脚本只是调用第一个加载的HelloWorld脚本程序。

  1. wchar_t VBscript[] = 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 标志:

  1. activeScriptParse->lpVtbl->ParseScriptText(activeScriptParse, &VBmacro[0],  
  2.     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”按钮。当用户点击时,主线程就设置事件信号量。

  1. // Let the script thread know that we want it to run a script   
  2. 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”按钮。在这个事件中,运行线程重复 加载/运行 第二段脚本的过程。但注意我们不需要重新加载宏脚本,它一直被保留在引擎中。

这就是运行线程中的“脚本循环”:

  1. for (;;)  
  2. {  
  3.    // Wait for main thread to signal us to run a script.   
  4.    WaitForSingleObject(NotifySignal, INFINITE);  
  5.   
  6.    // Have the script engine parse our second script and add it to   
  7.    // the internal list of scripts to run. NOTE: We do NOT specify   
  8.    // SCRIPTTEXT_ISPERSISTENT so this script will be unloaded   
  9.    // when the engine goes back to INITIALIZED state.   
  10.    activeScriptParse->lpVtbl->ParseScriptText(activeScriptParse,  
  11.       &VBscript[0], 0, 0, 0, 0, 0, 0, 0, 0);  
  12.   
  13.    // Run all of the scripts that we added to the engine.   
  14.    EngineActiveScript->lpVtbl->SetScriptState(EngineActiveScript,  
  15.       SCRIPTSTATE_CONNECTED);  
  16.   
  17.    // The above script has ended after SetScriptState returns. Now   
  18.    // let's set the engine state back to initialized to unload this   
  19.    // script. VBmacro[] remains still loaded.   
  20.    EngineActiveScript->lpVtbl->SetScriptState(EngineActiveScript,  
  21.       SCRIPTSTATE_INITIALIZED);  
  22. }  
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.cFile2.c。下面是 File1.c的内容:

  1. // File1.c   
  2. static void MyFunction(void)  
  3. {  
  4.    printf("File1.c");  
  5. }  
  6.   
  7. static void File1(void)  
  8. {  
  9.    MyFunction();  
  10. }  
// File1.c
static void MyFunction(void)
{
   printf("File1.c");
}

static void File1(void)
{
   MyFunction();
}

这是 File2.c 的内容:
  1. // File2.c   
  2. static void MyFunction(const char *ptr)  
  3. {  
  4.    printf(ptr);  
  5. }  
  6.   
  7. static void File2(void)  
  8. {  
  9.    MyFunction("File1.c");  
  10. }  
// File2.c
static void MyFunction(const char *ptr)
{
   printf(ptr);
}

static void File2(void)
{
   MyFunction("File1.c");
}

还有一些东西需需要注意:

  1. 由于static关键字修饰,File1.c中的MyFunction和File2.c中的MyFunction就不一样了。我们可以把两个源文件在一起编译和连接不会有问题(也就是说,不会有命名冲突)。

  2. 由于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命名项。以下是我们的具体做法:

  
  
  1. // Here's the contents of File1.c   
  2. wchar_t File1[] = L"static void MyFunction(void)  
  3. {  
  4.    printf(\"File1.c\");  
  5. }  
  6. static void File1(void)  
  7. {  
  8.    MyFunction();  
  9. }";  
  10.   
  11. // Here's the contents of File2.c   
  12. wchar_t File1[] = L"static void MyFunction(const char *ptr)  
  13. {  
  14.    printf(ptr);  
  15. }  
  16. static void File2(void)  
  17. {  
  18.    MyFunction(\"File1.c\");  
  19. }";  
  20.   
  21. // Create the File1.c named item. Error-checking omitted!   
  22. EngineActiveScript->lpVtbl->AddNamedItem(EngineActiveScript, "File1.c", 0);  
  23.   
  24. // Create the File2.c named item.   
  25. EngineActiveScript->lpVtbl->AddNamedItem(EngineActiveScript, "File2.c", 0);  
  26.   
  27. // Add the File1.c contents to the File1.c named object   
  28. activeScriptParse->lpVtbl->ParseScriptText(activeScriptParse, &File1[0],  
  29.     "File1.c", 0, 0, 0, 0, 0, 0, 0);  
  30.   
  31. // Add the File2.c contents to the File2.c named object   
  32. activeScriptParse->lpVtbl->ParseScriptText(activeScriptParse, &File2[0],  
  33.     "File2.c", 0, 0, 0, 0, 0, 0, 0);  
  34. 现在我们把VB“宏脚本”放到创建的命名项中。随便给这个项取名为:MyMacro。我们的具体做法如下:  
  35. // The name of the named item   
  36. wchar_t MyMacroObjectName[] = L"MyMacro";  
  37.   
  38. // Create the MyMacro named item   
  39. EngineActiveScript->lpVtbl->AddNamedItem(EngineActiveScript, &MyMacroObjectName[0], SCRIPTITEM_ISVISIBLE|SCRIPTITEM_ISPERSISTENT);  
  40.   
  41. // Add the contents of VBmacro to the MyMacro named item   
  42. activeScriptParse->lpVtbl->ParseScriptText(activeScriptParse, &VBmacro[0],  
  43.    &MyMacroObjectName[0], 0, 0, 0, 0, SCRIPTITEM_ISVISIBLE|SCRIPTTEXT_ISPERSISTENT,  
  44.    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中,这是通过 把它的名称作为对象使用 来完成的:

  1. wchar_t VBscript[] = L"MyMacro.HelloWorld";  
wchar_t VBscript[] = L"MyMacro.HelloWorld";

还有一件事情。当我们调用AddNamedItem创建“MyMacro”时,引擎会调用IActiveScriptSite的GetItemInfo,并传递其名字“MyMacro”作为参数。我们需要为这个命名项 获取并返回一个IDispatch指针。这个IDispatch从哪儿来?我们通过 调用引擎IActiveScript对象的GetScriptDispatch函数,传入命名项的名字 来得到它。这就是我们的IActiveScriptSite的GetItemInfo函数:

  
  
  1. STDMETHODIMP GetItemInfo(MyRealIActiveScriptSite *this, LPCOLESTR  
  2.    objectName, DWORD dwReturnMask, IUnknown **objPtr, ITypeInfo **typeInfo)  
  3. {  
  4.    if (dwReturnMask & SCRIPTINFO_IUNKNOWN) *objPtr = 0;  
  5.    if (dwReturnMask & SCRIPTINFO_ITYPEINFO) *typeInfo = 0;  
  6.   
  7.    // We assume that the named item the engine is asking for is our   
  8.    // "MyMacro" named item we created. We need to return the   
  9.    // IDispatch for this named item. Where do we get it? From the engine.   
  10.    // Specifically, we call the engine IActiveScript's GetScriptDispatch(),   
  11.    // passing objectName (which should be "MyMacro").   
  12.    if (dwReturnMask & SCRIPTINFO_IUNKNOWN)  
  13.       return(EngineActiveScript->lpVtbl->GetScriptDispatch(EngineActiveScript,  
  14.          objectName, objPtr));  
  15.    return(E_FAIL);  
  16. }  
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的GetIDsOfNamesInvoke函数。这和我们在IExampleApp3示例中,当我们使用IDispatch的Invoke来调用com对象中的函数时,做的很类似。你也许应该重新仔细阅读那个例子,以便记起来起来在里面我们是如何做的。 现在,我们要直接调用MyMacros命名项中的HelloWorld子例程。首先我们要通过引擎IActiveScript对象的GetScriptDispatch函数获取命名项的IDispatch。然后调用IDispatch的GetIDsOfNames得到 引擎用于标识我们想要调用的函数的唯一序列号 DISPID。

  1. // NOTE: Error-checking omitted!   
  2. IDispatch  *objPtr;  
  3. DISPID     dispid;  
  4. OLECHAR    *funcName;  
  5. DISPPARAMS dspp;  
  6. VARIANT    ret;  
  7.   
  8. // Get the IDispatch for "MyMacro" named item   
  9. EngineActiveScript->lpVtbl->GetScriptDispatch(EngineActiveScript,  
  10.    "MyMacro", &objPtr);  
  11.   
  12. // Now get the DISPID for the "HelloWorld" sub   
  13. funcName = (OLECHAR *)L"HelloWorld";  
  14. objPtr->lpVtbl->GetIDsOfNames(objPtr, &IID_NULL, &funcName, 1,  
  15.    LOCALE_USER_DEFAULT, &dispid);  
  16.   
  17. // Call HelloWorld.   
  18. // Since HelloWorld has no args passed to it, we don't have to do   
  19. // any grotesque initialization of DISPPARAMS.   
  20. ZeroMemory(&dspp, sizeof(DISPPARAMS));  
  21. VariantInit(&ret);  
  22. objPtr->lpVtbl->Invoke(objPtr, dispid, &IID_NULL, LOCALE_USER_DEFAULT,  
  23.   DISPATCH_METHOD, &dspp, &ret, 0, 0);  
  24. VariantClear(&ret);  
  25.   
  26. // Release the IDispatch now that we made the call   
  27. 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引擎中的例子:

  1. wchar_t VBscript[] = L"Sub main\r\nMsgBox \"Hello world\"\r\nEnd Sub";  
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”的值的 例子:

  1. // NOTE: Error-checking omitted!   
  2. IDispatch  *objPtr;  
  3. DISPID     dispid, dispPropPut;  
  4. OLECHAR    *varName;  
  5. DISPPARAMS dspp;  
  6. VARIANT    arg;  
  7.   
  8. // Get the IDispatch for "MyMacro" named item   
  9. EngineActiveScript->lpVtbl->GetScriptDispatch(EngineActiveScript,  
  10.    "MyMacro", &objPtr);  
  11.   
  12. // Now get the DISPID for the "MyVariable" variable (ie, property)   
  13. varName = (OLECHAR *)L"MyVariable";  
  14. objPtr->lpVtbl->GetIDsOfNames(objPtr, &IID_NULL, &varName, 1,  
  15.    LOCALE_USER_DEFAULT, &dispid);  
  16.   
  17. // Set the value to 10.   
  18. VariantInit(&arg);  
  19. ZeroMemory(&dspp, sizeof(DISPPARAMS));  
  20. dspp.cArgs = dspp.cNamedArgs = 1;  
  21. dispPropPut = DISPID_PROPERTYPUT;  
  22. dspp.rgdispidNamedArgs = &dispPropPut;  
  23. dspp.rgvarg = &arg;  
  24. arg.vt = VT_I4;  
  25. arg.lVal = 10;  
  26. objPtr->lpVtbl->Invoke(objPtr, dispid, &IID_NULL, LOCALE_USER_DEFAULT,  
  27.   DISPATCH_PROPERTYPUT, &dspp, 0, 0, 0);  
  28. VariantClear(&arg);  
  29.   
  30. // Release the IDispatch now that we made the call   
  31. 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函数,显示一个消息框:

  1. Sub SayHello  
  2.    MsgBox "Hello World"  
  3. End Sub  
Sub SayHello
   MsgBox "Hello World"
End Sub

那么就假设我们用下面的jscript函数来调用上面的的Vbscript函数:

  1. function main()  
  2. {  
  3.    SayHello();  
  4. }  
function main()
{
   SayHello();
}

首先,由于我们将会使用2种不同语言的脚本,jscript和vbscript,所以我们需要调用2次CoCreateInstance;一次得到jscript引擎的IActiveScript,另一次得到vbscript引擎的IActiveScript。当然,我们要把这2个指针分别保存到2个变量中(分别为:JActiveScriptVBActiveScript )。


我们还需要得到每个引擎的IActiveScriptParse。同时我们还需要调用每个引擎的SetScriptSite,把IActivesScriptSite传给引擎(我们可以为每个引擎分别传递不同的IActivesScriptSite,但是在这里,我们传给每个引擎同样的IActivesScriptSite,因为我们不会同时运行2种语言的脚本,只有在jscript引擎调用vbscript函数时才会用到vb引擎)。


换言之,runScript必须完成 要使用脚本引擎必需初始化的 工作,但是每个引擎只需要初始化一次。


然后,我们需要调用jscript引擎的ParseScriptText添加上面的jscript代码到jscript引擎中,同时需要调用vbscript引擎的ParseScriptText添加上面的vbscript代码到vbscript引擎中。我们会把这些代码分别加到2个引擎的全局命名项中。


为了方便jscript调用vbscript,我们需要在jscript引擎中创建一个命名项用来和vbscript交互。这需要在添加脚本到引擎中之前来做,我们随便给这个命名项取名为“VB”。

  1. JActiveScript->lpVtbl->AddNamedItem(JActiveScript, L"VB",  
  2.    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

  1. STDMETHODIMP GetItemInfo(MyRealIActiveScriptSite *this, LPCOLESTR  
  2.    objectName, DWORD dwReturnMask, IUnknown **objPtr, ITypeInfo **typeInfo)  
  3. {  
  4.    HRESULT    hr;  
  5.   
  6.    hr = E_FAIL;  
  7.   
  8.    if (dwReturnMask & SCRIPTINFO_ITYPEINFO) *typeInfo = 0;  
  9.   
  10.    if (dwReturnMask & SCRIPTINFO_IUNKNOWN)  
  11.    {  
  12.       *objPtr = 0;  
  13.   
  14.       // If the engine is asking for our "VB" named item we created,   
  15.       // then we know this is the JScript engine calling. We need to   
  16.       // return the IDispatch for VBScript's "global named item".   
  17.       if (!lstrcmpW(objectName, L"VB"))  
  18.       {  
  19.          hr = VBActiveScript->lpVtbl->GetScriptDispatch(VBActiveScript,  
  20.             0, objPtr);  
  21.       }  
  22.    }  
  23.   
  24.    return(hr);  
  25. }  
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标记的项之间可能会出现名字冲突。 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值