question

haton-9060 avatar image
0 Votes"
haton-9060 asked BioLiongLim-5256 commented

COM interop - pass a char[] array

I want to call a .NET method through .Invoke with a char[] parameter:

StringBuilder.Insert(int index, char[] value)

I reasoned that from C++, I should pass a safearray of VT_UI2, with variant type VT_ARRAY+VT_UI2, and that would be marshaled to a char[]. But I get an "incorrect parameter" error. Any recommendations?
Thanks

c++dotnet-runtime
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

RLWA32-6355 avatar image
0 Votes"
RLWA32-6355 answered RLWA32-6355 edited

I think the .Net COM interop has some kind of a problem with marshaling the SAFEARRAY of VT_UI2 to char[]. You could work around this on the .Net side by using a ushort[] array and converting to string for use with StringBuilder.Insert.

· 2
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Thanks for the feedback! Unfortunately i have no control on the .net side: I'm just calling the framework assemblies.
For this specific case I can work my way around by calling another overload of .Insert with a string argument. But in general cases i would like to find a way to correctly call methods that want a char[] parameter.

0 Votes 0 ·

I don't think its a matter of correctly calling IDispatch::Invoke. As far as I can tell the .Net COM interop is improperly returning E_INVALIDARG for a call to IDispatch:;:Invoke that is passing correct parameters.

If you look at the type library generated for a .Net COM Server you can see that it contains exactly the same method parameters for an interface method that has an [in] parameter of ushort[] or char[]. Each method in the type library uses [in] SAFEARRAY(unsigned short) for the parameter. Pass a SAFEARRAY of VT_UI2 to an interface method expecting ushort[] and all is well. Pass the same SAFEARRAY to an interface method expecting char[] and you get E_INVALIDARG, even though the call is properly structured and conforms to the generated type library.

0 Votes 0 ·
haton-9060 avatar image
0 Votes"
haton-9060 answered RLWA32-6355 commented

Yes, that is what I am observing.
Is it worth reporting the problem to Microsoft? I guess i am not the first one to stumble upon this.

· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Reporting the issue is probably a good idea. But I can't say for sure how that should be done. Perhaps someone from MS can comment here to provide you with appropriate guidance.

0 Votes 0 ·
haton-9060 avatar image
0 Votes"
haton-9060 answered RLWA32-6355 commented

Follow-up: I still try to .Invoke a .NET Framework method which wants a char[] parameter. I tried a sly hack: generate the char[] on the managed side.
I wrote a small C# class which generates a new char[] in method X() and returns a pointer by calling Marshal.GetIUnknownForObject.
I call this method X() from the unmanaged side through COM interop, and get the pointer result. To test the result, I cast this pointer to an Object and pass it again to the managed side to methods Y() and Z(). I hoped this object would be received as a char[] array but the results puzzle me:
- the object isn't accepted by method Y() which wants a char[] array parameter (Incorrect Parameter),
- it is accepted by method Z() which wants an Object parameter,
- Console.WriteLine(a.GetType()) prints 'System.Char[]',
- the parameter can be cast to a char[] array and the contents can be read correctly!

I haven't reach my goal but maybe I am on to something? I would be super grateful for any advice.

--C#
using System; using System.IO; using System.Runtime.InteropServices;
public class XHelper {
public IntPtr X() {
char[] a=new char[2] {'A', 'B'};
return Marshal.GetIUnknownForObject(a);
}
public void Y(char[] a){ // fails with INVALID PARAMETER
Console.WriteLine(a.GetType());
Console.WriteLine(((char[])a)[0]);
}
public void Z(Object a){ //parameter is accepted as Object
Console.WriteLine(a.GetType()); //DISPLAYS System.Char[]
Console.WriteLine(((char[])a)[0]); //DISPLAYS A (correct first character)
}
}















· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Since you said earlier that you didn't have any control over the .net side I'm not sure how the experiment is relevant.

Anyway, the earlier issue was related to using IDispatch from unmanaged C++ to pass a SAFEARRAY of VT_UI2 to a managed COM server interface method that wants char[]. Since the posted code doesn't contain any unmanaged C++ I don't understand what you are trying to achieve.

0 Votes 0 ·
haton-9060 avatar image
0 Votes"
haton-9060 answered RLWA32-6355 commented

I don't have control about the system methods I call.
But I can run some code bits of my own in parallel.

The unmanaged part looks like:

//----
XHelper helper=new XHelper;

VARIANT v;
dispidX=...(dispid of X method)
dispidY=...(dispid of Y method)
dispidZ=...(dispid of Z method)
helper->invoke (dispidX, IID_NULL, 0x400, DISPATCH_METHOD, null, &v, null, null);
//result variant is ant IntPtr, with type VT_UINT

//prepare Invoke arguments
VARIANT *args = new VARIANT[1];
args[0]=v;
args[0].vt = VT_UNKNOWN; //force to interpret v as an Object

//invoke Y
DISPPARAMS dp;
dp.cArgs=1;
dp.rgvarg = args;
helper->invoke (dispidY, IID_NULL, 0x400, DISPATCH_METHOD, &dp, &v, null, null);
//---

(This experiment is a first step. To be of any use, the next step would be to create the char[] array on the managed side based on a pointer provided from the unmanaged side).

