none
exception calling mannaged function from unmanaged code passing int32 array RRS feed

  • Question

  • Need help!!!  tried to follow some of the threads on this forum but I still miss something since I get the exception when calling function with array (or reference to array) as a parameter no matter what I tried

     

    Get exception when trying to call managed code DLL (C#) from unmanaged code transferring an array as parameter

     

    Have no problem with the same code when transferring int or other scalar parameter.

     

    I appreciate any help, following are the details:

     

    I have a library tst3mn.dll: managed C# DLL produced with Visual Studio 2010 and .NET 4

    It also generated a Type Library tst3mn.tlb

    I created a test application tst3um.exe and used the Visual Studio 2010 class wizard to create the com wrapper Ctst3mn.h

     

    The problem:

     

    I expose from the managed DLL two methods, one accept two int types and return their sum, this function works well

     

    I exposed additional function that accept an array parameter and when the execution reach the call to t5his function it cause exception, so it looks as the problem is in calling or marshaling function with array parameter over the unmanaged to managed boundaries.

     

    =======    The code for the C# managed DLL:  =========

     

    using System;

    using System.Collections.Generic;

    using System.Linq;

    using System.Text;

    using System.Runtime.InteropServices;

     

    namespace tst3mn  {

     

    [ComVisible(true)]

    [Guid("8D7C9492-FA0E-4384-8883-98CF895D4838")]

    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]

    public interface Itst3mn  {

     

    [DispId(1)]

    int Add(int a, int b);

     

    [DispId(2)]

    bool SetArr([In] ref Int32[] inArr);

    }

     

     

     

     

     

    [ComVisible(true)]

    [Guid("F8F316C1-9F46-4BF2-9142-55DF870FADFC")]

    [ClassInterface(ClassInterfaceType.None)]

    [ProgId("tst3mn.Ctst3mn")]

    public class Ctst3mn : Itst3mn  {

     

    public int Add(int a, int b)  {

     

    return (a + b);

    }

     

     

    public bool SetArr( [In] ref Int32[] inArr )  {

     

    return   true;

    }

     

    }

     

    }

     

     

     

    =======   The wrapper class generated from the type library:  ========

     

    // Machine generated IDispatch wrapper class(es) created with Add Class from Typelib Wizard

    #import "H:\\temp\\test3\\tst3mn\\tst3mn\\bin\\Debug\\tst3mn.tlb" no_namespace

    // Ctst3mn wrapper class

    class Ctst3mn : public COleDispatchDriver

    {

     

    public:

    Ctst3mn(){} // Calls COleDispatchDriver default constructor

    Ctst3mn(LPDISPATCH pDispatch) : COleDispatchDriver(pDispatch) {}

    Ctst3mn(const Ctst3mn& dispatchSrc) : COleDispatchDriver(dispatchSrc) {}

     

    // Attributes

    public:

     

    // Operations

    public: 

     

    // Itst3mn methods

     

    public:

    long Add(long a, long b)   {

     

    long result;

    static BYTE parms[] = VTS_I4 VTS_I4 ;

    InvokeHelper(0x1, DISPATCH_METHOD, VT_I4, (void*)&result, parms, a, b);

    return result;

    }

     

     

    BOOL SetArr(SAFEARRAY * * inArr)  {

     

    BOOL result;

    static BYTE parms[] = VTS_UNKNOWN ;

    InvokeHelper(0x2, DISPATCH_METHOD, VT_BOOL, (void*)&result, parms, inArr);

    return result;

    }

     

    // Itst3mn properties

     

    public:

     

    };

     

     

     

    ====== The "main test program contain the following code:  =======

     

    Ctst3mn user;

    CLSID clsid;

    HRESULT hr;

     

    CComBSTR bstr = "{F8F316C1-9F46-4BF2-9142-55DF870FADFC}";

    hr = CLSIDFromString( bstr.m_str , &clsid); // F8F316C1-9F46-4BF2-9142-55DF870FADFC

    if (hr != NOERROR)

             return FALSE;

     

    if (user.m_lpDispatch == NULL && !user.CreateDispatch( clsid, &e))

    return FALSE;

     

    int i = user.Add(1,2);

     

    unsigned int arr_a[10] = {0,1,2,3,4,5,6,7,8,9};

    SAFEARRAY *p_saArray = SafeArrayCreateVector( VT_UI4, 0, 10 );

    p_saArray->pvData = arr_a;

    user.SetArr( &p_saArray );

     

     

     

    Get the exception:

    Unhandled exception at 0x75c2d36f (KernelBase.dll) in tst3UM.exe: Microsoft C++ exception: COleException at memory location 0x0027f698..

     

     

     

     

     

     

     

     

     

     

     


    DA
    Sunday, July 31, 2011 8:31 PM

