是否可以代表VBScript执行QueryInterface

时间:2012-06-28 16:39:54

标签: c# com interface vbscript

我正在尝试使用一种方法创建一个COM类,该方法将代表VBScript将对象强制转换为特定的接口。

这是我正在使用的方法签名:
public object GetInterface(object unknown, string iid)

我认为这是可能的,因为如果方法显式地将返回类型声明为:
public IRequestedInterface GetInterface(object unknown, string iid)

然后VBScript获取对所需接口的引用。

所以我尝试了只是转换到界面
return (IRequestedInterface)unknown;

不幸的是,VBScript获取了对默认接口的引用,而不是所请求的接口。

我尝试使用ICustomMarshaler创建自定义编组程序来解决这个问题 我认为这会有效,因为方法MarshalManagedToNative会返回IntPtr

因此,我认为如果我只是将IntPtr返回到界面
return Marshal.GetComInterfaceForObject(unknown, typeof(IRequestedInterface));
它会奏效。但是,显然,它没有达到预期的效果:(

那么有人知道它是否可行以及你将如何做到这一点?

编辑:

我认为添加一个具体的例子(虽然它是设计的)有助于解释为什么我没有接受VBScript将始终获得默认接口。我仍然坚持我的希望。

下面你会看到3个文件的内容,'TestLib.cs','Build.cmd'和'Test.vbs'。这些有希望证明我仍然认为它“应该”成为可能的原因。

注意:我在Windows XP SP3(x86)上测试了这个。

TestLib.cs

using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;

[assembly: ComVisible(false)]
[assembly: Guid("64e20009-c664-4883-a6e5-1e36a31a0fd8")]
[assembly: AssemblyVersion("2012.06.*")]

[ComVisible(true)]
[Guid("EB77C7B1-D1B9-4BB3-9D63-FBFBD56C9ABA")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IPerformQi
{
    [DispId(1000)]
    object GetInterface(object unknown, string iid);

    [DispId(2000)]
    IRequested GetIRequested(object unknown);
}

[ComVisible(true)]
[Guid("7742BC0A-8719-483E-B1DF-AE9CD9A958DC")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IDefault
{
    [DispId(1000)]
    void SayHello(string name);
}

[ComVisible(true)]
[Guid("FFF34296-2A06-47D4-B09C-B93B63D5CC53")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IRequested
{
    [DispId(1000)]
    void SayGoodbye(string name);
}

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(IPerformQi))]
[Guid("222BB88D-B9FA-4F23-8DB3-BA998F4E668B")]
[ProgId("TestLib.PerformQi")]
public class PerformQi : IPerformQi
{
    object IPerformQi.GetInterface(object unknown, string iid)
    {
        if(iid == "FFF34296-2A06-47D4-B09C-B93B63D5CC53")
            return (IRequested)unknown;

        throw new Exception("Unable to find inteface");
    }

    IRequested IPerformQi.GetIRequested(object unknown)
    {
        return (IRequested)unknown;
    }
}

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(IDefault))]
[Guid("174ABED6-3325-4878-89E3-BF8BD1107488")]
[ProgId("TestLib.Test")]
public class Test : IDefault, IRequested
{
    void IDefault.SayHello(string name)
    {
        MessageBox.Show(string.Format("Hello '{0}'", name));
    }

    void IRequested.SayGoodbye(string name)
    {
        MessageBox.Show(string.Format("Goodbye '{0}'", name));
    }
}

Build.cmd

"%windir%\Microsoft.Net\Framework\v4.0.30319\csc.exe" /out:TestLib.dll /target:library /r:System.Windows.Forms.dll TestLib.cs
"%windir%\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe" TestLib.dll /codebase /tlb:TestLib.tlb
PAUSE

Test.vbs

Dim oPerformQi 'As TestLib.PerformQi
Dim oTest 'As TestLib.Test
Dim oTest2 'As IRequested
Dim oTest3 'As IRequested

Set oPerformQi = CreateObject("TestLib.PerformQi")
Set oTest = CreateObject("TestLib.Test")
Call oTest.SayHello("Robert")


Set oTest2 = oPerformQi.GetIRequested(oTest)
'Note: This works
Call oTest2.SayGoodbye("Robert")


Set oTest3 = oPerformQi.GetInterface(oTest, "FFF34296-2A06-47D4-B09C-B93B63D5CC53")
'Note: This does not work
Call oTest3.SayGoodbye("Robert")

使用通话oPerformQi.GetIRequested(oTest)可以调用oTest3.SayGoodbye("Robert")。这让我觉得你不仅仅局限于VBS中的默认界面。

由于对返回值的隐式转换,.Net可能无法返回指定的接口?理想情况下,我会使用泛型,但我们都知道COM不支持genrics。

根据这个限制,你能想到实现这个目的吗?

编辑2:

我发现我可以使用VB6实现这一点,下面是该类的代码。

Option Explicit

Public Function GetInterface(ByVal oUnknown As Object, ByVal IID As String) As Variant

    Dim oIRequested As IRequested

    If IID = "FFF34296-2A06-47D4-B09C-B93B63D5CC53" Then
        Set oIRequested = oUnknown
        Set GetInterface = oIRequested
    Else
        Err.Raise 1, , "Unable to find inteface"
    End If

End Function

我仍然希望找到一个C#版本,如果有人能够对这个主题有所了解我会很感激。

1 个答案:

答案 0 :(得分:1)

为了在单个对象上实现多个IDispatch派生接口,要从脚本环境访问,您应该实现IDispatchEx并在脚本调用发生后调用其方法。

您遇到的问题是由于首先对您的IDispatch进行脚本查询以及您的IDispatch派生接口返回相同的“主”IDispatch而导致的机会不存在其他接口的方法可以访问。

当VBS主机要在您的对象上调用方法时,它首先查询IDispatchEx。如果找到,则调用通过IDispatchEx::InvokeEx传递,您的COM类可以在内部将调用路由到正确的IDispatch实现,包括私有或前向外部/内部对象。

如果找不到IDispatchEx,它会查找IDispatch并且您遇到麻烦,因为它只能看到您的“主要”界面。也就是说,您的解决方法是实现IDispatchEx。您可以这样做:在COM类上实现,或者创建一个代理类以通过IDispatchEx::InvokeEx接受脚本调用,并转发以更正代码中的IDispatch

示例:AB类都实现IXIY接口,B另外实现IDispatchEx。接口方法是IX::X1IY::Y1

On Error Resume Next

Set A = CreateObject("Test.A")
WScript.Echo A.X1 ' Success, via IX::Invoke
WScript.Echo A.Y1 ' Failure, A's IDispatch is IX's parent and does not have Y1 method

Set B = CreateObject("Test.B")
WScript.Echo B.X1 ' Success, via IDispatchEx::InvokeEx
WScript.Echo B.Y1 ' Success, via IDispatchEx::InvokeEx