Prepare to pull your hair out. Using arrays in COM interop is one of the biggest areas of problems.
From a COM perspective, there are five types of arrays:
Safe array: A self-described array that can contain any type capable of being placed in a COM VARIANT. These arrays are typically converted to a System.Array type and vice versa.
Fixed-length array: A basic array where you define the size of the array in the method definition. A major limitation of this type of array is that the Interop Marshaler supports only one-dimensional fixed-length arrays.
HRESULT Test([in] int MyArray[10]
Varying array: Similar to a fixed-length array, but allows you to pass only a portion slice of an array. These arrays are also limited to only one dimension.
HRESULT VarArray [in, first_is(2), last_is(6)] short Array[1024]) HRESULT VarArray [in, first_is(2), length_is(5)] short Array[1024]) HRESULT VarArray([in] long cActual, [in, length_is(cActual)] short Array[1024])
Conformant array: An array with a dynamic capacity.
HRESULT ConArray([in] long cElems, [in, size_is(cElems)] short Array[*]) HRESULT ConArray([in] long cElems, [in, size_is(cElems)] short Array[]) HRESULT ConArray([in] long cElems, [in, size_is(cElems)] short* Array)
Conformant varying array: Combines the ability to pass a portion of the array while allowing for it to be dynamic.
HRESULT ConVarArray([in] cMax, [in] cActual, [in, size_is(cMax), length_is(cActual)] short* Array) HRESULT ConVarArray([in] cMax, [in] cActual, [in, size_is(cMax), length_is(cActual)] short Array[*]) HRESULT ConVarArray([in] cMax, [in] cActual, [in, size_is(cMax), length_is(cActual)] short Array[])
The following example demonstrates the managed code for passing an array of pointers.
public class PassingAnArrayByExample
{
//Create an array object that will hold X number of elements of Y objects.
public Array T = Array.CreateInstance((typeof(MSXML2.IXMLDOMDocument)),20);
public TestArrayLib.TestingClass MyObject = new TestArrayLib.TestingClass();
public PassingAnArrayByExample()
{
for (int i=0; i<20 ;i++)
{
//Fill the array members with the objects desired,
//so at this point we have an array of type
//System.Array.
SAFEARRAY(IDispatch))
//These will be passed to the COM object as a SafeArray(IDispatch).
//You can then pull out items and call QI to get the interface you
want.
T.SetValue((new MSXML2.DOMDocumentClass()),i);
Console.WriteLine( T.GetValue(i) );
}
MyObject.Testme( ref T);
}
In unmanaged code, the IDL definition of the method is as follows:
[id(1), helpstring("method Testme")] HRESULT Testme([in,out]
SAFEARRAY(IDispatch*)* m);
STDMETHODIMP CTesting::Testme(SAFEARRAY** m)
{
IDispatch* pDispatch;long ElementNumber = 0;
HRESULT hr = 0;
//This will pull out by element of the array the object's IDispatch interface.
hr = SafeArrayGetElement(*m, &ElementNumber, &pDispatch);
return S_OK;
}
The following example demonstrates the managed client code for passing an entire C-style array to COM code.
static void Main(string[] args)
{
int Size = 5;
byte[] Buffer = new byte[Size + 1];
Buffer[0] = 65;
Buffer[1] = 66;
Buffer[2] = 67;
COMTestClass Test = new COMTestClass();
Test.SetMessage(Buffer, Buffer.Length);
Console.WriteLine(Buffer[0]);
}
The unmanaged code is as follows:
STDMETHODIMP CCOMTest::SetMessage(BYTE* pBuffer, long size)
{
*pBuffer = 68;
return S_OK;
}
The IDL code looks like this:
[id(1), helpstring("method SetMessage")] HRESULT SetMessage
([in] BYTE* pBuffer, [in] long size);
You must modify the Microsoft Intermediate Language (MSIL) code generated by Visual Studio or tlbimp.exe because a C-style array parameter looks like a ref parameter, but it is actually treated as a by-value parameter. To modify the IL code, run the following:
ildasm /out:Interop.InteropTesterCOMLib.il Interop.InteropTesterCOMLib.dll
Then modify the method signature twice in the IL code. Open the IL code with Notepad and change the method signature from this:
instance void SetMessage([in] unsigned int8& pBuffer, [in] int32 size) runtime managed internalcall
to this (you will need to do this twice in the IL code):
instance void SetMessage([in] unsigned int8 [] marshal([+1]) pBuffer, [in] int32 size) runtime managed internalcall
Save the results.
Then compile the IL code:
ilasm /DLL Interop.InteropTesterCOMLib.IL /RESOURCE= Interop.InteropTesterCOMLib.res
Finally, add the new DLL as a reference.
The following example demonstrates setting security on a COM interface.
using System; using System.Runtime.InteropServices; using System.Reflection; namespace Client.Chapter_10___COM_and_.NET_Interoperability { public struct COAUTHIDENTITY { [MarshalAs(UnmanagedType.LPWStr)] public string User; public uint UserLength; [MarshalAs(UnmanagedType.LPWStr)] public string Domain; public uint DomainLength; [MarshalAs(UnmanagedType.LPWStr)] public string Password; public uint PasswordLength; public uint Flags; }; class Class1 { //Various constants const uint EOAC_NONE = 0; const uint SEC_WINNT_AUTH_IDENTITY_UNICODE = 2; const uint RPC_C_AUTHN_WINNT = 10; const uint RPC_C_AUTHZ_NONE = 0; const uint RPC_C_AUTHN_LEVEL_DEFAULT = 0; const uint RPC_C_IMP_LEVEL_IMPERSONATE = 3; [DllImport("Ole32.dll", CharSet = CharSet.Auto)] public static extern int CoSetProxyBlanket (IntPtr pProxy, uint dwAuthnSvc, uint dwAuthzSvc, uint pServerPrincName, uint dwAuthLevel, uint dwImpLevel, IntPtr pAuthInfo, uint dwCapabilities); [STAThread] static void Main(string[] args) { int hr; Guid CLSID = new Guid("1ACD2158-6E0E-48B6-A01C-ACF1CAC48580"); string machineName = "MyPCName"; System.Type typeInfo = Type.GetTypeFromCLSID(CLSID,machineName,true); Console.WriteLine("Type.GetTypeFromCLSID successful"); Console.ReadLine(); object objDCOM = Activator.CreateInstance(typeInfo); Console.WriteLine("Activator.CreateInstance successful"); Console.ReadLine(); COAUTHIDENTITY Auth = new COAUTHIDENTITY(); IntPtr pAuth = Marshal.AllocCoTaskMem(28); Auth.User = "myusername"; Auth.UserLength = (uint)Auth.User.Length; Auth.Domain = "mydomain"; Auth.DomainLength = (uint)Auth.Domain.Length; Auth.Password = "mypassword"; Auth.PasswordLength = (uint)Auth.Password.Length; Auth.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; Marshal.StructureToPtr(Auth, pAuth, false); hr = CoSetProxyBlanket (Marshal.GetIUnknownForObject(objDCOM), //pProxy RPC_C_AUTHN_WINNT, //dwAuthnSvc RPC_C_AUTHZ_NONE, //dwAuthzSvc 0, // pServerPrincName RPC_C_AUTHN_LEVEL_DEFAULT, //dwAuthnLevel RPC_C_IMP_LEVEL_IMPERSONATE, //dwImpLevel pAuth, //pAuthInfo EOAC_NONE); //dwCapabilities Console.WriteLine( "CoSetProxyBlanket for IUnknown returned " + hr); if (hr != 0) return; hr = CoSetProxyBlanket (Marshal.GetIDispatchForObject(objDCOM), //pProxy RPC_C_AUTHN_WINNT, //dwAuthnSvc RPC_C_AUTHZ_NONE, //dwAuthzSvc 0, // pServerPrincName RPC_C_AUTHN_LEVEL_DEFAULT, //dwAuthnLevel RPC_C_IMP_LEVEL_IMPERSONATE, //dwImpLevel pAuth, //pAut EOAC_NONE); //dwCapabilities Console.WriteLine( "CoSetProxyBlanket for IDispatch returned " + hr); if (hr != 0) return; object actualReturnValue = typeInfo.InvokeMember("TestMe", BindingFlags.Default|BindingFlags.InvokeMethod, null,objDCOM,null,null,null,null); Console.WriteLine("Done"); } } }