none
Passing an array of Structs to a COM Interface

    Question

  • I have a COM interface with an IDL file with the following declared:

    typedef [uuid(D7B6C495-FFF3-11E0-8A39-08002700D831)]
    struct PORT_CONFIG
    {
      unsigned char  rack;
      unsigned short port;
      unsigned char  offset;
    } PORT_CONFIG;
    
    [object, uuid(D7B6C492-FFF3-11E0-8A39-08002700D831), dual, nonextensible, pointer_default(unique)]
    interface IMED704 : IDispatch
    {
      [id(5), helpstring("method PortConfig")] HRESULT PortConfig([in] SAFEARRAY(PORT_CONFIG) portCfg, [in, defaultvalue(-1)] VARIANT_BOOL clearInputs);
    };
    

    Now in my C# program I am trying to call the PortConfig method:

    PORT_CONFIG[] portCfg = new PORT_CONFIG[12];
    
    // ...Initialize code goes here
    
    dig704.PortConfig(portCfg, true);
    

    However the program throws an exception when the method is called.  What am I doing wrong?

    The exception is:

        The parameter is incorrect. (Exception from HRESULT: 0x80070057 (E_INVALIDARG))

    More information if I try the following:

        IntPtr pointer = Marshal.GetITypeInfoForType(typeof(PORT_CONFIG));

    The exception that I receive is:

        The specified type must be visible from COM.\r\nParameter name: t


    Friday, May 18, 2012 12:36 PM

Answers

  • This is a little more complicated than it looks at first. Your code is not just taking an array, it's expecting a SAFEARRAY - which is itself a struct containing the elements of the array. So you have a struct with an array of custom UDTs inside of it. The marshaller doesn't know how to create that SAFEARRAY, so you have to manually tell it how.

    Full disclaimer, I have never had to solve this problem myself, so I'm working off of what others have found and documented. One possibility is just to set the Embed Interop Types property of the COM server to False (see this link). A second option is to change your interface so you take a SAFEARRAY of something like a variant or BSTR as a parameter instead (see the first answer here). If neither of those will work for you, it may get harder.

    In a nutshell, you have to go through several steps. Load the type library, get the IRecordInfo interface for your PORT_CONFIG struct, call SafeArrayCreateEx to create an instance of your safearray, populate the array with data, then pass it in. For a full walkthrough on how to do this from beginning to end, see this article. The article is written in C++, but it should just be a matter of translating it into C#


    Check out My Blog. Now updated to actually work!

    Friday, May 18, 2012 1:59 PM
  • Ok I was stumped as everything looks fine.  I tried it at home and I've figured out the problem but I can't explain why.  For some reason the interop layer fails on SAFEARRAY parameters when the interop type is embedded (the default in VS2010).  To work around this issue right-click the reference to the COM object and set Embed Interop Type to False.  Then try running your program again and see if the problem goes away.  It did for me.

    Michael Taylor - 5/18/2012
    http://msmvps.com/blogs/p3net

    Saturday, May 19, 2012 3:07 AM

