none
Interop C++ --> COM --> C# RRS feed

  • Question

  • Scenario:
    I have a C++ DLL that is calling functions from a C# DLL using COM.
    The C++ dll is more or less of a wrapper.

    I have defined the following in C#:

    public delegate int delegatePTM([MarshalAs(UnmanagedType.U4)]UInt32 ID, [In, Out] MSG[] pMSG, ref UInt32 pNumMsgs, [MarshalAs(UnmanagedType.U4)]UInt32 Barcode); // MSG is a Structure

    Which is translated to:

                instance int32  Invoke(uint32  marshal( unsigned int32) ChannelID,
                                       [in][out] valuetype Driver.Objects.MSG[] pMsg,
                                       uint32& pNumMsgs,
                                       uint32  marshal( unsigned int32) Barcode) runtime managed

    So the above works if I'm directly accessing a C++ DLL from C#. When when trying to send data through COM to the C# DLL I get a multitude of problems. The first:

    SafeArray of rank n has been passed to a method expecting an array of rank 1.

    I understand why this is happening. I did not specify how many objects are going to be in the array of MSG. I understand that but what I don't get is why is the marshaller using SAFEARRAY when I've tried to pass the array by value ?


    Inside the C++ dll I have the following:
    ExternalConnection->PTM(ID, (SAFEARRAY*) pMsg, pNumMsgs, barcode);

    Summary:
    I have a C++ Wrapper for a C# DLL. I'm trying to communicate via COM. I've established an interface and a class that has methods that can be accessed from C++. Most of these methods work. I'm having trouble marshaling an array of structures to the C# DLL from C++. What confuses me is that code is working and correctly automatically marshaling in the scenario if a C++ DLL is attached directly to C# using P/Invoke. There's something simple I'm missing, and I apologize for my ignorance but I can not see it.

     

     

     

     

    Friday, February 25, 2011 10:53 PM

