none
COM interop: issue with C-style array of interface pointers RRS feed

  • Question

  • Dear community,

    I have an issue with COM interop. I need to marshal a C-Style array of interface pointers.  I have changed IL to get this array marshaled correctly but I have issues accessing the array elements in the managed code.  Below are the IDL as well as my three tries that did not work.

    Any suggestions?
    Thanks,
    dpomt

     

    IDL

    [local] HRESULT Next([in] ULONG cElems, [out] ISomeObject *prgElems, [out] ULONG *pcFetched); \

             [call_as(Next)] HRESULT RemNext([in] ULONG cElems, [out, size_is(cElems), length_is(*pcFetched)] xxx *prgElems, [out] ULONG *pcFetched); \

     

    Generated IL (tlbimp+ildasm)

    instance void  RemNext([in] uint32 cElems,
                   [out] class SomeLib.ISomeObject&  marshal( interface ) prgElems,
                   [out] uint32& pcFetched) runtime managed internalcall

     

     

    Try #1:
    change il to and use then ilasm to recompile:

    a) //[in][out] native int& pElemens ==> ref pElements

    b) //[out] native int& pElemens ==> out pElements

    IntPtr pElements = Marshal.AllocCoTaskMem(iFetchSize * Marshal.SizeOf(typeof(int)));

     

    try

    {

       while (true)                   

       {

          int iFetchSize = 25;

          int hr = enumBDO.RemNext((uint)iFetchSize, ref pElements, out uiFetched);

          // ^^^ a) ref pElements, b) out pElements

          if (hr < 0)

             Marshal.ThrowExceptionForHR(hr);

                                                                               

          IntPtr pCurrent = pElements; // array!

          for (int i = 0; i < uiFetched; ++i)

          {

             ISomeObject bdoTmp = (ISomeObject)Marshal.GetObjectForIUnknown(pCurrent);  

             //bdoTmp is COMObject but accessing it fails (COMObject is null)

             listBDOs.Add(bdoTmp);

             pCurrent = (IntPtr)((int)pCurrent + Marshal.SizeOf(typeof(int)));

          }

                          

          if (hr == HRESULTS.S_FALSE)

             break;

       }

    }

    finally

    {

       Marshal.FreeCoTaskMem(pElements);
    }

     

    è bdoTmp here is a __COMObject, but its content is null

     

     

     

    Try #2:
    same change of il code as #1 but different code accessing IntPtr
    a) //[in][out] native int& pElemens ==> ref pElements

    b) //[out] native int& pElemens ==> out pElements

     

    IntPtr pElements = Marshal.AllocCoTaskMem(iFetchSize * Marshal.SizeOf(typeof(int)));

     

    try

    {

       while (true)                   

       {

          int iFetchSize = 25;

          int hr = enumBDO.RemNext((uint)iFetchSize, ref pElements, out uiFetched);

          // ^^^ a) ref pElements, b) out pElements

          if (hr < 0)

             Marshal.ThrowExceptionForHR(hr);

                                                                              

          IntPtr[] managedArray2 = new IntPtr[uiFetched];

          Marshal.Copy(pElements, managedArray2, 0, (int)uiFetched);

          foreach (IntPtr p in managedArray2)

          {

                ISomeObject bdoTmp = (ISomeObject)Marshal.GetObjectForIUnknown(p);
                // ^^^{"Unknown error (Exception from HRESULT: 0x80004005 (E_FAIL))"}

                // System.SystemException {System.Runtime.InteropServices.COMException}

     

                listBDOs.Add(bdoTmp);

            }

            if (hr == HRESULTS.S_FALSE)

              break;

       }

    }

    finally

    {

       Marshal.FreeCoTaskMem(pElements);
    }

     

    è Marshal.GetObjectForIUnknown(p); will fail here (see error message on comment above)

     

     

     

    Try #3:
    same change of il code as #1 but different code accessing IntPtr
    a) //[in][out] native int& pElemens ==> ref pElements

    b) //[out] native int& pElemens ==> out pElements

     

    IntPtr pElements = Marshal.AllocCoTaskMem(iFetchSize * Marshal.SizeOf(typeof(int)));

     

    try

    {

       while (true)                   

       {

          int iFetchSize = 25;

          int hr = enumBDO.RemNext((uint)iFetchSize, ref pElements, out uiFetched);

          // ^^^ a) ref pElements, b) out pElements

          if (hr < 0)

             Marshal.ThrowExceptionForHR(hr);

                                                                              

             ISomeObject[] bdoArr = new ISomeObject [iFetchSize];                                                   

             int hr = enumBDO.RemNext((uint)iFetchSize, ref bdoArr, out uiFetched); // crash here

             if (hr < 0)

                Marshal.ThrowExceptionForHR(hr);

                   

             for (int i=0; i<uiFetched; ++i)

             {

                ISomeObject bdoTmp = bdoArr[i];

                listBDOs.Add(bdoTmp);

             }  

          }

    }

    finally

    {

       Marshal.FreeCoTaskMem(pElements);
    }

     

    è application crashes for call enumBDO.RemNext

             

    Any ideas what I am acutally doing wrong?

    I read somewhere to use LPARRAY with sizeparam but did not find a possibily to to this in IL.

     

    Friday, April 1, 2011 10:35 AM