All replies

  • Quick random shot in the dark here.

    I often see people add the following attributes to their struct declaration:

    [ComVisible(true)]
    [StructLayout(LayoutKind.Sequential)]

    Friday, May 18, 2012 1:25 PM
  • Thank you for your reply.

    I think your suggestion is for the reverse situation.  You use the ComVisible when the struct is declared in C# and is to be exported to the COM interface.

    My struct is declared in the COM interface and is being exported to C#.

    Still I did try it and my COM project would not compile when I tried to add those declartions:

    [ComVisible(true)]
    [StructLayout(LayoutKind.Sequential)]
    typedef [uuid(D7B6C495-FFF3-11E0-8A39-08002700D831)]
    struct PORT_CONFIG
    {
      unsigned char  rack;
      unsigned short port;
      unsigned char  offset;
    } PORT_CONFIG;
    error MIDL2025: syntax error : expecting ] or , near "ComVisible"
    error MIDL2026: cannot recover from earlier syntax errors; aborting compilation
    Friday, May 18, 2012 1:49 PM
  • This is a little more complicated than it looks at first. Your code is not just taking an array, it's expecting a SAFEARRAY - which is itself a struct containing the elements of the array. So you have a struct with an array of custom UDTs inside of it. The marshaller doesn't know how to create that SAFEARRAY, so you have to manually tell it how.

    Full disclaimer, I have never had to solve this problem myself, so I'm working off of what others have found and documented. One possibility is just to set the Embed Interop Types property of the COM server to False (see this link). A second option is to change your interface so you take a SAFEARRAY of something like a variant or BSTR as a parameter instead (see the first answer here). If neither of those will work for you, it may get harder.

    In a nutshell, you have to go through several steps. Load the type library, get the IRecordInfo interface for your PORT_CONFIG struct, call SafeArrayCreateEx to create an instance of your safearray, populate the array with data, then pass it in. For a full walkthrough on how to do this from beginning to end, see this article. The article is written in C++, but it should just be a matter of translating it into C#


    Check out My Blog. Now updated to actually work!

    Friday, May 18, 2012 1:59 PM
  • I assume that you generated a PIA for the COM interface by adding a reference to the COM component in your project correct?  If so then everything should be set up properly.  All you need to do is instantiate an instance of the PIA class and pass it the correct data.

    Dump the signature and structure definition from the PIA so we can see what it generated. 

    Michael Taylor - 5/18/2012
    http://msmvps.com/blogs/p3net

    Friday, May 18, 2012 2:00 PM
  • Hi, 

    Same topic has been discsussed over here

    http://social.msdn.microsoft.com/Forums/en-US/clr/thread/6641abfc-3a9c-4976-a523-43890b2b79a2

    and

    http://msdn.microsoft.com/en-us/library/z6cfh6e6(v=VS.80).aspx#cpcondefaultmarshalingforarraysanchor4


    If this post answers your question, please click "Mark As Answer". If this post is helpful please click "Mark as Helpful".

    Friday, May 18, 2012 2:15 PM
  • I hope that this is what you are looking for.  I get this information by right clicking on PORT_CONFIG in my C# program and selecting Go to Definition.

    #region Assembly Interop.MED704APILib.dll, v4.0.30319
    // C:\Shuttle Box\obj\Debug\Interop.MED704APILib.dll
    #endregion
    
    using System;
    using System.Runtime.InteropServices;
    
    namespace MED704APILib
    {
      [Guid("D7B6C495-FFF3-11E0-8A39-08002700D831")]
      public struct PORT_CONFIG
      {
        public byte offset;
        public ushort port;
        public byte rack;
      }
    }


    Friday, May 18, 2012 5:19 PM
  • What about the PortConfig method you are trying to call? 

    The call to GetITypeInfo won't work with PORT_CONFIG.  It isn't necessary anyway.

    My initial hunch is that the COM call is working but the COM server doesn't like one of the values in your array so it is generating an exception.  You'd have to look at your values you're passing and make sure the COM server is going to like each of them.  It could be something as simple as a port that is set to 0 or an offset that is invalid.  I don't believe any of this is specifically related to the marshaling itself.  I believe it is just bad data but you'd have to verify.

    Michael Taylor - 5/18/2012
    http://msmvps.com/blogs/p3net

    Friday, May 18, 2012 5:25 PM
  • I included the GetITypeInfo call because it gave a different exception error that might be useful.

    I think the problem is that my IPA does not have the [ComVisible(true)] in it.  The IPA is generated by the COM interface.

    What do I need to change so that when I compile my COM interface it puts the [ComVisible(true)] in the IPA?

    Gary

    Friday, May 18, 2012 5:35 PM
  • That is incorrect.  The errors are unrelated.  If you want to expose a type to COM you mark it with ComVisible.  In this case the type is coming from COM so the generated PIA will contain the right information.  You don't need to do anything with the PIA if it was generated by adding the COM component as a reference to your project.

    The reason the second error is not relevant is because you are calling GetITypeInfoFromType which attempts to build the COM ITypeInfo information from a managed type.  If you were to pass it a COM-visible class or interface it'll work.  Nothing else would or even makes sense.  Hence calling it with the struct is something you'd never do and the error you got is expected and correct.  There is no COM type information for the struct because structs aren't called directly by COM, they are only used as parameters.

    I'm reasonably confident the issue is with the data you're passing to the COM method.  The remote server is getting the data, enumerating the array and finding a value it doesn't like.  The COM server then generates a standard COM exception saying the argument you passed it contains bad data.  This is no different than you performing argument validation in a method call.  E_INVALIDARG is the standard COM error code for "I got bad data from you". 

    Take a look at all the data you're passing to the method and ensure that each data point contains a valid value.  If necessary start with a single data element and work your way up until it fails.  If you have the COM server code then you can also debug the COM server side as well.  This would make it a lot easier to see what is going on.

    Friday, May 18, 2012 6:00 PM
  • I modified the code as follows to make sure that I was passing valid values.

    PORT_CONFIG[] portCfg = new PORT_CONFIG[1];
    
    portCfg[0].rack   = 1;
    portCfg[0].port   = 792;
    portCfg[0].offset = 0;
    
    dig704.PortConfig(portCfg, true);

    I get the exact same error message.

    The parameter is incorrect. (Exception from HRESULT: 0x80070057 (E_INVALIDARG))

    I wrote the COM interface and when I try to debug from it I get an Unhandled Exception and the code breaks somewhere in assembly code of the KernelBase.dll

    Call Stack:

    KernelBase.dll!758ed36f() [Frames below may be incorrect and/or missing, no symbols loaded for KernelBase.dll] KernelBase.dll!758ed36f() clr.dll!5d5e5c3b() 0030e4fc() WindowsBase.ni.dll!562600a6() clr.dll!5d5b2e93() WindowsBase.ni.dll!5625ffd5() WindowsBase.ni.dll!5625fe83() WindowsBase.ni.dll!5625fdb2() WindowsBase.ni.dll!5625e2cc() WindowsBase.ni.dll!5625f8b8() user32.dll!7745c4e7() user32.dll!7745c5e7() user32.dll!7745c590() user32.dll!7745cc19() user32.dll!7745cc70() WindowsBase.ni.dll!562790fc() WindowsBase.ni.dll!562790fc() WindowsBase.ni.dll!5625d511() PresentationFramework.ni.dll!50d8b17f() PresentationFramework.ni.dll!50d8ad50() PresentationFramework.ni.dll!50d8aa9f() PresentationFramework.ni.dll!50d8a6eb() clr.dll!5d5b21bb() clr.dll!5d5e4227() clr.dll!5d5e43c4() clr.dll!5d5e43f9() clr.dll!5d5e4419() clr.dll!5d70887a() clr.dll!5d708988() clr.dll!5d70879c() clr.dll!5d708b91() clr.dll!5d708a92() clr.dll!5d703a30() mscoreei.dll!6d7555ab() mscoree.dll!6dab7f16() mscoree.dll!6dab4de3() kernel32.dll!7724ed6c() ntdll.dll!7773377b() ntdll.dll!7773374e()

    ***

    *** I believe that the problem is that the COM interface is not visible.***

    ***

    but maybe you are correct. Here is the function definition that I see in C#.  I see this when I hover over the function name with the mouse.

    void IMED704.PortConfig(Array portCfg, [bool clearInputs = true])

    Here is the function definition as it is declared in the COM interface

    PortConfig(SAFEARRAY *portCfg, VARIANT_BOOL clearInputs)

    And the definition as it is declared in the IDL file

    [id(5), helpstring("method PortConfig")] HRESULT PortConfig([in] SAFEARRAY(PORT_CONFIG) portCfg, [in, defaultvalue(-1)] VARIANT_BOOL clearInputs);

    What gets me is the Array portCfg. If anything is an Invalid Parameter it is that.


    Friday, May 18, 2012 6:44 PM
  • Ok I was stumped as everything looks fine.  I tried it at home and I've figured out the problem but I can't explain why.  For some reason the interop layer fails on SAFEARRAY parameters when the interop type is embedded (the default in VS2010).  To work around this issue right-click the reference to the COM object and set Embed Interop Type to False.  Then try running your program again and see if the problem goes away.  It did for me.

    Michael Taylor - 5/18/2012
    http://msmvps.com/blogs/p3net

    Saturday, May 19, 2012 3:07 AM
  • Thank you!!!  That solves the problem.  I knew that it had to be something small, but I would not/did not guess that it was a property on the reference resource.  Looking at the documentation this is something that changed with .NET 4.0.  If I had been using .NET 3.5 this problem would not have occurred.
    Monday, May 21, 2012 1:09 PM
  • I actually mentioned that way back in my first post (see the middle paragraph)...I assumed you had already tried the things in that post before going through all of this!

    Check out My Blog. Now updated to actually work!

    Monday, May 21, 2012 1:19 PM
  • Hi Tim,

    Yes you did.  I either missed that or didn't understand it when I read it.  I think that I had to go through the whole process to be able to understand why something is done a specific way.  My problem could have been solved much earlier if I had been capable of understanding the valuable information that you had wrote.

    Monday, May 21, 2012 1:39 PM