我正在尝试调用 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访问。该对象提供了一个早期绑定接口 ,ICryptoTransform
该接口将方法声明为 using 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); };
使用对象 Pascal语法:
ICryptoTransform = interface(IDispatch) ['{8ABAD867-F515-3CF6-BB62-5F0C88B3BB11}'] ... function TransformFinalBlock(inputBuffer: PSafeArray; inputOffset: Integer; inputCount: Integer): PSafeArray; safecall; end;
这意味着在使用早期绑定时,您必须传递方法 a SAFEARRAY
。我使用的语言支持 SafeArray API,我是否可以轻松地执行调用:
var
inputBuffer: PSafeArray;
xform: ICryptoTransform;
...
begin
...
xform.TransformFinalBlock(inputBuffer, ...);
...
end;
这是类似 java 语言的相同代码:
PSafeArray inputBuffer;
ICryptoTransform xform;
...
xform.TransformFinalBlock(inputBuffer, ...);
一切正常;但这不是我的问题。
注意:我试图强调这是一个与语言无关的问题,因为COM 是一种与语言无关的技术。但是在某些时候,我们必须实际使用一种语言来演示代码。有些人将语言与技术混淆了。如果我知道 Knuth 发明的语言,我会使用它。
但是后期绑定IDispatch
呢?
现在我们知道我们可以将 a 传递SAFEARRAY
给 COM 对象(使用early-binding时),我需要解决使用late-binding传递数组的问题。
注意:除此之外,如何通过 IDispatch 将 SAFEARRAY 传递给 COM 对象的问题对我很有用
ICryptoTransform
。
IDispatch
一些语言提供了在运行时通过接口调用方法的自动机制(即后期绑定)。事实上IDispatch
,后期绑定是为 VBScript 发明的:
Dim xform = CreateObject("System.Security.Cryptography.SHA256Managed");
Dim buffer;
o.TransformFinalBlock(buffer, 0, 8);
.NET 4.0 中添加了后期绑定编译器自动魔法:
dynamic xform = Activator.CreateInstance(Type.GetTypeFromProgID("System.Security.Cryptography.SHA256Managed", true));
xform.TransformFinalBlock(buffer, 0, 8);
Delphi 中也存在后期绑定编译器魔法:
xform: OleVariant;
buffer: OleVariant;
xform.TransformFinalBlock(buffer, 0, 8);
我碰巧在使用 Dephi,这个调用失败了。
但这并不是真正的编译器魔法
VBScript、C# dynamic 和 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
包含参数的结构。
参数将是一个变体
通过 DISPARAMS 传递的参数都是变体:
typedef struct tagDISPPARAMS {
VARIANTARG *rgvarg;
DISPID *rgdispidNamedArgs;
UINT cArgs;
UINT cNamedArgs;
} DISPPARAMS;
所以无论发生什么,我的“字节数组”都将是一个变体。
A 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 union时必须执行的操作VT_ARRAY | *
:
价值:
VT_ARRAY | <anything>
说明:传递了一个数据类型的数组。VT_EMPTY 和 VT_NULL 是与 VT_ARRAY 组合的无效类型。pbyrefVal中的指针指向一个数组描述符,它描述了数组的维度、大小和内存中的位置。
这意味着使用该parray
成员:
SAFEARRAY *parray;
您需要将parray
member 设置为指向SAFEARRAY
结构的指针:
typedef struct tagSAFEARRAY {
USHORT cDims;
USHORT fFeatures;
ULONG cbElements;
ULONG cLocks;
PVOID pvData;
SAFEARRAYBOUND rgsabound[1];
} SAFEARRAY, *LPSAFEARRAY;
就我而言,我的字节数组实际上是 a 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;
注意:当然我还没有疯狂到自己创建了这个安全数组;我正在使用
SafeArrayCreate
api 功能。我只是在证明这一切都是可知的,而不是魔法。
换句话说,我传递了一个字节的数组变体
换句话说,我正在传递一个字节数组,包装在一个变体中,因为所有调用:
dispatch.Invoke(...);
一定是。除了后期绑定调用抛出错误:
The parameter is incorrect.
那么我可能做错了什么?
如何将一个字节数组传递给后期绑定 IDispatch
调用?
我的问题
如何通过 IDispatch 将 SAFEARRAY 传递给 COM 对象?