Answers

  • Hello DPOMT,

     

    1. Some Errros in IDL Declaration.

    First please note that the IDL declaration for the "Next()" and "RemNext()" methods are not correct :

    [local] HRESULT Next([in] ULONG cElems, [out] ISomeObject *prgElems, [out] ULONG *pcFetched);

    [call_as(Next)] HRESULT RemNext([in] ULONG cElems, [out, size_is(cElems), length_is(*pcFetched)] xxx *prgElems, [out] ULONG *pcFetched); 

    1.1 For the Next() method, the second "out" parameter "prgElems" should have been declared as "[out] ISomeObject** prgElems". This is required of an "out" parameter which returns an array of ISomeObject interface pointers.

    1.2 For the RemNext() method, what exactly is "xxx" ? For now I shall assume that it is "ISomeObject". Then again, it needs to be declared as "ISomeObject** prgElems".

    1.3 Judging from the signature of the Next() and RemNext() methods, it appears that the interface that contains them is a variation of an IEnumXXX interface. I will assume so and for the rest of this discussion, I shall refer to this interface as IEnumBDO (I took the cue from the sample code listed in the OP).

    1.4 I also assume that proper working code has been created for the IEnumBDO_Next_Proxy() and IEnumBDO_Next_Stub() functions for facilitating the [local] and [call_as()] IDL attributes for the interface that contains the Next() and RemNext() methods.

    1.5 It is important that the proxy/stub marshaler DLL for the IEnumBDO interface be successfully built and registered. Failure of which may cause failure of the RemNext() method. This will be especially so if the implementation for the IEnumBDO interface lives in a separate apartment from the C# client code.

     

    2. Suggestion For IL Modification For RemNext() Method.

    2.1 Instead of the following modification for RemNext() in the generated IL :

    instance void  RemNext([in] uint32 cElems,
      [in][out] native int& pElemens, 
      [out] uint32& pcFetched) runtime managed internalcall

    Use the following instead :

    instance void  RemNext([in] uint32 cElems,
      [in] native int pElemens,
      [out] uint32& pcFetched) runtime managed internalcall

    2.2 This is because at runtime, the proxy/stub DLL will require only a pointer to the buffer that has been assigned by the caller that will be filled with ISomeInterface pointers by RemNext(). Hence all that is required is an IntPtr that is the address of this buffer. It need not be passed by reference because we do not expect RemNext() to modify it. Passing an 'in' IntPtr will do.

    2.3 If no cross-apartment marshaling is involved, RemNext() can work directly with this buffer. If cross-apartment marshaling gets involved, it is the job of the proxy/stub marshaler DLL to fill this buffer with ISomeInterface pointers returned by RemNext().

    2.4 This will produce the following C# signature for the RemNext() method :

    void RemNext(uint cElems, System.IntPtr pElemens, out uint pcFetched);

    2.5 The following is a sample call to RemNext() based on code presented in the OP :

                int iFetchSize = 25;
                uint uiFetched = 0;
                IntPtr pElements = Marshal.AllocCoTaskMem(iFetchSize * Marshal.SizeOf(typeof(IntPtr)));

                try
                {                   
                    enumBDO.RemNext((uint)iFetchSize, pElements, out uiFetched);
                       
                    IntPtr[] managedArray2 = new IntPtr[uiFetched];
                    Marshal.Copy(pElements, managedArray2, 0, (int)uiFetched);
                    foreach (IntPtr p in managedArray2)
                    {
                        ISomeObject bdoTmp = (ISomeObject)Marshal.GetObjectForIUnknown(p);
                        bdoTmp.SomeMethod(); // Call a method of ISomeObject for testing.
                    }
                }
                finally
                {
                    Marshal.FreeCoTaskMem(pElements);
                }

     

    3. Using an Array with SizeParam.

    3.1 >> I read somewhere to use LPARRAY with sizeparam but did not find a possibily to to this in IL.

    Yes there is a way to do this. I shall post again to provide sample codes.

     

    - Bio.

     



    • Marked as answer by DPOMT Tuesday, April 5, 2011 6:26 PM
    Sunday, April 3, 2011 8:39 AM
  • Hello DPOMT,

     

    1. Using an Array with SizeParam.

    1.1. >> I read somewhere to use LPARRAY with sizeparam but did not find a possibily to to this in IL.

    1.2. To do this, modify the RemNext() method in the IL to the following :

    instance void  RemNext([in] uint32 cElems,
             native int [] marshal([+0]) prgElems,
             [out] uint32& pcFetched) runtime managed internalcall

    The "marshal([+0])" syntax indicates that "prgElems" is to be treated as a C-style array and the SizeParamIndex is zero which indicates that the zero-based index of the parameter which is to hold the size of the "prgElems" array is the "cElems" parameter.

    Note that "cElems" is to hold the size of the "prgElems" array on entry to the RemNext() method. It cannot be used to indicate the size on return from RemNext(). Hence SizeParamIndex is not as powerful as the IDL size_is() attribute which can indicates the size of an array on input and on output.

    1.3 The C# signature produced by the Object Browser for RemNext() is :

    void RemNext(uint cElems, System.IntPtr[] prgElems, out uint pcFetched);

    It does not show the full syntax which should include at least the following MarshalAsAttribute for the "prgElems" parameter :

    void RemNext(uint cElems, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=0) ] System.IntPtr[] prgElems, out uint pcFetched);

     

    2. Sample Use Of This Version Of RemNext().

    2.1 The following is a sample code that demonstrates how to use this version of RemNext() :

                uint cElemsRequired = 8;
                IntPtr[] pElements = new IntPtr[cElemsRequired];
                uint pcFetched = 0;

                pIDPOMTObject01.RemNext(cElemsRequired, pElements, out pcFetched);

                for (int i = 0; i < pcFetched; i++)
                {
                    ISomeObject some_object = (ISomeObject)Marshal.GetObjectForIUnknown(pElements[i]);
                    some_object.SomeMethod(); // Call a method to test.
                }

     

    - Bio.

     


    • Marked as answer by DPOMT Tuesday, April 5, 2011 6:31 PM
    Sunday, April 3, 2011 9:17 AM

