winapi - 如何通过 IDispatch 将 SAFEARRAY 传递给 COM 对象?

标签 winapi com variant safearray

我正在尝试调用 COM 对象的方法,其中记录的参数之一是“字节数组”。实际的声明取决于您正在查看的每种语言的文档:

  • C#语言中:

    byte[] TransformFinalBlock(
        byte[] inputBuffer,
        int inputOffset,
        int inputCount
    )
    
  • 使用C++语言;

    array<unsigned char>^ TransformFinalBlock(
        array<unsigned char>^ inputBuffer, 
        int inputOffset, 
        int inputCount
    )
    
  • 使用VB语言:

    Function TransformFinalBlock ( _
        inputBuffer As Byte(), _
        inputOffset As Integer, _
        inputCount As Integer _
    ) As Byte()
    
  • 使用F#语言:

    abstract TransformFinalBlock : 
            inputBuffer:byte[] * 
            inputOffset:int * 
            inputCount:int -> byte[] 
    

我正在使用的对象也可以使用COM访问。该对象提供了一个早期绑定(bind)接口(interface),ICryptoTransform ,它将方法声明为使用 SAFEARRAY

来自类型库:

  • 使用IDL语法

    [
      odl,
      uuid(8ABAD867-F515-3CF6-BB62-5F0C88B3BB11),
      version(1.0),
      dual,
      oleautomation,
      custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "System.Security.Cryptography.ICryptoTransform")    
    ]
    interface ICryptoTransform : IDispatch {
       ...
       [id(0x60020005)]
       HRESULT TransformFinalBlock(
                    [in] SAFEARRAY(unsigned char) inputBuffer, 
                    [in] long inputOffset, 
                    [in] long inputCount, 
                    [out, retval] SAFEARRAY(unsigned char)* pRetVal);
    };
    
  • 使用object Pascal语法:

    ICryptoTransform = interface(IDispatch)
         ['{8ABAD867-F515-3CF6-BB62-5F0C88B3BB11}']
         ...
         function TransformFinalBlock(inputBuffer: PSafeArray; inputOffset: Integer; inputCount: Integer): PSafeArray; safecall;
    end;
    

这意味着在使用早期绑定(bind)时,您必须向方法传递 SAFEARRAY 。我使用的语言支持 SafeArray API,我可以轻松地执行调用吗:

var
   inputBuffer: PSafeArray;
   xform: ICryptoTransform;
   ...
begin
   ...

   xform.TransformFinalBlock(inputBuffer, ...);
   ...
end;

这是使用类 java 语言编写的相同代码:

PSafeArray inputBuffer;
ICryptoTransform xform;

...

xform.TransformFinalBlock(inputBuffer, ...);

一切正常正常;但这不是我的问题。

Note: i'm trying drive home the point that this is a language-agnostic question, as COM is a language agnostic technology. But at some point we have to actually use a language that we will demonstrate code in. Some people confuse a language with a technology. If i knew Knuth's invented language, i would have used that.

但是后期绑定(bind)又如何呢 IDispatch

现在我们知道我们可以传递 SAFEARRAY 到 COM 对象(当使用早期绑定(bind)时),我需要解决使用后期绑定(bind)传递数组的问题

Note: The question of how to pass a SAFEARRAY to a COM object through IDispatch is useful me to in circumstances besides ICryptoTransform.

某些语言提供自动机制来通过IDispatch调用方法。运行时的接口(interface)(即后期绑定(bind))。事实上IDispatch后期绑定(bind)是为 VBScript 发明的:

Dim xform = CreateObject("System.Security.Cryptography.SHA256Managed");
Dim buffer;
o.TransformFinalBlock(buffer, 0, 8);

.NET 4.0 中添加了后期绑定(bind)编译器自动魔法:

dynamic xform = Activator.CreateInstance(Type.GetTypeFromProgID("System.Security.Cryptography.SHA256Managed", true));
xform.TransformFinalBlock(buffer, 0, 8);

Delphi 中也存在后期绑定(bind)编译器魔法:

xform: OleVariant;
buffer: OleVariant;
xform.TransformFinalBlock(buffer, 0, 8);

碰巧正在使用Dephi,并且此调用失败。

但这并不是真正的编译器魔法

VBScript、C# 动态和 Delphi 所做的事情其实并不神奇。他们只是打电话 IDispatch.Invoke :

IDispatch = interface(IUnknown)
   ['{00020400-0000-0000-C000-000000000046}']
   function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
         Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;
end;

困惑的是设置这些参数:

xform.Invoke(
      1610743820,      //DispID
      IID_NULL,        //riid (reserved for future use, must be IID_NULL)
      0,               //locale id (lcid)
      DISPATCH_METHOD, //flags
      dispParams,      //Pointer to a DISPPARAMS structure
      null,            //Pointer to the location where the result is to be stored, or NULL if the caller expects no result
      exceptInfo,      //Pointer to a structure that contains exception information
      null);           //This argument can be set to null. 

真正的技巧是dispParams结构,包含参数。

参数将是一个变体

通过 DISPPARAMS 传递的参数都是变体:

typedef struct tagDISPPARAMS {
  VARIANTARG *rgvarg;
  DISPID     *rgdispidNamedArgs;
  UINT       cArgs;
  UINT       cNamedArgs;
} DISPPARAMS;