Answers

  • Hello KeyScan,

     

    1. Limitations with the MFC COleDispatchDriver::InvokeHelper() Method.

    1.1 There are 2 causes of the problem :

    • The wizard-generated wrapper for the SetArr() method.
    • Current limitations on parameter type expressions available for COleDispatchDriver::InvokeHelper().

    1.2 The first problem is directly caused by the second. To understand this, observe the Ctst3mn wrapper for the SetArr() method :

    BOOL SetArr(SAFEARRAY * * inArr) 
    {
      BOOL result;
      static BYTE parms[] = VTS_UNKNOWN ;
      InvokeHelper(0x2, DISPATCH_METHOD, VT_BOOL, (void*)&result, parms, inArr);
      return result;
    }

    Note "parms". This indicates the type of the parameter for the SetArr() method. It indicates that it is of IUnknown pointer type.

    1.3 Compare this with the wrapper for Add() method :

    VTS_I4 VTS_I4

    which indicates that the first and second parameters are of type VT_I4 (4-byte integral value, i.e. 32-bit integer).

    1.4 The wizard-generated wrapper for SetArr() is obviously incorrect because we know that the parameter for SetArr() is an array of 32-bit integers, However, the problem is that the COleDispatchDriver::InvokeHelper() method is not able to accept SAFEARRAY parameters. There are no VTS_* constants that indicate a SafeArray parameter type. This is a limitation.

    1.5 There are 2 solutions that you can use to workaround the problem. These are explained in the next 2 sections.

     

    2. Workaround 1.

    2.1 Before we discuss the workaround, note that the declararion for the SetArr() method must be declared with both [In] and [Out] arrtibutes :

    bool SetArr([In][Out] ref Int32[] inArr);

    If the OutAttribute is omitted, changes made to "inArr" in the C# implementation will not be received in the client code.

    2.2 We now discuss the workaround. The first workaround is to directly call the IDispatch::Invoke() mehod. That is, do not use COleDispatchDriver::InvokeHelper().

    2.3 The following is a sample code that demonstrates this :

    void Ctst3umDlg::OnBnClickedButtonDoTest()
    {
     // TODO: Add your control notification handler code here
     Ctst3mn   user;
     CLSID   clsid;
     COleException e;
     HRESULT   hr;

     CComBSTR bstr = "{F8F316C1-9F46-4BF2-9142-55DF870FADFC}";

     hr = CLSIDFromString( bstr.m_str , &clsid); // F8F316C1-9F46-4BF2-9142-55DF870FADFC

     if (hr != NOERROR)
      return;

     if (user.m_lpDispatch == NULL && !user.CreateDispatch( clsid, &e))
      return;

     int i = user.Add(1,2);

     unsigned int arr_a[10] = {0,1,2,3,4,5,6,7,8,9};
     // Note that you must change use VT_I4 instead of VT_UI4.
     // VT_I4 corresponds to the C# Int32 type.
     SAFEARRAY *p_saArray = SafeArrayCreateVector( /*VT_UI4*/ VT_I4, 0, 10 );
     p_saArray->pvData = arr_a;
      
     //user.SetArr( &var );
     DISPPARAMS dp;
     VARIANTARG Arg;
     VARIANTARG vr;
     
     VariantInit(&vr);
     
     VariantInit(&Arg);
     V_VT(&Arg) = (VT_BYREF | VT_ARRAY | VT_I4);
     V_ARRAYREF(&Arg) = &p_saArray; 

     memset(&dp, 0, sizeof(DISPPARAMS));
     dp.cArgs = 1;
     dp.rgvarg = &Arg;
     
     // Obtain the ID of the SetArr() method.
     OLECHAR FAR* rgszNames = L"SetArr";
     DISPID   dispid = 0;
     EXCEPINFO  excepinfo;

     (user.m_lpDispatch) -> GetIDsOfNames
     (
      IID_NULL,                 
      &rgszNames, 
      1,         
      LOCALE_SYSTEM_DEFAULT,                  
      &dispid         
     );
     
         // Finally making the call
     UINT nErrArg;
     hr = (user.m_lpDispatch) -> Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dp, &vr, &excepinfo, &nErrArg);
     
    }

    2.4 Note well that when you call SafeArrayCreateVector() you must set VT_I4 instead of VT_UI4. VT_I4 corresponds to the C# Int32 type. Using VT_UI4 will cause type mismatch.

     

    3. Workaround 2.

    3.1 The second workaround is to change the SetArr() method parameter type from "ref Int32[]" to "ref object", Listed below is a sample implementation using a new SetArrByObject() method :

        public interface Itst3mn
        {
            ...

            [DispId(3)]
            bool SetArrByObject([In][Out] ref object inArr);
        }

        public class Ctst3mn : Itst3mn
        {
            ...
            public bool SetArrByObject([In][Out] ref object inArr)
            {
                // Cast inArr to Int32[].
                Int32[] arr = (Int32[])inArr;
                int i = 0;

                // Modify the values.
                for (i = 0; i < arr.Length; i++)
                {
                    arr[i] += 10;
                }

                return true;
            }
        }

    3.2 The wizard will generate the following wrapper :

     BOOL SetArrByObject(VARIANT * inArr)
     {
      BOOL result;
      static BYTE parms[] = VTS_PVARIANT ;
      InvokeHelper(0x3, DISPATCH_METHOD, VT_BOOL, (void*)&result, parms, inArr);
      return result;
     }

    3.3 Here is a sample client calling code :

    void Ctst3umDlg::OnBnClickedButtonDoTest2()
    {
     // TODO: Add your control notification handler code here
     Ctst3mn   user;
     CLSID   clsid;
     COleException e;
     HRESULT   hr;

     CComBSTR bstr = "{F8F316C1-9F46-4BF2-9142-55DF870FADFC}";

     hr = CLSIDFromString( bstr.m_str , &clsid); // F8F316C1-9F46-4BF2-9142-55DF870FADFC

     if (hr != NOERROR)
      return;

     if (user.m_lpDispatch == NULL && !user.CreateDispatch( clsid, &e))
      return;

     int i = user.Add(1,2);

     unsigned int arr_a[10] = {0,1,2,3,4,5,6,7,8,9};
     SAFEARRAY *p_saArray = SafeArrayCreateVector( VT_I4, 0, 10 );
     p_saArray->pvData = arr_a;
     
     VARIANT var;
     
     VariantInit(&var);
     V_VT(&var) = (VT_ARRAY | VT_I4);
     V_ARRAY(&var) = p_saArray;
      
     user.SetArrByObject( &var ); 
    }

     

    - Bio.

     


    Please visit my blog : http://limbioliong.wordpress.com/
    • Marked as answer by KeyScan Wednesday, August 3, 2011 2:00 PM
    Wednesday, August 3, 2011 9:53 AM

