问题
i am trying to call a method of COM object, where one of the documented parameters is an "array of bytes". The actual declartion depends on the per-language documentation you're looking at:
in C# language:
byte[] TransformFinalBlock( byte[] inputBuffer, int inputOffset, int inputCount )
in C++ language;
array<unsigned char>^ TransformFinalBlock( array<unsigned char>^ inputBuffer, int inputOffset, int inputCount )
in VB language:
Function TransformFinalBlock ( _ inputBuffer As Byte(), _ inputOffset As Integer, _ inputCount As Integer _ ) As Byte()
in F# language:
abstract TransformFinalBlock : inputBuffer:byte[] * inputOffset:int * inputCount:int -> byte[]
The object i'm using can also be accessed using COM. The object provides an early binding interface, ICryptoTransform
, which declares the method as using SAFEARRAY.
From the type library:
using IDL syntax
[ 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); };
using object Pascal syntax:
ICryptoTransform = interface(IDispatch) ['{8ABAD867-F515-3CF6-BB62-5F0C88B3BB11}'] ... function TransformFinalBlock(inputBuffer: PSafeArray; inputOffset: Integer; inputCount: Integer): PSafeArray; safecall; end;
This means that when using early-binding you must pass the method a SAFEARRAY
. The language i use has support for SafeArray APIs, can i can perform the call easily enough:
var
inputBuffer: PSafeArray;
xform: ICryptoTransform;
...
begin
...
xform.TransformFinalBlock(inputBuffer, ...);
...
end;
Here's the same code in a java-like language:
PSafeArray inputBuffer;
ICryptoTransform xform;
...
xform.TransformFinalBlock(inputBuffer, ...);
And everything works fine; but that's not my question.
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.
But what about late-binding IDispatch
?
Now that we know we can pass a SAFEARRAY to a COM object (when using early-binding), i need to solve the problem of passing an array using late-binding.
Note: The question of how to pass a SAFEARRAY to a COM object through IDispatch is useful me to in circumstances besides
ICryptoTransform
.
- passing an array of BSTR to MSHTML control
- passing an array of byte to other COM objects
Some languages provide automatic mechanisms to invoke methods through an IDispatch
interface at run-time (i.e. late-binding). In fact IDispatch
late binding was invented for VBScript:
Dim xform = CreateObject("System.Security.Cryptography.SHA256Managed");
Dim buffer;
o.TransformFinalBlock(buffer, 0, 8);
And late-binding compiler auto-magic was added in .NET 4.0:
dynamic xform = Activator.CreateInstance(Type.GetTypeFromProgID("System.Security.Cryptography.SHA256Managed", true));
xform.TransformFinalBlock(buffer, 0, 8);
Late-binding compiler magic also existed in Delphi:
xform: OleVariant;
buffer: OleVariant;
xform.TransformFinalBlock(buffer, 0, 8);
i happen to be using Dephi, and this call fails.
But it's not really compiler magic
It's not really magic what VBScript, C# dynamic, and Delphi are doing. They're just calling 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;
The mess is setting up these parameters:
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.
The real trick is the dispParams
structure, that contains the arguments.
The argument will be an variant
The arguments that get passed through DISPPARAMS are all variants:
typedef struct tagDISPPARAMS {
VARIANTARG *rgvarg;
DISPID *rgdispidNamedArgs;
UINT cArgs;
UINT cNamedArgs;
} DISPPARAMS;
So no matter what happens, my "array of bytes" is going to be a variant.
A VARIANT, in Win32, is simply a union that contains:
VARTYPE vt
: The type of data in the union.the appropriate union member, e.g.:
BYTE bVal; IDispatch *pdispVal; SAFEARRAY *parray; BYTE *pbVal; IDispatch *ppdispVal; SAFEARRAY *pparray; VARIANT *pvarVal; PVOID byref; CHAR cVal;
Up to now i have been passing a variant of type:
vt = VT_ARRAY | VT_UI1
MSDN documents what you must do when you want to use the parray union with 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.
What this means is that using the parray
member:
SAFEARRAY *parray;
You need to set parray
member to a pointer to a SAFEARRAY structure:
typedef struct tagSAFEARRAY {
USHORT cDims;
USHORT fFeatures;
ULONG cbElements;
ULONG cLocks;
PVOID pvData;
SAFEARRAYBOUND rgsabound[1];
} SAFEARRAY, *LPSAFEARRAY;
In my case, my array of bytes is actually a SAFEARRAY
, which is then being stored in a variant:
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.
In other words i pass an array variant of bytes
In other words i am passing an array of bytes, wrapped in a variant, as all calls to:
dispatch.Invoke(...);
must be. Except that the late-binding call throws an error:
The parameter is incorrect.
So what am i possibly doing wrong?
How does one pass an array of byte to a late-bound IDispatch
call?
My Question
How to pass SAFEARRAY to COM object through IDispatch?
回答1:
This should give you some insight:
On the caller side, C# code:
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));
The Foo.Bar
IDL:
interface IFoo : IDispatch
{
[id(1)] HRESULT Bar([in] VARIANT vInput, [out, retval] VARIANT* pvOutput);
};
And C++ (ATL) server implementation with safe arrays:
// 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;
}
Source: Trac, Subversion - beware Visual Studio 2012.
来源:https://stackoverflow.com/questions/11977806/how-to-pass-safearray-to-com-object-through-idispatch