所以无论发生什么,我的“字节数组”都将是一个变体。

一个 VARIANT 在 Win32 中,只是一个包含以下内容的联合:

  • VARTYPE vt :联合体中数据的类型。
  • 适当的工会成员,例如:

    BYTE        bVal;
    IDispatch  *pdispVal;
    SAFEARRAY  *parray;
    BYTE       *pbVal;
    IDispatch  *ppdispVal;
    SAFEARRAY  *pparray;
    VARIANT    *pvarVal;
    PVOID       byref;
    CHAR        cVal;
    

到目前为止,我一直在传递类型的变体:

vt = VT_ARRAY | VT_UI1

MSDN 记录了当您想要将 parray 联合与 VT_ARRAY | * 一起使用时必须执行的操作:

Value: VT_ARRAY | <anything>

Description: An array of data type was passed. VT_EMPTY and VT_NULL are invalid types to combine with VT_ARRAY. The pointer in pbyrefVal points to an array descriptor, which describes the dimensions, size, and in-memory location of the array.

这意味着使用 parray 成员(member):

    SAFEARRAY  *parray;

您需要设置parray指向 SAFEARRAY 的指针的成员结构:

typedef struct tagSAFEARRAY {
  USHORT         cDims;
  USHORT         fFeatures;
  ULONG          cbElements;
  ULONG          cLocks;
  PVOID          pvData;
  SAFEARRAYBOUND rgsabound[1];
} SAFEARRAY, *LPSAFEARRAY;

就我而言,我的字节数组实际上是 SAFEARRAY ,然后存储在变体中:

VARIANT *inputBuffer;
SAFEARRAY *safeArray;

//Setup our SAFEARRAY of data
safeArray.cDims = 1;
safeArray.fFeatures = FADF_HAVEVARTYPE;
safeArray.cbElements = 1;
safeArray.cbLocks = 0;
safeArray.pvData = pMyData;
safeArray.rgsabound[0].ElementCount = 1;
safeArray.rgsabound[0].LowBound = 0;

//Wrap the safearray in a variant
inputBuffer.vt = VT_ARRAY | VT_UI1; //$2011
vt.parray = safeArray;

Note: Of course i'm not crazy enough to have created this safearray myself; i'm using the SafeArrayCreate api function. i'm just demonstrating that it's all knowable, and not magic.

换句话说,我传递了一个字节数组变体

换句话说,我正在传递一个字节数组,包装在一个变体中,就像所有调用一样:

dispatch.Invoke(...);

一定是。除了后期绑定(bind)调用会抛出错误:

The parameter is incorrect.

那么我可能做错了什么?

如何将字节数组传递给后期绑定(bind) IDispatch打电话?

我的问题

如何通过 IDispatch 将 SAFEARRAY 传递给 COM 对象?

最佳答案

这应该会给你一些见解:

在调用方,C# 代码:

Foo foo = new Foo();
byte[] input = new byte[] { 1, 2, 3, 4 };
byte[] output = foo.Bar(input);
byte[] referenceOutput = new byte[] { 4, 3, 2, 1 };
Debug.Assert(Enumerable.SequenceEqual(output, referenceOutput));

Foo.Bar IDL:

interface IFoo : IDispatch
{
    [id(1)] HRESULT Bar([in] VARIANT vInput, [out, retval] VARIANT* pvOutput);
};

以及带有安全数组的 C++ (ATL) 服务器实现:

// IFoo
    STDMETHOD(Bar)(VARIANT vInput, VARIANT* pvOutput) throw()
    {
        _ATLTRY
        {
            ATLENSURE_THROW(vInput.vt == (VT_ARRAY | VT_UI1), E_INVALIDARG);
            CComSafeArray<BYTE> pInputArray(vInput.parray);
            ATLASSERT(pInputArray.GetDimensions() == 1);
            const ULONG nCount = pInputArray.GetCount();
            CComSafeArray<BYTE> pOutputArray;
            ATLENSURE_SUCCEEDED(pOutputArray.Create(nCount));
            for(ULONG nIndex = 0; nIndex < nCount; nIndex++)
                pOutputArray[(INT) nIndex] = pInputArray[(INT) ((nCount - 1) - nIndex)];
            ATLASSERT(pvOutput);
            VariantInit(pvOutput);
            CComVariant vOutput(pOutputArray.Detach());
            ATLVERIFY(SUCCEEDED(vOutput.Detach(pvOutput)));
        }
        _ATLCATCH(Exception)
        {
            return Exception;
        }
        return S_OK;
    }

来源:Trac , Subversion - 当心 Visual Studio 2012。

关于winapi - 如何通过 IDispatch 将 SAFEARRAY 传递给 COM 对象?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11977806/

相关文章:

vba - 将函数结果写入变量,其中结果可以是对象

c++ - 如何在 win32 中更改窗口的背景图像?

c++ - 创建指向特殊文件夹(如控制面板或计算机管理)的快捷方式

windows - 是否可以在 Windows Azure 上部署进程外 COM 服务器并更改其激活权限?

c++ - 如何绑定(bind)变体apply_visitor?

c++ - 如何从 std::visit 返回常量?

c - ShellExecute 在哪里找到 exe 文件

c - 在c中打开注册表项

.net - VB6 如何阻止 COM 事件的传递(.net -> VB6 via COM)?

c# - 通过 .NET 手动调用 COM 对象的方法