我正在尝试调用 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/