All replies

  • Hi,

     

    Thank you for your question, we're doing research on this case, it might take some time before we get back to you.


    Eric Yang [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Monday, August 1, 2011 6:41 AM
  • Hello KeyScan,

     

    1. Limitations with the MFC COleDispatchDriver::InvokeHelper() Method.

    1.1 There are 2 causes of the problem :

    • The wizard-generated wrapper for the SetArr() method.
    • Current limitations on parameter type expressions available for COleDispatchDriver::InvokeHelper().

    1.2 The first problem is directly caused by the second. To understand this, observe the Ctst3mn wrapper for the SetArr() method :

    BOOL SetArr(SAFEARRAY * * inArr) 
    {
      BOOL result;
      static BYTE parms[] = VTS_UNKNOWN ;
      InvokeHelper(0x2, DISPATCH_METHOD, VT_BOOL, (void*)&result, parms, inArr);
      return result;
    }

    Note "parms". This indicates the type of the parameter for the SetArr() method. It indicates that it is of IUnknown pointer type.

    1.3 Compare this with the wrapper for Add() method :

    VTS_I4 VTS_I4

    which indicates that the first and second parameters are of type VT_I4 (4-byte integral value, i.e. 32-bit integer).

    1.4 The wizard-generated wrapper for SetArr() is obviously incorrect because we know that the parameter for SetArr() is an array of 32-bit integers, However, the problem is that the COleDispatchDriver::InvokeHelper() method is not able to accept SAFEARRAY parameters. There are no VTS_* constants that indicate a SafeArray parameter type. This is a limitation.

    1.5 There are 2 solutions that you can use to workaround the problem. These are explained in the next 2 sections.

     

    2. Workaround 1.

    2.1 Before we discuss the workaround, note that the declararion for the SetArr() method must be declared with both [In] and [Out] arrtibutes :

    bool SetArr([In][Out] ref Int32[] inArr);

    If the OutAttribute is omitted, changes made to "inArr" in the C# implementation will not be received in the client code.

    2.2 We now discuss the workaround. The first workaround is to directly call the IDispatch::Invoke() mehod. That is, do not use COleDispatchDriver::InvokeHelper().

    2.3 The following is a sample code that demonstrates this :

    void Ctst3umDlg::OnBnClickedButtonDoTest()
    {
     // TODO: Add your control notification handler code here
     Ctst3mn   user;
     CLSID   clsid;
     COleException e;
     HRESULT   hr;

     CComBSTR bstr = "{F8F316C1-9F46-4BF2-9142-55DF870FADFC}";

     hr = CLSIDFromString( bstr.m_str , &clsid); // F8F316C1-9F46-4BF2-9142-55DF870FADFC

     if (hr != NOERROR)
      return;

     if (user.m_lpDispatch == NULL && !user.CreateDispatch( clsid, &e))
      return;

     int i = user.Add(1,2);

     unsigned int arr_a[10] = {0,1,2,3,4,5,6,7,8,9};
     // Note that you must change use VT_I4 instead of VT_UI4.
     // VT_I4 corresponds to the C# Int32 type.
     SAFEARRAY *p_saArray = SafeArrayCreateVector( /*VT_UI4*/ VT_I4, 0, 10 );
     p_saArray->pvData = arr_a;
      
     //user.SetArr( &var );
     DISPPARAMS dp;
     VARIANTARG Arg;
     VARIANTARG vr;
     
     VariantInit(&vr);
     
     VariantInit(&Arg);
     V_VT(&Arg) = (VT_BYREF | VT_ARRAY | VT_I4);
     V_ARRAYREF(&Arg) = &p_saArray; 

     memset(&dp, 0, sizeof(DISPPARAMS));
     dp.cArgs = 1;
     dp.rgvarg = &Arg;
     
     // Obtain the ID of the SetArr() method.
     OLECHAR FAR* rgszNames = L"SetArr";
     DISPID   dispid = 0;
     EXCEPINFO  excepinfo;

     (user.m_lpDispatch) -> GetIDsOfNames
     (
      IID_NULL,                 
      &rgszNames, 
      1,         
      LOCALE_SYSTEM_DEFAULT,                  
      &dispid         
     );
     
         // Finally making the call
     UINT nErrArg;
     hr = (user.m_lpDispatch) -> Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dp, &vr, &excepinfo, &nErrArg);
     
    }

    2.4 Note well that when you call SafeArrayCreateVector() you must set VT_I4 instead of VT_UI4. VT_I4 corresponds to the C# Int32 type. Using VT_UI4 will cause type mismatch.

     

    3. Workaround 2.

    3.1 The second workaround is to change the SetArr() method parameter type from "ref Int32[]" to "ref object", Listed below is a sample implementation using a new SetArrByObject() method :

        public interface Itst3mn
        {
            ...

            [DispId(3)]
            bool SetArrByObject([In][Out] ref object inArr);
        }

        public class Ctst3mn : Itst3mn
        {
            ...
            public bool SetArrByObject([In][Out] ref object inArr)
            {
                // Cast inArr to Int32[].
                Int32[] arr = (Int32[])inArr;
                int i = 0;

                // Modify the values.
                for (i = 0; i < arr.Length; i++)
                {
                    arr[i] += 10;
                }

                return true;
            }
        }

    3.2 The wizard will generate the following wrapper :

     BOOL SetArrByObject(VARIANT * inArr)
     {
      BOOL result;
      static BYTE parms[] = VTS_PVARIANT ;
      InvokeHelper(0x3, DISPATCH_METHOD, VT_BOOL, (void*)&result, parms, inArr);
      return result;
     }

    3.3 Here is a sample client calling code :

    void Ctst3umDlg::OnBnClickedButtonDoTest2()
    {
     // TODO: Add your control notification handler code here
     Ctst3mn   user;
     CLSID   clsid;
     COleException e;
     HRESULT   hr;

     CComBSTR bstr = "{F8F316C1-9F46-4BF2-9142-55DF870FADFC}";

     hr = CLSIDFromString( bstr.m_str , &clsid); // F8F316C1-9F46-4BF2-9142-55DF870FADFC

     if (hr != NOERROR)
      return;

     if (user.m_lpDispatch == NULL && !user.CreateDispatch( clsid, &e))
      return;

     int i = user.Add(1,2);

     unsigned int arr_a[10] = {0,1,2,3,4,5,6,7,8,9};
     SAFEARRAY *p_saArray = SafeArrayCreateVector( VT_I4, 0, 10 );
     p_saArray->pvData = arr_a;
     
     VARIANT var;
     
     VariantInit(&var);
     V_VT(&var) = (VT_ARRAY | VT_I4);
     V_ARRAY(&var) = p_saArray;
      
     user.SetArrByObject( &var ); 
    }

     

    - Bio.

     


    Please visit my blog : http://limbioliong.wordpress.com/
    • Marked as answer by KeyScan Wednesday, August 3, 2011 2:00 PM
    Wednesday, August 3, 2011 9:53 AM
  • Bio,

    Thanks for the detailed feedback it is most helpful

    In addition I must admit that I found a silly bug in my initial implementation:

    On the umanaged calling side I had-

    unsigned int arr_a[10] = {0,1,2,3,4,5,6,7,8,9};

    SAFEARRAY *p_saArray = SafeArrayCreateVector( VT_UI4, 0, 10 );

    p_saArray->pvData = arr_a;

    user.SetArr( &p_saArray );

    The problem was the assignment of the arr_a[] into the safearray this broke the safearray that expect one memory block allocated by the create vector function

    What it should have been is-

     

    unsigned int

    arr_a[10] = {0,1,2,3,4,5,6,7,8,9};

    SAFEARRAY *p_saArray = SafeArrayCreateVector( VT_I4, 0, 10 );

    memcpy( p_saArray->pvData, arr_a, 10*

    sizeof(unsigned int

    ));

    b = COM_ptr->SetArr(&p_saArray);

     

    once I fixed it everything works, this leads to the conclusion that transferring the SAFEARRAY using the unknown interface does have enough information for the managed code to restore it automatically into the compatible array since the type information is there.

    Thanks again for you helpful detailed feedback it is well appreciated.

     

     

     

     

     

     

     


    DA
    Wednesday, August 3, 2011 2:00 PM