· 3
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Well, if you can use your own code why not just pass a string and convert it to char[]?

0 Votes 0 ·

Indeed, moving the method call to the managed side is an option if all else fails, but I would need to restructure other parts of the code.

Meanwhile I noticed a funny effect:
helper->invoke of Y() fails because of a wrong parameter.
However, helper.GetType()->InvokeMember of Y() does work, the parameter is seen as a valid char[]
I can't imagine why it is so, but it gives me a way!

0 Votes 0 ·

How is this an improvement over much simpler ways to convert ushort[] (from unmanaged code SAFEARRAY) to char[]?

0 Votes 0 ·
haton-9060 avatar image
0 Votes"
haton-9060 answered RLWA32-6355 edited

This is an improvement because it lets me stick to my tactics of calling invoke (or invokemember) from the unmanaged side.
By the way, as you mention simpler ways, is there a way in C# to cast/convert ushort[] to char[] without copying the data?

· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Help me to understand. Please post an example that passes the SAFEARRAY of UI2 from unmanaged code to a C# COM server using your technique so that the C# COM servers sees a char[] array.

If you do post an example, please use the "code sample" button. Thanks.

0 Votes 0 ·
BioLiongLim-5256 avatar image
0 Votes"
BioLiongLim-5256 answered BioLiongLim-5256 commented

Hello haton-9060,

I think the problem is with that the interop marshaler is not able to convert the array of ushorts to an array of char. This is possibly because there is no direct mapping from a VARIANT VT_UI2 to a Managed Char Type.

I have tried my hand at your code by writing a Custom Marshaler (named CharArrayMarshaler) and then specifying that the "a" parameter be marshaled with my Custom Marshaler as follows :

public void Y ([In][MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(CharArrayMarshaler))] char[] a)

but CharArrayMarshaler is not even invoked.

I have not investigated why the Custom Marshaler is not invoked. But I suspect that this may have something to do with the call being made through IDispatch Invoke(). I'm not sure at this time.

I then implementing the IDispatch interface for XHelper via ICustomQueryInterface. This essentially makes XHelper bypass the Interop Marshaler and allows it to handle Invoke() calls on its own.

The XHelper code that I wrote goes something like the following :

     [ComVisible(true)]
     [Guid("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")]
     [ProgId("CSDLLServer.XHelper")]
     [ClassInterface(ClassInterfaceType.AutoDispatch)]
     public class XHelper : ICustomQueryInterface, IDispatch
     {
         ...
     }

My Invoke() method for Y() is as follows :

         public void Invoke
         (
             int dispId,
             ref Guid riid,
             int lcid,
             ushort wFlags,
             ref System.Runtime.InteropServices.ComTypes.DISPPARAMS pDispParams,
             out object result,
             IntPtr pExcepInfo,
             IntPtr puArgErr
         )
 {
    ...
    
                 // 2 is the dispid of Y()
                 case 2:
                     {
                         int i = 0;
                         result = null;
    
                         // We create an array of pointers to the VARIANTs inside the 
                         // DISPPARAMS.
                         IntPtr[] pVariantArray = new IntPtr[pDispParams.cArgs];
    
                         IntPtr pVariantTemp = pDispParams.rgvarg;
                         for (i = 0; i < pDispParams.cArgs; i++)
                         {
                             pVariantArray[i] = pVariantTemp;
                             pVariantTemp += Marshal.SizeOf(typeof(VariantStructGeneric));
                         }
    
                         // We access the reference to the SAFEARRAY in the first VARIANT and 
                         // create a managed array from it. Note that this is an array of UInt16.
                         UInt16[] uiArray = Marshal.GetObjectForNativeVariant<UInt16[]>(pVariantArray[0]);
    
                         char[] chArray = new char[uiArray.Length];
    
                         // We convert the UInt16 array to a Char array.
                         for (i = 0; i < uiArray.Length; i++)
                         {
                             chArray[i] = Convert.ToChar(uiArray[i]);
                         }
    
                         Y(chArray);
    
                         break;
                     }
    ...
 }

This worked well since the conversion from a UInt16 array to a Char array is done manually by my Invoke() method instead of relying on the Interop Marshaler.

I think this may help you achieve your objectives. For more information on the ICustomQueryInterface interface, see : CustomQueryInterface


I have also written an article with some code examples on ICustomQueryInterface and IDispatch. See :

passing-a-reference-to-a-safearray-as-parameter-to-a-managed-com-event-handler-part-3




Hope this will be helpful to you.






· 3
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

@BioLiongLim-5256 I read your post with great interest. Unfortunately, the OP indicated that the unamanged C/C++ COM code was calling into managed code that was not within the OP's control.

0 Votes 0 ·

Hello RLWA32-6355,

Noted and thanks for the message.

It's unfortunate then that the managed code is out of the control of the OP. One workaround would be to created a wrapper class around the target managed class if this is possible.

  • Bio.

0 Votes 0 ·

Hello RLWA32-6355,

  1. Just wish to inform you that I have written an article on implementing an IDispatch interface for a C# class via ICustomQueryInterface that is based on the issue raised in the OP.

  2. Here is the link to my article : A Use Case for ICustomQueryInterface.

  3. A link to the Source Codes on GitHub is provided.

  4. I hope it will be useful to all.

  • Bio.


0 Votes 0 ·