0

VBScript の代わりに特定のインターフェイスにオブジェクトをキャストする 1 つのメソッドを持つ COM クラスを作成しようとしています。

これは、私が使用しているメソッド シグネチャです。
public object GetInterface(object unknown, string iid)

メソッドが戻り値の型を次のように明示的に宣言している場合、これが可能になると思いました。
public IRequestedInterface GetInterface(object unknown, string iid)

次に、VBScript は目的のインターフェイスへの参照を取得します。

だから私はちょうどインターフェースにキャストしようとしました
return (IRequestedInterface)unknown;

残念ながら、VBScript は、要求されたインターフェイスではなく、既定のインターフェイスへの参照を取得します。

を使用してカスタムマーシャラーを作成することで、これを回避しようとしましたICustomMarshaler。メソッドが を返す
ので、これはうまくいくと思いました。MarshalManagedToNativeIntPtr

IntPtrこのため、 をインターフェイスに戻せばうまくいくと思いました
return Marshal.GetComInterfaceForObject(unknown, typeof(IRequestedInterface));
。しかし、明らかに、それは望ましい効果がありませんでした:(

それで、それが可能かどうか、そしてあなたがそれをどのように行うかを誰か知っていますか?

編集:

VBScript が常に既定のインターフェイスを取得することを受け入れなかった理由を説明するために、具体的な例 (これは不自然ではありますが) を追加すると役立つと思いました。私はまだ私の希望にしがみついています。

以下に、'TestLib.cs'、'Build.cmd'、'Test.vbs' の 3 つのファイルの内容を示します。これらは、私がまだそれが「可能である」と考える理由を示していることを願っています.

注: 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));
    }
}

ビルド.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 がジェネリックをサポートしていないことは周知のとおりです。

この制限の下で、これを達成するために考えられる他の方法はありますか?

編集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#バージョンを見つけたいと思っています。

4

1 に答える 1

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
于 2012-06-28T17:16:30.233 に答える