Answers

  • Hello BlndLeadingDef,

     

    1. The situation is different with COM because now COM rules are involved.

     

    2. Assuming that you have defined the interface in C#, the following method :

    int PTM([MarshalAs(UnmanagedType.U4)]UInt32 ID, [In, Out] MSG[] pMSG, ref UInt32 NumMsgs, [MarshalAs(UnmanagedType.U4)]UInt32 Barcode);

    will be transformed into :

            HRESULT _stdcall PTM(
                            [in] unsigned long ID,
                            [in, out] SAFEARRAY(MSG) pMSG,
                            [in, out] unsigned long* NumMsgs,
                            [in] unsigned long Barcode,
                            [out, retval] long* pRetVal);

    as you are well aware.

    2.1 C# arrays are by default transformed into SAFEARRAYs due to the fact that COM SAFEARRAYs and C# arrays are both self-descriptive : i.e. each intrinsically contain information about the contained type as well as bounds information (number of dimensions, number of elements per dimension).

    2.2 In your particular case, avoiding the use of SAFEARRAY is the right thing to do because you want to exchange an array of MSG structures. In COM, a SAFEARRAY -can- contain an array of structures but each structure must be contained inside a VT_RECORD VARIANT. The SAFEARRAY must therefore hold an array of VARIANTs (of type VT_RECORD) each containing one structure. This is a common task in COM but the problem is that in C#, it is not possible to access the structures from the array of VT_RECORD VARIANTs. This is a known limitation albeit there are (tedious) ways around this problem.

     

    3. >> I can change what it expects by entering: [MarshalAs(???)]. The problem is that I don't think anything really fits. Do you have any suggestions?

    3.1 Yes. My suggestion for you is to add the "[MarshalAs(UnmanagedType.LPArray, SizeParamIndex=2)]" attribute for the MSG array parameter and remove the "ref" keyword for the "pNumMsgs" paramater :

    int PTM([MarshalAs(UnmanagedType.U4)]UInt32 ID, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=2)] [In, Out] MSG[] pMSG, UInt32 NumMsgs, [MarshalAs(UnmanagedType.U4)]UInt32 Barcode);

    UnmanagedType.LPArray will indicate that pMSG is to be marshaled as a C-style array of MSG structs.

    SizeParamIndex=2 will indicate that the size of the array can be found in the 3rd parameter (SizeParamIndex is zero-based) "NumMsgs". Note that "NumMsgs" must be passed by value and cannot be passed by reference.

    3.2 This will produce the following method signature for its COM equivalent :

            HRESULT _stdcall PTM(
                            [in] unsigned long ID,
                            [in, out] MSG* pMSG,
                            [in] unsigned long NumMsgs,

                            [in] unsigned long Barcode,
                            [out, retval] long* pRetVal);

    3.3 The above will work for your case but please note the advise given in the next section.

     

    4. Note that the above COM method signature is still not completely correct. A completely correct COM method signature would be :

            HRESULT _stdcall PTM(
                            [in] unsigned long ID,
                            [in, out, size_is(NumMsgs)] MSG* pMSG,
                            [in] unsigned long NumMsgs,
                            [in] unsigned long Barcode,
                            [out, retval] long* pRetVal);

    4.1 This is important because if multiple apartments are involved, without the size_is() COM IDL attribute, only one MSG struct will be marshaled across apartment boundaries. This will happen if the C#-derived interface (which contains the PTM() method) is implemented by an unmanaged COM server (written in C++, say) and apartment marshaling is involved.

    4.2 In your particular case, where the COM server is actually implemented in C#, this will not be a problem because the designers of .NET has sought to avoid thread affinity wherever possible. And COM components written in C# are apartment-unaware.

    4.3 Hence if you are never going to implement the interface (which contains the PTM() method) in C++, you will not face this problem.

    4.4 If there is a possibility that you may want to implement the interface in C++, then you can do the following :

    4.4.1 Use OLEVIEW to save the IDL of the C#-derived TLB into a separate IDL file.

    4.4.2 Aded the size_is() attribute.

    4.4.3 Compile the new IDL into a new TLB file.

    4.4.4 When you develope in C++, reference the new IDL and/or the new TLB.

     

    Hope the above will be helpful.

    - Bio.

     

    Saturday, February 26, 2011 11:51 AM
  • Hello BlndLeadingDef,

     

    1. >> Is there a way to have an the SizeParamIndex be an Out variable as well as In?

    1.1 Unfortunately, no. The MarshalAsAttribute.SizeParamIndex field is not as flexible as the IDL size_is() attribute.

    1.2. I have 2 suggestions for you which are outlined below.

     

    2. Use a Large (fixed-sized) Array

    2.1 Set the SizeParamIndex to a large value and indeed pass a large fixed-sized array over to the PTM() method.

    2.2 Use a separate ref parameter which can be used by the PTM() method to indicate back to the client code how many of the array items were actually modified.

    2.3 The declaration for PTM() may be something like :

    int PTM2([MarshalAs(UnmanagedType.U4)]UInt32 ID, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] [In, Out] MSG[] pMSG, UInt32 NumMsgsIn, ref UInt32 NumMsgsOut, [MarshalAs(UnmanagedType.U4)]UInt32 Barcode);

    2.4 The "NumMsgsOut" parameter is used by the PTM() method implementation to return the number of MSG structs that have been modified and are relevent for the client code.

     

    3. Use a Thin Wrapper for the MSG struct.

    3.1 Define a thin object wrapper interface for the MSG struct, e.g. :

        [ComVisible(true)]
        [Guid("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        public interface IMSGWrapper
        {
            MSG MSGStruct
            {
                get;
                set;
            }
        }

    Although it is not possible (in C#) to access user-defined structures from a SAFEARRAY of VARIANTs, there is no problem to access IUnknown-based interfaces from a SAFEARRAY of VARIANTs.

    3.2 This interface can be implemented very simply, e.g. :

        [ComVisible(true)]
        [ProgId("BlndLeadingDefImpl01.MSGWrapperClass01")]
        [Guid("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")]
        [ClassInterface(ClassInterfaceType.None)]
        public class MSGWrapperClass01 : IMSGWrapper
        {
            public MSGWrapperClass01()
            {
            }

            ~MSGWrapperClass01()
            {
            }

            public MSG MSGStruct
            {
                get
                {
                    return m_MSG;
                }

                set
                {
                    m_MSG = value;
                }
            }

            private MSG m_MSG;
        }

    3.3 Define the PTM() method as follows :

    int PTM([MarshalAs(UnmanagedType.U4)]UInt32 ID, [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_VARIANT)] [In, Out] ref object[] pMSG, UInt32 NumMsgs, [MarshalAs(UnmanagedType.U4)]UInt32 Barcode);

    That is, redefine the "pMSG" array as a ref array of objects. Note well how the MarshalAsAttribute is declared. It indicates that "pMSG" is to be marshaled to and from the unmanaged side as a SAFEARRAY of VARIANTs. The C++ declaration for the PTM() method (generated via #import) is :

          virtual HRESULT __stdcall PTM (
            /*[in]*/ unsigned long ID,
            /*[in,out]*/ SAFEARRAY * * pMSG,
            /*[in]*/ unsigned long NumMsgs,
            /*[in]*/ unsigned long Barcode,
            /*[out,retval]*/ long * pRetVal ) = 0;

    3.4 The C++ client must create COM objects (of IMSGWrapper interface type), wrap each inside a VARIANT (the VARTYPE will be VT_UNKNOWN) and then use SAFEARRAY APIs to store each VARIANT inside a SAFEARRAY. Thereafter, the address of the pointer to this SAFEARRAY is passed to the PTM() method.

    3.5 When the PTM() method (implemented in C#) is called, "pMSG" is fully copied into a managed array of object types. Each object will then need to be cast into an IMSGWrapper object. Note that the interop marshaler will know how to read the SAFEARRAY, extract each VARIANT from the array and then store each IMSGWrapper interface pointer contained inside the VARIANT inside an object.

    3.6 The advantage of using a SAFEARRAY is that the size and contents of the array can be flexibly modified. There is no need to use the "NumMsgs" parameter to indicate how many array items are exchanged with the unmanaged side. 

    3.7 Here is a sample C# implementation for the PTM() method :

            public int PTM(uint ID, ref object[] pMSG, uint NumMsgs, uint Barcode)
            {
                // Resize the array to a bigger size (five elements larger).
                Array.Resize(ref pMSG, pMSG.Length + 5);

                // Set the values inside pMSG array.
                for (int i = 0; i < pMSG.Length; i++)
                {
                    IMSGWrapper pWrapper = (IMSGWrapper)pMSG[i];

                    // This next step is necessary because the array has been
                    // resized to a larger size and thus new array items
                    // will be set to null.
                    if (pWrapper == null)
                    {
                        pWrapper = new MSGWrapperClass01();
                    }

                    MSG MSGNew = new MSG();

                    MSGNew.i1 = i;
                    MSGNew.i2 = i;

                    pWrapper.MSGStruct = MSGNew;

                    pMSG[i] = pWrapper;
                }

                return 0;
            }

    3.8 On the C++ side, the same SAFEARRAY that was passed to the PTM() method will contain an updated count of array items and each array item, if modified by PTM() will have its value updated :

       spIBlndLeadingDef -> PTM
       (
            ID,
            &pSafeArrayOfObjects,
            NumMsgs,
            Barcode,
            &RetVal
          );

     

    4. My personal preference is to use the SAFEARRAY approach albeit this incurs greater complexity and cost of maintenance.

    4.1 Using a fixed array is definitely simpler albeit it may not be very efficient.

    4.2 If you need any sample C++ code to manipulate SAFEARRAYs, please let me know.

     

    Best Regards,

    - Bio.

     

    Wednesday, March 2, 2011 7:22 AM

All replies

  • It is my understanding that SAFEARRAY's can only contain automation data types.  The MSG struct is not an automation data type, so not sure what is happening there.
    MCP
    Saturday, February 26, 2011 5:19 AM
  • webjose, Thank you for your reply.

     

    That may be possible, to be honest I don't know much about them. The thing is I'm trying to replicate what is already happening. Using P/Invoke the marshaller can by value pass the MSG struct in and out. I can't seem to be able to do that myself. Should I instruct the marshaller to expect another data type, if so which would you recommend?

    public delegate int delegatePTM([MarshalAs(UnmanagedType.U4)]UInt32 ID, [In, Out] MSG[] pMSG , ref UInt32 pNumMsgs, [MarshalAs(UnmanagedType.U4)]UInt32 Barcode); // MSG is a Structure

    The bolded area is somehow being interpreted as a SAFEARRAY when converted to a tlb. I can change what it expects by entering: [MarshalAs(???)]. The problem is that I don't think anything really fits. Do you have any suggestions?

     

     

    Saturday, February 26, 2011 7:19 AM
  • Hello BlndLeadingDef,

     

    1. The situation is different with COM because now COM rules are involved.

     

    2. Assuming that you have defined the interface in C#, the following method :

    int PTM([MarshalAs(UnmanagedType.U4)]UInt32 ID, [In, Out] MSG[] pMSG, ref UInt32 NumMsgs, [MarshalAs(UnmanagedType.U4)]UInt32 Barcode);

    will be transformed into :

            HRESULT _stdcall PTM(
                            [in] unsigned long ID,
                            [in, out] SAFEARRAY(MSG) pMSG,
                            [in, out] unsigned long* NumMsgs,
                            [in] unsigned long Barcode,
                            [out, retval] long* pRetVal);

    as you are well aware.

    2.1 C# arrays are by default transformed into SAFEARRAYs due to the fact that COM SAFEARRAYs and C# arrays are both self-descriptive : i.e. each intrinsically contain information about the contained type as well as bounds information (number of dimensions, number of elements per dimension).

    2.2 In your particular case, avoiding the use of SAFEARRAY is the right thing to do because you want to exchange an array of MSG structures. In COM, a SAFEARRAY -can- contain an array of structures but each structure must be contained inside a VT_RECORD VARIANT. The SAFEARRAY must therefore hold an array of VARIANTs (of type VT_RECORD) each containing one structure. This is a common task in COM but the problem is that in C#, it is not possible to access the structures from the array of VT_RECORD VARIANTs. This is a known limitation albeit there are (tedious) ways around this problem.

     

    3. >> I can change what it expects by entering: [MarshalAs(???)]. The problem is that I don't think anything really fits. Do you have any suggestions?

    3.1 Yes. My suggestion for you is to add the "[MarshalAs(UnmanagedType.LPArray, SizeParamIndex=2)]" attribute for the MSG array parameter and remove the "ref" keyword for the "pNumMsgs" paramater :

    int PTM([MarshalAs(UnmanagedType.U4)]UInt32 ID, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=2)] [In, Out] MSG[] pMSG, UInt32 NumMsgs, [MarshalAs(UnmanagedType.U4)]UInt32 Barcode);

    UnmanagedType.LPArray will indicate that pMSG is to be marshaled as a C-style array of MSG structs.

    SizeParamIndex=2 will indicate that the size of the array can be found in the 3rd parameter (SizeParamIndex is zero-based) "NumMsgs". Note that "NumMsgs" must be passed by value and cannot be passed by reference.

    3.2 This will produce the following method signature for its COM equivalent :

            HRESULT _stdcall PTM(
                            [in] unsigned long ID,
                            [in, out] MSG* pMSG,
                            [in] unsigned long NumMsgs,

                            [in] unsigned long Barcode,
                            [out, retval] long* pRetVal);

    3.3 The above will work for your case but please note the advise given in the next section.

     

    4. Note that the above COM method signature is still not completely correct. A completely correct COM method signature would be :

            HRESULT _stdcall PTM(
                            [in] unsigned long ID,
                            [in, out, size_is(NumMsgs)] MSG* pMSG,
                            [in] unsigned long NumMsgs,
                            [in] unsigned long Barcode,
                            [out, retval] long* pRetVal);

    4.1 This is important because if multiple apartments are involved, without the size_is() COM IDL attribute, only one MSG struct will be marshaled across apartment boundaries. This will happen if the C#-derived interface (which contains the PTM() method) is implemented by an unmanaged COM server (written in C++, say) and apartment marshaling is involved.

    4.2 In your particular case, where the COM server is actually implemented in C#, this will not be a problem because the designers of .NET has sought to avoid thread affinity wherever possible. And COM components written in C# are apartment-unaware.

    4.3 Hence if you are never going to implement the interface (which contains the PTM() method) in C++, you will not face this problem.

    4.4 If there is a possibility that you may want to implement the interface in C++, then you can do the following :

    4.4.1 Use OLEVIEW to save the IDL of the C#-derived TLB into a separate IDL file.

    4.4.2 Aded the size_is() attribute.

    4.4.3 Compile the new IDL into a new TLB file.

    4.4.4 When you develope in C++, reference the new IDL and/or the new TLB.

     

    Hope the above will be helpful.

    - Bio.

     

    Saturday, February 26, 2011 11:51 AM
  • Bio,

    Thank you Thank you.
    I can't thank you enough for your reply. It's informative and complete. Thank you.

     

    Unfortunately , I have an issue that throws a bit of a wrench. There's one change there I wasn't clear to you about. Concerning 3.1 :

    PTM not only returns an array of pMSG's but it also returns the count of pMSG's.  So optimally:

            HRESULT _stdcall PTM(

                            [in] unsigned long
                            [in, out] MSG* pMSG,
                            [in, out] unsigned long NumMsgs,

                            [in] unsigned long Barcode,
                            [out, retval] long* pRetVal);

    In trying to learn, and not bother you again :),  I did some research on SizeParamIndex as mentioned earlier. (Please read this whole thing as it explains how I'm not doubting what you've provided).  If I'm understanding it correctly (according to MSDN) it doesn't care whether the parameter it points to is by val or a ref. (http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshalasattribute.sizeparamindex.aspx) So I modified the code to look like:

    int PTM([MarshalAs(UnmanagedType.U4)]UInt32 ID, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=2)] [In, Out] MSG[] pMSG, [In, Out] UInt32 NumMsgs, [MarshalAs(UnmanagedType.U4)]UInt32 Barcode);

    The problem is that the tlb while reflect in and out, maintains unsigned long NumMsgs. On the other hand the tli, (autogenerated based on the tlb) only shows:

    PTM ( unsigned long ID, MSG * pMSG, unsigned long pNumMsgs, unsigned long Timeout )

    To, me this shows that the parameter pNumMsgs can never be an out variable unless it's marked as ref. The next step I took was to contradict what you've written above and mark it as an ref and hopefully pray that MSDN was right. I was quickly proved wrong with E_OUTOFMEMORY issues. Is there a way to have an the SizeParamIndex be an Out variable as well as In?

    Currently I've tried this as well but this throws an exception:

    int PTM([MarshalAs(UnmanagedType.U4)]UInt32 ID, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] [In, Out] MSG[] pMSG, [In, Out] ref UInt32 pNumMsgs, [MarshalAs(UnmanagedType.U4)]UInt32 Barcode)

            HRESULT _stdcall PTM(

                            [in] unsigned long
                            [in, out] MSG* pMSG,
                            [in, out] unsigned long * NumMsgs,

                            [in] unsigned long Barcode,
                            [out, retval] long* pRetVal);

     

     

    I really appreciate your help, time, and support.

    Thank you.


     

    Monday, February 28, 2011 5:57 PM
  • Hello BlndLeadingDef,

     

    1. >> Is there a way to have an the SizeParamIndex be an Out variable as well as In?

    1.1 Unfortunately, no. The MarshalAsAttribute.SizeParamIndex field is not as flexible as the IDL size_is() attribute.

    1.2. I have 2 suggestions for you which are outlined below.

     

    2. Use a Large (fixed-sized) Array

    2.1 Set the SizeParamIndex to a large value and indeed pass a large fixed-sized array over to the PTM() method.

    2.2 Use a separate ref parameter which can be used by the PTM() method to indicate back to the client code how many of the array items were actually modified.

    2.3 The declaration for PTM() may be something like :

    int PTM2([MarshalAs(UnmanagedType.U4)]UInt32 ID, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] [In, Out] MSG[] pMSG, UInt32 NumMsgsIn, ref UInt32 NumMsgsOut, [MarshalAs(UnmanagedType.U4)]UInt32 Barcode);

    2.4 The "NumMsgsOut" parameter is used by the PTM() method implementation to return the number of MSG structs that have been modified and are relevent for the client code.

     

    3. Use a Thin Wrapper for the MSG struct.

    3.1 Define a thin object wrapper interface for the MSG struct, e.g. :

        [ComVisible(true)]
        [Guid("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        public interface IMSGWrapper
        {
            MSG MSGStruct
            {
                get;
                set;
            }
        }

    Although it is not possible (in C#) to access user-defined structures from a SAFEARRAY of VARIANTs, there is no problem to access IUnknown-based interfaces from a SAFEARRAY of VARIANTs.

    3.2 This interface can be implemented very simply, e.g. :

        [ComVisible(true)]
        [ProgId("BlndLeadingDefImpl01.MSGWrapperClass01")]
        [Guid("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")]
        [ClassInterface(ClassInterfaceType.None)]
        public class MSGWrapperClass01 : IMSGWrapper
        {
            public MSGWrapperClass01()
            {
            }

            ~MSGWrapperClass01()
            {
            }

            public MSG MSGStruct
            {
                get
                {
                    return m_MSG;
                }

                set
                {
                    m_MSG = value;
                }
            }

            private MSG m_MSG;
        }

    3.3 Define the PTM() method as follows :

    int PTM([MarshalAs(UnmanagedType.U4)]UInt32 ID, [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_VARIANT)] [In, Out] ref object[] pMSG, UInt32 NumMsgs, [MarshalAs(UnmanagedType.U4)]UInt32 Barcode);

    That is, redefine the "pMSG" array as a ref array of objects. Note well how the MarshalAsAttribute is declared. It indicates that "pMSG" is to be marshaled to and from the unmanaged side as a SAFEARRAY of VARIANTs. The C++ declaration for the PTM() method (generated via #import) is :

          virtual HRESULT __stdcall PTM (
            /*[in]*/ unsigned long ID,
            /*[in,out]*/ SAFEARRAY * * pMSG,
            /*[in]*/ unsigned long NumMsgs,
            /*[in]*/ unsigned long Barcode,
            /*[out,retval]*/ long * pRetVal ) = 0;

    3.4 The C++ client must create COM objects (of IMSGWrapper interface type), wrap each inside a VARIANT (the VARTYPE will be VT_UNKNOWN) and then use SAFEARRAY APIs to store each VARIANT inside a SAFEARRAY. Thereafter, the address of the pointer to this SAFEARRAY is passed to the PTM() method.

    3.5 When the PTM() method (implemented in C#) is called, "pMSG" is fully copied into a managed array of object types. Each object will then need to be cast into an IMSGWrapper object. Note that the interop marshaler will know how to read the SAFEARRAY, extract each VARIANT from the array and then store each IMSGWrapper interface pointer contained inside the VARIANT inside an object.

    3.6 The advantage of using a SAFEARRAY is that the size and contents of the array can be flexibly modified. There is no need to use the "NumMsgs" parameter to indicate how many array items are exchanged with the unmanaged side. 

    3.7 Here is a sample C# implementation for the PTM() method :

            public int PTM(uint ID, ref object[] pMSG, uint NumMsgs, uint Barcode)
            {
                // Resize the array to a bigger size (five elements larger).
                Array.Resize(ref pMSG, pMSG.Length + 5);

                // Set the values inside pMSG array.
                for (int i = 0; i < pMSG.Length; i++)
                {
                    IMSGWrapper pWrapper = (IMSGWrapper)pMSG[i];

                    // This next step is necessary because the array has been
                    // resized to a larger size and thus new array items
                    // will be set to null.
                    if (pWrapper == null)
                    {
                        pWrapper = new MSGWrapperClass01();
                    }

                    MSG MSGNew = new MSG();

                    MSGNew.i1 = i;
                    MSGNew.i2 = i;

                    pWrapper.MSGStruct = MSGNew;

                    pMSG[i] = pWrapper;
                }

                return 0;
            }

    3.8 On the C++ side, the same SAFEARRAY that was passed to the PTM() method will contain an updated count of array items and each array item, if modified by PTM() will have its value updated :

       spIBlndLeadingDef -> PTM
       (
            ID,
            &pSafeArrayOfObjects,
            NumMsgs,
            Barcode,
            &RetVal
          );

     

    4. My personal preference is to use the SAFEARRAY approach albeit this incurs greater complexity and cost of maintenance.

    4.1 Using a fixed array is definitely simpler albeit it may not be very efficient.

    4.2 If you need any sample C++ code to manipulate SAFEARRAYs, please let me know.

     

    Best Regards,

    - Bio.

     

    Wednesday, March 2, 2011 7:22 AM
  • Bio,

     

    Your responses have been incredible. Thank you so much. I have implemented 2.2 last night and, as you mentioned it works, but, if given the choice would you still take the SAFEARRAY approach? If so, I would like to also go this route only to learn the correct methodology. If possible, and if it's not too much work -- you've helped me so much so far, sample C++ code to manipulate SAFEARRAY's would be terrific. Once again I really appreciate your help.

    I will mark both of your answers as correct as together they are a whole.

     

    BldLdnDef

     

    Wednesday, March 2, 2011 3:19 PM
  • Hello BlndLeadingDef,

     

    1. >> if given the choice would you still take the SAFEARRAY approach?

    1.1 Yes I personally would choose to use the thin-wrapper and SAFEARRAY approach. But take note that when deciding what technology to use, maintenance and learning curve issues are very important.

    1.2 Using the SAFEARRAY approach requires careful attention to reference counts of COM objects. Objects contained within the SAFEARRAY would have to be copied to C++ arrays or the STL vector for ease of use. This can be tedious and error-prone if not done carefully.

    1.3 I would say use the fixed-size array approach if it is more practical for your project. It is certainly easier to maintain as long as its use is well-documented.

     

    2. Example Code

    2.1 I've provided some code below together with some documentation to help you get started if you intend to use SAFEARRAYs and to use the thin-wrapper for the MSG struct.

    2.2 In the demo code, I assume that you have #imported the appropriate type library in your C++ code and _com_ptr_t smart pointer types have been generated for the COM interfaces that correspond to C# interfaces.

    2.3 I also assume that the MSG struct wrapper COM interface is IMSGWrapper and that the smart pointer for this interface is IMSGWrapperPtr.

    2.4 I also use the STL vector to store smart pointer objects, e.g. :

    #include <vector>

    typedef std::vector<IUnknownPtr> VECTOR_IUnknownPtr;

     

    3. Storing COM Objects Into The Vector

    3.1 The code below shows how to store IMSGWrapperPtr objects into a VECTOR_IUnknownPtr named "vecIUnknownPtr" :

       VECTOR_IUnknownPtr vecIUnknownPtr;
      
       for (int i = 0; i < 10; i++)
       {
         MSG msg;

         // Code to insert values into "msg".
         ...
           
         IMSGWrapperPtr spIMSGWrapperTemp = NULL;
         IUnknownPtr  spIUnknownTemp = NULL;
       
         // Code to instantiate spIMSGWrapperTemp.
         ...
     
         spIMSGWrapperTemp -> put_MSG(msg);
         spIMSGWrapperTemp -> QueryInterface(&spIUnknownTemp);
         vecIUnknownPtr.push_back(spIUnknownTemp);
       }

    3.2 The for loop creates 10 instances of IMSGWrapperPtr objects. Within each iteration, it fills each IMSGWrapper object with a "msg" struct value and then QI() the object for its IUnknown pointer (which is stored inside an IUnknownPtr).

    3.3 It then stores the IUnknownPtr inside the "vecIUnknownPtr" vector.

    3.4 It is with the "vecIUnknownPtr" vector that we create a SAFEARRAY as will be shown next.

     

    4. Creating The SAFEARRAY

    4.1 The code below shows how a VECTOR_IUnknownPtr object, when filled with IUnknownPtr objects (each pointing to an instance of a IMSGWrapperPtr) can be used to create a SAFEARRAY :

       SAFEARRAY* pSafeArrayOfObjects = NULL;
      
       CreateSafeArrayOfVariants
       (
          vecIUnknownPtr,
          pSafeArrayOfObjects
       );

       ExternalConnection -> PTM
       (
            ID,
            &pSafeArrayOfObjects,
            NumMsgs,
            Barcode,
            &RetVal 
       );

    4.2 The source for the CreateSafeArrayOfVariants() function is listed below with explanatory comments :

    void CreateSafeArrayOfVariants
    (
      /*[in]*/ const VECTOR_IUnknownPtr& vecIUnknownPtr,
      /*[out]*/ SAFEARRAY*& pSafeArrayReceiver
    )
    {
      size_t   stVectorSize = vecIUnknownPtr.size();
      SAFEARRAY*  pSAFEARRAYRet = NULL;
      SAFEARRAYBOUND rgsabound[1];
      ULONG    ulIndex = 0;

      // Initialise receiver.
      pSafeArrayReceiver = NULL;

      // Create a SAFEARRAY and provide bounds information.
      rgsabound[0].lLbound = 0;
      rgsabound[0].cElements = stVectorSize;

      pSAFEARRAYRet = (SAFEARRAY*)SafeArrayCreate
      (
        (VARTYPE)VT_VARIANT,
        (unsigned int)1,            
        (SAFEARRAYBOUND*)rgsabound
      );

      // Fill the SAFEARRAY with VARIANTs.
      // Each VARIANT contains an IUnknown pointer.
      for (ulIndex = 0; ulIndex < stVectorSize; ulIndex++)
      {
        VARIANT var;
        long lIndexVector[1];

        lIndexVector[0] = ulIndex;
     
        VariantInit(&var);
        V_VT(&var) = VT_UNKNOWN;
        V_UNKNOWN(&var) = (IUnknown*)(vecIUnknownPtr[ulIndex]);

        // SafeArrayPutElement will AddRef() on the IUnknown ptr inside var.
        SafeArrayPutElement
        (
          (SAFEARRAY*)pSAFEARRAYRet, 
          (long*)lIndexVector,
          (void*)&var
        );
      }

      if (pSAFEARRAYRet)
      {
        pSafeArrayReceiver = pSAFEARRAYRet;
      }

      return;
    }

     

    5. Retrieving Objects From The SAFEARRAY

    5.1 The code below demonstrates how, once the PTM() method has been called, the IUnknownPtr objects contained within the SAFEARRAY can be retrieved back into another (separate) receiving VECTOR_IUnknownPtr :

       ExternalConnection -> PTM
       (
            ID,
            &pSafeArrayOfObjects,
            NumMsgs,
            Barcode,
            &RetVal 
       );
          
       VECTOR_IUnknownPtr vecIUnknownPtr2;
         
       GetIUnknownArrayFromSafeArray
       (
            pSafeArrayOfObjects,
            vecIUnknownPtr2
       );

    5.2 The function to use is GetIUnknownArrayFromSafeArray() (will be listed below).

    5.3 It is important to use a separate vector to store the new array of IUnknownPtr objects. This is because the GetIUnknownArrayFromSafeArray() function will insert IUnknownPtr objects at the end of the receiving vector and so if the first vector "vecIUnknownPtr" had been used for GetIUnknownArrayFromSafeArray(), "vecIUnknownPtr" will end up containing the original list of IUnknownPtr objects as well as new ones returned from the PTM() method.

    5.4 The complete listing of the GetIUnknownArrayFromSafeArray() function is given below :

    void GetIUnknownArrayFromSafeArray
    (
      /*[in]*/ const SAFEARRAY* pSAFEARRAY,
      /*[out]*/ VECTOR_IUnknownPtr& vecIUnknownPtrReceiver
    )
    {
      ULONG     ul = 0;
      const SAFEARRAYBOUND* psafearraybound = NULL;

      // Obtain the bounds information from the incoming
      // SAFEARRAY.
      psafearraybound = &((pSAFEARRAY -> rgsabound)[0]);

      // Process each VARIANT contained inside the SAFEARRAY.
      for (ul = 0; ul < psafearraybound -> cElements; ul++)
      {
        VARIANT var;
       
        VariantInit(&var);

        SafeArrayGetElement
        (
          (SAFEARRAY*)pSAFEARRAY, 
          (long*)&ul,
          (void*)(&var)
        );

        // Check that the retrieved VARIANT contains
        // an IUnknown pointer.
        if (V_VT(&var) == VT_UNKNOWN)
        {
          // Retrieve the IUnknown pointer from the VARIANT
          // and store it inside the receiving vector.
          IUnknownPtr spIUnknown(V_UNKNOWN(&var));
          // The push_back() will cause the IUnknown pointer
          // to AddRef().
          vecIUnknownPtrReceiver.push_back(spIUnknown);
        }
       
        VariantClear(&var);
      }
    }

    5.5 The code below demonstrates how the individual IUnknownPtr objects contained within "vecIUnknownPtr2" can now be retrieved and its IMSGWrapper interface obtained :

     // size() will return the new size of the array
     // returned from PTM()
     size_t stVectorSize = vecIUnknownPtr2.size();

     for (int i = 0; i < stVectorSize; i++)
     {
         MSG   msg;
         IMSGWrapperPtr spIMSGWrapperTemp = NULL;
         IUnknownPtr  spIUnknownTemp = vecIUnknownPtr2[i];
        
         spIUnknownTemp -> QueryInterface(&spIMSGWrapperTemp);
       
         spIMSGWrapperTemp -> get_MSG(&msg);
     }
          
     SafeArrayDestroy(pSafeArrayOfObjects);
     pSafeArrayOfObjects = NULL;

    5.6 Note well to destroy the SAFEARRAY when it is no longer required by using SafeArrayDestroy(). In fact, the SAFEARRAY can be destroyed right after its contents have been retrieved into "vecIUnknownPtr2" after the call to GetIUnknownArrayFromSafeArray().

     

    6. Note that when the vectors go out of scope, the IUnknownPtr objects contained within the vectors will all be Release()'d. This is the advantage of using smart pointer types.

     

    7. I do hope that you will benefit from the code listings above. Post again if you need any clarifications. Best of luck.

     

    - Bio.

     

    Thursday, March 3, 2011 6:06 AM
  • Bio,

     

    I want to say Thanks again for the sample code. I will be attempting to implement the code in this Monday. I will be slow replying from here on out but I would like to say that it has already benefited me tremendously because of the lessons I'm learning through it. I will K.I.T. about the progress. Thanks again!

     

    BldLdnDef
    Friday, March 4, 2011 10:39 PM
  • Lim Bio Liong,

    I wanted to write back and update you that I did learn alot through out my plight to implementing the code you wrote above. I'm still working on understanding COM programming completely but your code samples and detail has been invaluable. Thank you again.

    BlD

    Friday, April 22, 2011 10:07 PM
  • Hello BlndLeadingDef,

    Thank you very much for the update. Keep up the COM learning. Post in this forum again should you encounter any difficulties.

    I wish you success,

    - Bio.

     

    Sunday, April 24, 2011 3:43 PM