none
Late-Binding Com Method parameter pointer: How to Get member values of structure pointer passed to late binding COM Objects RRS feed

  • Question

  • Issue:

    I am writing a support application which needs to access all versions of a specific COM object.

    I use late-binding to create it, so I will not be tied to a specific version.

    The function I need takes in a long* , casts it to a pointer to a structure, gathers some values and sets the corresponding members in the structure.

    When I call this function, I can see in the logs that the COM function runs correctly and gets all values, but the C# code cannot see the values that the COM function pulled and put onto the structure.

    I am using Type.InvokeMember to call the method.  Should I be using something else?

    note:  I am running this on Windows 7 X64

    Code

    C++ Signature

                    inline VARIANT_BOOL IBiosManager::MyCOMFunction ( long * pCaps )

    {

      //This function casts pCaps to MyStructure and then sets values on it.

    }

    C# code

                    public static Main( . . .)

            {

                object myObj = null;

               

                CreateInstanceFromType (out myObj, "MyNamespace.MyClass", "DellPBAIO", "4");

                CallMyFunction(myObj);

              

            }

     

    private void CreateInstanceFromType(out object obj, string typeString)

            {

                Type myType = Type.GetTypeFromProgID(typeString);

                obj = Activator.CreateInstance(myType, false);

            }

     

    private void CallMyFunction(ref object obj)

    {

    MyStructure struct = new MyStructure ();

     

    IntPtr pStruct = Marshal.AllocHGlobal(Marshal.SizeOf(struct));

    // Copy the struct to unmanaged memory.

    Marshal.StructureToPtr(struct, pStruct, false);

    Type ComType = obj.GetType();

    object[] parameters = new object[1];

                  parameters[0] = pStruct;

     

    // Call the function

    ComType.InvokeMember("MyCOMFunction ",BindingFlags.InvokeMethod,null, obj,

                        parameters);

                                   

    Problem:

    MyCOMFunction runs successfully.

    I can see from the logs that the COM function is getting the correct files.

     

    However, if I look at parameters[0], the bytes are all zero, so the values that I want are not coming back to my managed code.


    Thursday, July 21, 2011 5:20 PM

Answers

  • Hello Christine,

     

    1. The Error.

    >> The function I need takes in a long* , casts it to a pointer to a structure, gathers some values and sets the corresponding members in the structure...

    >> [id(0x6002002b), helpstring("method MyComMethod")]
    >> HRESULT MyComMethod ([out] long* pCaps, [out, retval] VARIANT_BOOL* pbSuccess);


    1.1 This is a mistake. By declaring "pCaps" as a pointer to a long, the IDL treats it as it is : a pointer to a single long value.

    1.2 You may have had success when the client was a C++ application and the client code (which called MyComMethod()) lives in the same apartment as the IBiosManager implementation (i.e. CMyComClass) itself. This avoided the need for inter-apartment marshaling which resulted in the fortuitous situation where "pCaps" can be treated as a pointer (to a MyStructure structure).

    1.3 This, however, is a very risky way of returning a structure because once inter-apartement marshaling gets involed, the "pCaps" gets treated as a pointer to a single long value.

     

    2. Using Type.InvokeMember().

    2.1 Using Type.InvokeMember() is equivalent to using the IDispatch::Invoke() COM late-bound call.

    2.2 This will also not work for two reasons :

    • The structure contains a character pointer member field which disqualifies it from being insertable into a VT_RECORD VARIANT.
    • A VT_RECORD VARIANT cannot be returned (via a reference) from a Type.InvokeMember() call.

    2.3 Furthermore, COM method calls from managed code always involves both interop marshaling and COM inter-apartment marshaling. With code like the following :

    private void CallMyFunction(ref object obj)
    {
      MyStructure struct = new MyStructure ();
      IntPtr pStruct = Marshal.AllocHGlobal(Marshal.SizeOf(struct));

      // Copy the struct to unmanaged memory.
      Marshal.StructureToPtr(struct, pStruct, false);

      Type ComType = obj.GetType();
      object[] parameters = new object[1];
      parameters[0] = pStruct;
     
      // Call the function
      ComType.InvokeMember("MyCOMFunction ",BindingFlags.InvokeMethod,null, obj, parameters);
      ...
      ...
      ...
    }

    "pStruct" is treated as a pointer to a long value, nothing more.

     

    3. The Correct Way to Declare "pCaps".

    3.1 The correct way to declare "pCaps" is to define it as a pointer to the "MyStructure" structure, e.g. :

    [id(2), helpstring("method MyCOMFunction2")] HRESULT MyCOMFunction2([out] MyStructure* pCaps, [out, retval] VARIANT_BOOL* pbSuccess);

     

    3.2 Furthermore, the "char1" field member of "MyStructure" must be decorated with the [string] IDL attribute :

    typedef struct MyStructure
    {
      int i1;
      [string]
      char* char1;  
      int i2;
    } MyStructure;

    The [string] IDL attribute makes "char1" a C-style string (i.e. a NULL-terminated character array).

    Without the [string] IDL attribute, "char1" is (you should know by now) a pointer to a single character.


    4. Sample Code.

    4.1 The following is a sample C++ code for MyCOMFunction2() :

    STDMETHODIMP CBiosManager::MyCOMFunction2(MyStructure* pCaps, VARIANT_BOOL* pbSuccess)
    {
     // TODO: Add your implementation code here
     pCaps -> i1 = 0;
     pCaps -> i2 = 1;
     char szNewString[] = "Hello World";
     pCaps -> char1 = (unsigned char*)::CoTaskMemAlloc(sizeof(szNewString));
     strcpy((char*)(pCaps -> char1), szNewString);
     
     *pbSuccess = VARIANT_TRUE;

     return S_OK;
    }

    Note that because "char1" is a character string, it must be allocated via ::CoTaskMemAlloc(). It cannot be allocated via "new" or "malloc()". The reason for this will be explained in section 5 below where we discuss the string exchange protocol between managed and unmanaged code.


    4.2 The following is a sample C# calling code :

    private static void CallMyFunction2(ref object obj)
    {
      IBiosManager pIBiosManager = (IBiosManager)obj;

      // Declare a MyStructure struct but do not initialize it.
      MyStructure mystruct;
      // It is MyCOMFunction2() that will initialize it with values.
      bool bSuccess = pIBiosManager.MyCOMFunction2(out mystruct);

      Console.WriteLine("MyStructure.i1    : [{0:D}]", mystruct.i1);
      Console.WriteLine("MyStructure.i2    : [{0:D}]", mystruct.i2);
      Console.WriteLine("MyStructure.char1 : [{0:D}]", mystruct.char1);
    }

    Note that the C# code declares a MyStructure structure but does not initialize it. It is MyCOMFunction2() that will initialize it with values.


    5. The Protocol for String Exchange between Managed Code and Unmanaged Code.

    5.1 The general protocol is that if the unmanaged side should allocate memory for a C-style string which is to be returned to the managed side, the ::CoTaskMemAlloc() API is to be used.

    5.2 Thereafter, the interop marshaler will use the unmanaged C-style string to create a managed string object.

    5.3 Now, because the unmanaged string was returned to the managed side, it is the managed side that owns this string memory (even though it is an unmanaged string). The onus is on the managed side (i.e. the interop marshaler) to free this memory.

    5.4 The managed side will then use Marshal.FreeCoTaskMem() (which eventually calls ::CoTaskMemFree()) to free the memory allocated for the unmanaged string.

    5.5 This is the reason why only ::CoTaskMemAlloc() can be used to allocate the memory for the string in "MyStructure.char1". If "new" or "malloc()" was used, the interop marshaler will not know how to free the string. This is because "new" and "malloc()" are C/C++ library dependent. The ::CoTaskMemAlloc() API is a Windows API and is standard.


    - Bio.

     


    Please visit my blog : http://limbioliong.wordpress.com/
    • Proposed as answer by Paul Zhou Tuesday, August 2, 2011 6:20 AM
    • Marked as answer by Paul Zhou Wednesday, August 3, 2011 2:13 AM
    Sunday, July 24, 2011 8:45 AM

All replies

  • Issue:

    I am writing a support application which needs to access all versions of a specific COM object.

    I use late-binding to create it, so I will not be tied to a specific version.

    The function I need takes in a long* , casts it to a pointer to a structure, gathers some values and sets the corresponding members in the structure.

    When I call this function, I can see in the logs that the COM function runs correctly and gets all values, but the C# code cannot see the values that the COM function pulled and put onto the structure.

    I am using Type.InvokeMember to call the method.  Should I be using something else?

    note:  I am running this on Windows 7 X64

    Code

    C++ Signature

                    inline VARIANT_BOOL IBiosManager::MyCOMFunction ( long * pCaps )

    {

      //This function casts pCaps to MyStructure and then sets values on it.

    } 

    C# code

                    public static Main( . . .)

            {

                object myObj = null;

               

                CreateInstanceFromType (out myObj, "MyNamespace.MyClass", "DellPBAIO", "4");

                CallMyFunction(myObj);

              

            }

     

    private void CreateInstanceFromType(out object obj, string typeString)

            {

                Type myType = Type.GetTypeFromProgID(typeString);

                obj = Activator.CreateInstance(myType, false);

            }

     

    private void CallMyFunction(ref object obj)

    {

    MyStructure struct = new MyStructure ();

     

    IntPtr pStruct = Marshal.AllocHGlobal(Marshal.SizeOf(struct));

    // Copy the struct to unmanaged memory.

    Marshal.StructureToPtr(struct, pStruct, false);

    Type ComType = obj.GetType();

    object[] parameters = new object[1];

                  parameters[0] = pStruct;

     

    // Call the function

    ComType.InvokeMember("MyCOMFunction ",BindingFlags.InvokeMethod,null, obj,

                        parameters);

                                   

    Problem:

    MyCOMFunction runs successfully.

    I can see from the logs that the COM function is getting the correct files.

     

    However, if I look at parameters[0], the bytes are all zero, so the values that I want are not coming back to my managed code.


    Have you run all this in debug and verified that the actual pointer value in 'pStruct' is numerically the same as the received pointer value 'pCaps'?

    I think you may be better of declaring a new delegate type that has the exact signature to match the com func.

    You can then create an instance of the delegate from MethodInfo for the method which you can get from the Type.

    In fact if the C# structs are blittable (identical physlcal layout to the native versions) then you can simply declare the struct and pass it by ref to the com func. If you declare the delegate to accept (ref struct_type) it will actually pass a true pointer, no need to mess around with all that alloc, copy or marshal stuff which can be costly.

    I'm no expert on C++ (hardly used it) but I have done a great deal of this with C. 

    Cap'n

     





    Thursday, July 21, 2011 6:44 PM
  • I will try to write the wrapper.  If succesful, I'll post the code.

     

    Thanks


    Christine Murphy Software Engineer
    Thursday, July 21, 2011 7:40 PM
  • Hi,

    I have tried this and am getting a little stuck.

    Do you think that you could show with the above code sample how you would do that.  Assume that the C++ structured is

    struct myStruct

    {

      int i1;

      char* char1;

      int i2;

    }

     

    Thanks.

     


    Christine Murphy Software Engineer
    Friday, July 22, 2011 2:53 PM
  • Hi,

    I have tried this and am getting a little stuck.

    Do you think that you could show with the above code sample how you would do that.  Assume that the C++ structured is

    struct myStruct

    {

      int i1;

      char* char1;

      int i2;

    }

     

    Thanks.

     


    Christine Murphy Software Engineer


    Hi

    I just noticed that the struct contains a pointer field, is this a pointer to a null terminated string that the callee sets? If so, how long is the string typically? is there a max length?

    OK, here is a stab at this, code compiles but I have made assumptions and hard coded a few strings that are unknown to me...

        public struct MyStructure
        {
          int    i1;
          fixed byte char1[64]; // assume 64 is long enough
          int    i2;
        }
    
        public delegate bool ComFunction (out MyStructure argumnent);
    
          // This is code that goes in some method of yours
    
          // Get the method info from the type.
    
          Type com_method_type = Type.GetTypeFromProgID("whatever");
    
          MethodInfo com_method_info = com_method_type.GetMethod("name_of_method");
    
          // Create delegate 
    
          ComFunction com_method = (ComFunction)Delegate.CreateDelegate(typeof(ComFunction),com_method_info);
    
          // Declare struct and invoke
    
          MyStructure arg_struct;
    
          com_method(out arg_struct);
          
          
    
    

     

    Hope this helps.

    Cap'n

     

    Friday, July 22, 2011 3:32 PM
  • It would be awesome if I could do that.

    However, the type.GetMethod returns null.

    This has shown that my example is off by a little.  It is actually a class which implements an interface.  When I look at OLE/Com Object viewer, the class that I want shows only one thing underneath it (the interface that it implements).  That interface, then has the function.

    I tried creating a type object of that interface, and I got null.  com_method_type.GetInterfaces() returns no interfaces.

    So, here's the snippet of what the class looks like in c++

     

    Code

    C++ Signature (from tli file)

                    inline VARIANT_BOOL IBiosManager::MyCOMFunction ( long * pCaps )

    {

      //This function casts pCaps to MyStructure and then sets values on it.

    }

    C++ signature (from Ole/Com object viewer)

    [id(0x6002002b), helpstring("method MyComMethod")]

    HRESULT MyComMethod (

                    [out] long* pCaps,

                    [out, retval] VARIANT_BOOL* pbSuccess);

     

    C++ code file

    class ATL_NO_VTABLE CMyComClass :

          public CComObjectRootEx<CComSingleThreadModel>,

          public IDispatchImpl<IMyComClass, &IID_IMyComClass, &LIBID_MyNamespaceLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,

          public IPersistStreamInitImpl< CMyComClass >,

          public IOleControlImpl< CMyComClass >,

          public IOleObjectImpl< CMyComClass >,

          public IOleInPlaceActiveObjectImpl< CMyComClass >,

          public IViewObjectExImpl< CMyComClass >,

          public IOleInPlaceObjectWindowlessImpl< CMyComClass >,

          public ISupportErrorInfo,

          public IPersistStorageImpl< CMyComClass >,

          public ISpecifyPropertyPagesImpl< CMyComClass >,

          public IQuickActivateImpl< CMyComClass >,

    #ifndef _WIN32_WCE

          public IDataObjectImpl< CMyComClass >,

    #endif

          public IProvideClassInfo2Impl<&CLSID_MyComClass, NULL, &LIBID_ MyNamespaceLib >,

    #ifdef _WIN32_WCE // IObjectSafety is required on Windows CE for the control to be loaded correctly

          public IObjectSafetyImpl<CBiosManager, INTERFACESAFE_FOR_UNTRUSTED_CALLER>,

    #endif

          public CComCoClass< CMyComClass, &CLSID_ MyComClass >,

          public CComControl< CMyComClass >

    {

    DECLARE_OLEMISC_STATUS(OLEMISC_RECOMPOSEONRESIZE |

          OLEMISC_CANTLINKINSIDE |

          OLEMISC_INSIDEOUT |

          OLEMISC_ACTIVATEWHENVISIBLE |

          OLEMISC_SETCLIENTSITEFIRST

    )

     

    DECLARE_REGISTRY_RESOURCEID(IDR_MyComClass1)

     

     

    BEGIN_COM_MAP(CMyComClass)

          COM_INTERFACE_ENTRY(IMyComClass)

          COM_INTERFACE_ENTRY(IDispatch)

          COM_INTERFACE_ENTRY(IViewObjectEx)

          COM_INTERFACE_ENTRY(IViewObject2)

          COM_INTERFACE_ENTRY(IViewObject)

          COM_INTERFACE_ENTRY(IOleInPlaceObjectWindowless)

          COM_INTERFACE_ENTRY(IOleInPlaceObject)

          COM_INTERFACE_ENTRY2(IOleWindow, IOleInPlaceObjectWindowless)

          COM_INTERFACE_ENTRY(IOleInPlaceActiveObject)

          COM_INTERFACE_ENTRY(IOleControl)

          COM_INTERFACE_ENTRY(IOleObject)

          COM_INTERFACE_ENTRY(IPersistStreamInit)

          COM_INTERFACE_ENTRY2(IPersist, IPersistStreamInit)

          COM_INTERFACE_ENTRY(ISupportErrorInfo)

          COM_INTERFACE_ENTRY(ISpecifyPropertyPages)

          COM_INTERFACE_ENTRY(IQuickActivate)

          COM_INTERFACE_ENTRY(IPersistStorage)

    #ifndef _WIN32_WCE

          COM_INTERFACE_ENTRY(IDataObject)

    #endif

          COM_INTERFACE_ENTRY(IProvideClassInfo)

          COM_INTERFACE_ENTRY(IProvideClassInfo2)

    #ifdef _WIN32_WCE // IObjectSafety is required on Windows CE for the control to be loaded correctly

          COM_INTERFACE_ENTRY_IID(IID_IObjectSafety, IObjectSafety)

    #endif

    END_COM_MAP()

    BEGIN_PROP_MAP(CMyComClass)

          PROP_DATA_ENTRY("_cx", m_sizeExtent.cx, VT_UI4)

          PROP_DATA_ENTRY("_cy", m_sizeExtent.cy, VT_UI4)

          // Example entries

          // PROP_ENTRY_TYPE("Property Name", dispid, clsid, vtType)

          // PROP_PAGE(CLSID_StockColorPage)

    END_PROP_MAP()

     

     

    BEGIN_MSG_MAP(CMyComClass)

          CHAIN_MSG_MAP(CComControl<CMyComClass>)

          DEFAULT_REFLECTION_HANDLER()

    END_MSG_MAP()

    . . .

          STDMETHOD(MyComMethod)(LONG* pCaps, VARIANT_BOOL* pbSuccess);

     

    }

     


    Christine Murphy Software Engineer
    Friday, July 22, 2011 5:49 PM
  • It would be awesome if I could do that.

    However, the type.GetMethod returns null.

    This has shown that my example is off by a little.  It is actually a class which implements an interface.  When I look at OLE/Com Object viewer, the class that I want shows only one thing underneath it (the interface that it implements).  That interface, then has the function.

    I tried creating a type object of that interface, and I got null.  com_method_type.GetInterfaces() returns no interfaces.

    So, here's the snippet of what the class looks like in c++

     

    Code

    C++ Signature (from tli file)

                    inline VARIANT_BOOL IBiosManager::MyCOMFunction ( long * pCaps )

    {

      //This function casts pCaps to MyStructure and then sets values on it.

    }

    C++ signature (from Ole/Com object viewer)

    [id(0x6002002b), helpstring("method MyComMethod")]

    HRESULT MyComMethod (

                    [out] long* pCaps,

                    [out, retval] VARIANT_BOOL* pbSuccess);

     

    C++ code file

    class ATL_NO_VTABLE CMyComClass :

          public CComObjectRootEx<CComSingleThreadModel>,

          public IDispatchImpl<IMyComClass, &IID_IMyComClass, &LIBID_MyNamespaceLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,

          public IPersistStreamInitImpl< CMyComClass >,

          public IOleControlImpl< CMyComClass >,

          public IOleObjectImpl< CMyComClass >,

          public IOleInPlaceActiveObjectImpl< CMyComClass >,

          public IViewObjectExImpl< CMyComClass >,

          public IOleInPlaceObjectWindowlessImpl< CMyComClass >,

          public ISupportErrorInfo,

          public IPersistStorageImpl< CMyComClass >,

          public ISpecifyPropertyPagesImpl< CMyComClass >,

          public IQuickActivateImpl< CMyComClass >,

    #ifndef _WIN32_WCE

          public IDataObjectImpl< CMyComClass >,

    #endif

          public IProvideClassInfo2Impl<&CLSID_MyComClass, NULL, &LIBID_ MyNamespaceLib >,

    #ifdef _WIN32_WCE // IObjectSafety is required on Windows CE for the control to be loaded correctly

          public IObjectSafetyImpl<CBiosManager, INTERFACESAFE_FOR_UNTRUSTED_CALLER>,

    #endif

          public CComCoClass< CMyComClass, &CLSID_ MyComClass >,

          public CComControl< CMyComClass >

    {

    DECLARE_OLEMISC_STATUS(OLEMISC_RECOMPOSEONRESIZE |

          OLEMISC_CANTLINKINSIDE |

          OLEMISC_INSIDEOUT |

          OLEMISC_ACTIVATEWHENVISIBLE |

          OLEMISC_SETCLIENTSITEFIRST

    )

     

    DECLARE_REGISTRY_RESOURCEID(IDR_MyComClass1)

     

     

    BEGIN_COM_MAP(CMyComClass)

          COM_INTERFACE_ENTRY(IMyComClass)

          COM_INTERFACE_ENTRY(IDispatch)

          COM_INTERFACE_ENTRY(IViewObjectEx)

          COM_INTERFACE_ENTRY(IViewObject2)

          COM_INTERFACE_ENTRY(IViewObject)

          COM_INTERFACE_ENTRY(IOleInPlaceObjectWindowless)

          COM_INTERFACE_ENTRY(IOleInPlaceObject)

          COM_INTERFACE_ENTRY2(IOleWindow, IOleInPlaceObjectWindowless)

          COM_INTERFACE_ENTRY(IOleInPlaceActiveObject)

          COM_INTERFACE_ENTRY(IOleControl)

          COM_INTERFACE_ENTRY(IOleObject)

          COM_INTERFACE_ENTRY(IPersistStreamInit)

          COM_INTERFACE_ENTRY2(IPersist, IPersistStreamInit)

          COM_INTERFACE_ENTRY(ISupportErrorInfo)

          COM_INTERFACE_ENTRY(ISpecifyPropertyPages)

          COM_INTERFACE_ENTRY(IQuickActivate)

          COM_INTERFACE_ENTRY(IPersistStorage)

    #ifndef _WIN32_WCE

          COM_INTERFACE_ENTRY(IDataObject)

    #endif

          COM_INTERFACE_ENTRY(IProvideClassInfo)

          COM_INTERFACE_ENTRY(IProvideClassInfo2)

    #ifdef _WIN32_WCE // IObjectSafety is required on Windows CE for the control to be loaded correctly

          COM_INTERFACE_ENTRY_IID(IID_IObjectSafety, IObjectSafety)

    #endif

    END_COM_MAP()

    BEGIN_PROP_MAP(CMyComClass)

          PROP_DATA_ENTRY("_cx", m_sizeExtent.cx, VT_UI4)

          PROP_DATA_ENTRY("_cy", m_sizeExtent.cy, VT_UI4)

          // Example entries

          // PROP_ENTRY_TYPE("Property Name", dispid, clsid, vtType)

          // PROP_PAGE(CLSID_StockColorPage)

    END_PROP_MAP()

     

     

    BEGIN_MSG_MAP(CMyComClass)

          CHAIN_MSG_MAP(CComControl<CMyComClass>)

          DEFAULT_REFLECTION_HANDLER()

    END_MSG_MAP()

    . . .

          STDMETHOD(MyComMethod)(LONG* pCaps, VARIANT_BOOL* pbSuccess);

     

    }

     


    Christine Murphy Software Engineer


    Hi

    Sadly I know nothing about interop with COM nor do I have any C++ expertise and I think we need to get at that next.

    I'm confident that once you get that method into a delagate, you will be able to pass the struct as I have shown.

    Good luck and do keep us posted !

     

    Cap'n

     


    Friday, July 22, 2011 7:35 PM
  • Hello Christine,

     

    1. The Error.

    >> The function I need takes in a long* , casts it to a pointer to a structure, gathers some values and sets the corresponding members in the structure...

    >> [id(0x6002002b), helpstring("method MyComMethod")]
    >> HRESULT MyComMethod ([out] long* pCaps, [out, retval] VARIANT_BOOL* pbSuccess);


    1.1 This is a mistake. By declaring "pCaps" as a pointer to a long, the IDL treats it as it is : a pointer to a single long value.

    1.2 You may have had success when the client was a C++ application and the client code (which called MyComMethod()) lives in the same apartment as the IBiosManager implementation (i.e. CMyComClass) itself. This avoided the need for inter-apartment marshaling which resulted in the fortuitous situation where "pCaps" can be treated as a pointer (to a MyStructure structure).

    1.3 This, however, is a very risky way of returning a structure because once inter-apartement marshaling gets involed, the "pCaps" gets treated as a pointer to a single long value.

     

    2. Using Type.InvokeMember().

    2.1 Using Type.InvokeMember() is equivalent to using the IDispatch::Invoke() COM late-bound call.

    2.2 This will also not work for two reasons :

    • The structure contains a character pointer member field which disqualifies it from being insertable into a VT_RECORD VARIANT.
    • A VT_RECORD VARIANT cannot be returned (via a reference) from a Type.InvokeMember() call.

    2.3 Furthermore, COM method calls from managed code always involves both interop marshaling and COM inter-apartment marshaling. With code like the following :

    private void CallMyFunction(ref object obj)
    {
      MyStructure struct = new MyStructure ();
      IntPtr pStruct = Marshal.AllocHGlobal(Marshal.SizeOf(struct));

      // Copy the struct to unmanaged memory.
      Marshal.StructureToPtr(struct, pStruct, false);

      Type ComType = obj.GetType();
      object[] parameters = new object[1];
      parameters[0] = pStruct;
     
      // Call the function
      ComType.InvokeMember("MyCOMFunction ",BindingFlags.InvokeMethod,null, obj, parameters);
      ...
      ...
      ...
    }

    "pStruct" is treated as a pointer to a long value, nothing more.

     

    3. The Correct Way to Declare "pCaps".

    3.1 The correct way to declare "pCaps" is to define it as a pointer to the "MyStructure" structure, e.g. :

    [id(2), helpstring("method MyCOMFunction2")] HRESULT MyCOMFunction2([out] MyStructure* pCaps, [out, retval] VARIANT_BOOL* pbSuccess);

     

    3.2 Furthermore, the "char1" field member of "MyStructure" must be decorated with the [string] IDL attribute :

    typedef struct MyStructure
    {
      int i1;
      [string]
      char* char1;  
      int i2;
    } MyStructure;

    The [string] IDL attribute makes "char1" a C-style string (i.e. a NULL-terminated character array).

    Without the [string] IDL attribute, "char1" is (you should know by now) a pointer to a single character.


    4. Sample Code.

    4.1 The following is a sample C++ code for MyCOMFunction2() :

    STDMETHODIMP CBiosManager::MyCOMFunction2(MyStructure* pCaps, VARIANT_BOOL* pbSuccess)
    {
     // TODO: Add your implementation code here
     pCaps -> i1 = 0;
     pCaps -> i2 = 1;
     char szNewString[] = "Hello World";
     pCaps -> char1 = (unsigned char*)::CoTaskMemAlloc(sizeof(szNewString));
     strcpy((char*)(pCaps -> char1), szNewString);
     
     *pbSuccess = VARIANT_TRUE;

     return S_OK;
    }

    Note that because "char1" is a character string, it must be allocated via ::CoTaskMemAlloc(). It cannot be allocated via "new" or "malloc()". The reason for this will be explained in section 5 below where we discuss the string exchange protocol between managed and unmanaged code.


    4.2 The following is a sample C# calling code :

    private static void CallMyFunction2(ref object obj)
    {
      IBiosManager pIBiosManager = (IBiosManager)obj;

      // Declare a MyStructure struct but do not initialize it.
      MyStructure mystruct;
      // It is MyCOMFunction2() that will initialize it with values.
      bool bSuccess = pIBiosManager.MyCOMFunction2(out mystruct);

      Console.WriteLine("MyStructure.i1    : [{0:D}]", mystruct.i1);
      Console.WriteLine("MyStructure.i2    : [{0:D}]", mystruct.i2);
      Console.WriteLine("MyStructure.char1 : [{0:D}]", mystruct.char1);
    }

    Note that the C# code declares a MyStructure structure but does not initialize it. It is MyCOMFunction2() that will initialize it with values.


    5. The Protocol for String Exchange between Managed Code and Unmanaged Code.

    5.1 The general protocol is that if the unmanaged side should allocate memory for a C-style string which is to be returned to the managed side, the ::CoTaskMemAlloc() API is to be used.

    5.2 Thereafter, the interop marshaler will use the unmanaged C-style string to create a managed string object.

    5.3 Now, because the unmanaged string was returned to the managed side, it is the managed side that owns this string memory (even though it is an unmanaged string). The onus is on the managed side (i.e. the interop marshaler) to free this memory.

    5.4 The managed side will then use Marshal.FreeCoTaskMem() (which eventually calls ::CoTaskMemFree()) to free the memory allocated for the unmanaged string.

    5.5 This is the reason why only ::CoTaskMemAlloc() can be used to allocate the memory for the string in "MyStructure.char1". If "new" or "malloc()" was used, the interop marshaler will not know how to free the string. This is because "new" and "malloc()" are C/C++ library dependent. The ::CoTaskMemAlloc() API is a Windows API and is standard.


    - Bio.

     


    Please visit my blog : http://limbioliong.wordpress.com/
    • Proposed as answer by Paul Zhou Tuesday, August 2, 2011 6:20 AM
    • Marked as answer by Paul Zhou Wednesday, August 3, 2011 2:13 AM
    Sunday, July 24, 2011 8:45 AM