All replies

  • Hello DPOMT,

     

    1. Some Errros in IDL Declaration.

    First please note that the IDL declaration for the "Next()" and "RemNext()" methods are not correct :

    [local] HRESULT Next([in] ULONG cElems, [out] ISomeObject *prgElems, [out] ULONG *pcFetched);

    [call_as(Next)] HRESULT RemNext([in] ULONG cElems, [out, size_is(cElems), length_is(*pcFetched)] xxx *prgElems, [out] ULONG *pcFetched); 

    1.1 For the Next() method, the second "out" parameter "prgElems" should have been declared as "[out] ISomeObject** prgElems". This is required of an "out" parameter which returns an array of ISomeObject interface pointers.

    1.2 For the RemNext() method, what exactly is "xxx" ? For now I shall assume that it is "ISomeObject". Then again, it needs to be declared as "ISomeObject** prgElems".

    1.3 Judging from the signature of the Next() and RemNext() methods, it appears that the interface that contains them is a variation of an IEnumXXX interface. I will assume so and for the rest of this discussion, I shall refer to this interface as IEnumBDO (I took the cue from the sample code listed in the OP).

    1.4 I also assume that proper working code has been created for the IEnumBDO_Next_Proxy() and IEnumBDO_Next_Stub() functions for facilitating the [local] and [call_as()] IDL attributes for the interface that contains the Next() and RemNext() methods.

    1.5 It is important that the proxy/stub marshaler DLL for the IEnumBDO interface be successfully built and registered. Failure of which may cause failure of the RemNext() method. This will be especially so if the implementation for the IEnumBDO interface lives in a separate apartment from the C# client code.

     

    2. Suggestion For IL Modification For RemNext() Method.

    2.1 Instead of the following modification for RemNext() in the generated IL :

    instance void  RemNext([in] uint32 cElems,
      [in][out] native int& pElemens, 
      [out] uint32& pcFetched) runtime managed internalcall

    Use the following instead :

    instance void  RemNext([in] uint32 cElems,
      [in] native int pElemens,
      [out] uint32& pcFetched) runtime managed internalcall

    2.2 This is because at runtime, the proxy/stub DLL will require only a pointer to the buffer that has been assigned by the caller that will be filled with ISomeInterface pointers by RemNext(). Hence all that is required is an IntPtr that is the address of this buffer. It need not be passed by reference because we do not expect RemNext() to modify it. Passing an 'in' IntPtr will do.

    2.3 If no cross-apartment marshaling is involved, RemNext() can work directly with this buffer. If cross-apartment marshaling gets involved, it is the job of the proxy/stub marshaler DLL to fill this buffer with ISomeInterface pointers returned by RemNext().

    2.4 This will produce the following C# signature for the RemNext() method :

    void RemNext(uint cElems, System.IntPtr pElemens, out uint pcFetched);

    2.5 The following is a sample call to RemNext() based on code presented in the OP :

                int iFetchSize = 25;
                uint uiFetched = 0;
                IntPtr pElements = Marshal.AllocCoTaskMem(iFetchSize * Marshal.SizeOf(typeof(IntPtr)));

                try
                {                   
                    enumBDO.RemNext((uint)iFetchSize, pElements, out uiFetched);
                       
                    IntPtr[] managedArray2 = new IntPtr[uiFetched];
                    Marshal.Copy(pElements, managedArray2, 0, (int)uiFetched);
                    foreach (IntPtr p in managedArray2)
                    {
                        ISomeObject bdoTmp = (ISomeObject)Marshal.GetObjectForIUnknown(p);
                        bdoTmp.SomeMethod(); // Call a method of ISomeObject for testing.
                    }
                }
                finally
                {
                    Marshal.FreeCoTaskMem(pElements);
                }

     

    3. Using an Array with SizeParam.

    3.1 >> I read somewhere to use LPARRAY with sizeparam but did not find a possibily to to this in IL.

    Yes there is a way to do this. I shall post again to provide sample codes.

     

    - Bio.

     



    • Marked as answer by DPOMT Tuesday, April 5, 2011 6:26 PM
    Sunday, April 3, 2011 8:39 AM
  • Hello DPOMT,

     

    1. Using an Array with SizeParam.

    1.1. >> I read somewhere to use LPARRAY with sizeparam but did not find a possibily to to this in IL.

    1.2. To do this, modify the RemNext() method in the IL to the following :

    instance void  RemNext([in] uint32 cElems,
             native int [] marshal([+0]) prgElems,
             [out] uint32& pcFetched) runtime managed internalcall

    The "marshal([+0])" syntax indicates that "prgElems" is to be treated as a C-style array and the SizeParamIndex is zero which indicates that the zero-based index of the parameter which is to hold the size of the "prgElems" array is the "cElems" parameter.

    Note that "cElems" is to hold the size of the "prgElems" array on entry to the RemNext() method. It cannot be used to indicate the size on return from RemNext(). Hence SizeParamIndex is not as powerful as the IDL size_is() attribute which can indicates the size of an array on input and on output.

    1.3 The C# signature produced by the Object Browser for RemNext() is :

    void RemNext(uint cElems, System.IntPtr[] prgElems, out uint pcFetched);

    It does not show the full syntax which should include at least the following MarshalAsAttribute for the "prgElems" parameter :

    void RemNext(uint cElems, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=0) ] System.IntPtr[] prgElems, out uint pcFetched);

     

    2. Sample Use Of This Version Of RemNext().

    2.1 The following is a sample code that demonstrates how to use this version of RemNext() :

                uint cElemsRequired = 8;
                IntPtr[] pElements = new IntPtr[cElemsRequired];
                uint pcFetched = 0;

                pIDPOMTObject01.RemNext(cElemsRequired, pElements, out pcFetched);

                for (int i = 0; i < pcFetched; i++)
                {
                    ISomeObject some_object = (ISomeObject)Marshal.GetObjectForIUnknown(pElements[i]);
                    some_object.SomeMethod(); // Call a method to test.
                }

     

    - Bio.

     


    • Marked as answer by DPOMT Tuesday, April 5, 2011 6:31 PM
    Sunday, April 3, 2011 9:17 AM
  • Hello Bio,

    I changed the IL signature as described and it works now perfectly.
    Thanks a lot for providing the solution and excellent description.

    Concerning the IDL signature - this has been used for years in our software.
    I do not know where exactly got this from but it's labeled as (c) Don Box...

    For completeness, here is the desclaraton we use:

     

    //////////////////////////////////////////////////////////////////
    //
    // enumgen.h - Copyright 1997, Don Box
    //
    // MIDL/C Header that vastly simplifies defining enumerator interfaces
    // in IDL. This header file ensures that the generated proxy/stub DLL
    // uses the method aliasing technique to allow null for the 3rd parameter
    // to Next. It does this via trickery to say the least.
    //
    //
    // example:
    /*
        enumerator(long, IEnumLong, 12345678-1234-4567-2345-141425253636)
        enumerator(IDispatch *, IEnumDispatch, 12345679-1234-4567-2345-141425253636)

        [ uuid(1234567A-1234-4567-2345-141425253636), object ]
        interface IUseEm : IUnknown
        {
          HRESULT GetSomeLongs([out] IEnumLong **ppel);
          HRESULT CallSomeMethod([in] IEnumDispatch *ped, LPCOLESTR pwszMethodName);
        }
    */
    //

    #ifndef _ENUMGEN_H
    #define _ENUMGEN_H

    #if (defined(__midl))
    #define enumerator(xxx, IEnumXXX, guid) \
    [ uuid(guid), object, pointer_default(unique) ] \
    interface IEnumXXX : IUnknown { \
      [local] HRESULT Next([in] ULONG cElems, [out] xxx *prgElems, [out] ULONG *pcFetched); \
      [call_as(Next)] HRESULT RemNext([in] ULONG cElems, [out, size_is(cElems), length_is(*pcFetched)] xxx *prgElems, [out] ULONG *pcFetched); \
      HRESULT Skip([in] ULONG cElems); \
      HRESULT Reset(void); \
      HRESULT Clone([out] IEnumXXX **ppenum); \
    }\
    cpp_quote("#ifdef __midl_proxy")\
    cpp_quote("#include \"enumgen.h\"")\
    cpp_quote("GENERATE_NEXT_CALLAS(")\
    cpp_quote(#IEnumXXX)\
    cpp_quote(",")\
    cpp_quote(#xxx)\
    cpp_quote(")")\
    cpp_quote("#endif")
    #else
    #define GENERATE_NEXT_CALLAS(IEnumXXX, xxx) \
    static HRESULT STDMETHODCALLTYPE IEnumXXX##_Next_Proxy\
    (IEnumXXX* This, ULONG cElems, xxx *prgElems, ULONG *pcFetched) \
    {  ULONG cFetched; if (pcFetched == 0 && cElems > 1) return E_INVALIDARG; else return IEnumXXX##_RemNext_Proxy(This, cElems, prgElems, pcFetched ? pcFetched : &cFetched); } \
    static HRESULT STDMETHODCALLTYPE \
    IEnumXXX##_Next_Stub \
    (IEnumXXX * This, ULONG cElems, xxx *prgElems, ULONG *pcFetched) \
    {  HRESULT hr = This->lpVtbl->Next(This, cElems, prgElems, pcFetched); if (hr == S_OK && cElems == 1) *pcFetched = 1; return hr; }
    #endif
    #endif

     

    Best regards
    DPOMT

    Tuesday, April 5, 2011 6:30 PM
  • Hello Bio,

    thanks for providing that further solution.
    I have tried it and it worked perfectly.

    Again, thank you!

    Best regards
    DPOMT

    Tuesday, April 5, 2011 6:32 PM
  • Hello DPOMT,

    Congratulations. You are most welcome :-)

    - Bio.

     

    Wednesday, April 6, 2011 8:09 AM
  • This helped me too. It was not obvious that the array needs to be a regular input parameter. 

    One thing I thought I'd add is that I think this approach is leaking the ISomeObject pointers returned from the Next() enumerator function.  When calling a function which returns an IUnknown, it has already been AddRef'd.  Since this marshalling is doing so via IntPtr instead of using a CCW, .NET is unaware of this.  So the caller should call Marshal.Release(pElements[i]) to take care of this.

    Note that when calling Marshal.GetObjectForIUnknown() to get the IntPtr into a managed object, that function will do an additional AddRef as well.  .NET will cleanup this one, but not the original AddRef for the IntPtr in pElements[I].

    Jason Shay [MSFT]

    Friday, November 7, 2014 5:21 PM