none
How can I marshall a SAFEARRAY(UDT) from unmanaged to managed RRS feed

  • Question

  • Dear,

    I hope this is the right section to post this type of quesiton...

    I developed a simple COM DLL with the following IDL:

    // SMSFilter.idl : IDL source for SMSFilter
    //
    
    // This file will be processed by the MIDL tool to
    // produce the type library (SMSFilter.tlb) and marshalling code.
    
    import "oaidl.idl";
    import "ocidl.idl";
    
      typedef 
      [ 
        uuid(98426610-CCF2-4b96-91AF-A608A6F0B481),
        version(1.0),
        helpstring("Defines the action to take on a filter match")
      ]
      enum SMSFilterActionEnum
      {
        SMS_FLTR_NOT_NOTIFY  = 0x01,  ///< The SMS MUST NOT be notified to the user
        SMS_FLTR_COS         = 0x02,  ///< The SMS is been indentified as a Command
        SMS_FLTR_ARCHIEVE    = 0x04,  ///< The SMS is been archieved
        SMS_FLTR_MOVE_TO_FLD = 0x08,  ///< The SMS is moved to a folder
        SMS_FLTR_COPY_TO_FLD = 0x10,  ///< The SMS is copied to a folder
        SMS_FLTR_FORWARD     = 0x20,  ///< The SMS is been forwarded
        SMS_FLTR_REPLY       = 0x40,  ///< Is been answered to the sender
        SMS_FLTR_TO_STATE    = 0x80,  ///< The SMS will be translated into a state
        SMS_FLTR_TO_CALL     = 0x100, ///< The SMS will be translated into a call
        SMS_FLTR_TO_GPS      = 0x200  ///< The SMS will be translated into a GPS position
      } SMSFilterActionEnum;
    
    
      typedef 
      [ 
        uuid(6DDF95EA-A594-45fc-882B-B4A26153ADD3), 
        version(1.0),
        helpstring("Filed on which the filter will be applied")
      ]
      enum SMSFilterFieldEnum
      {
        FLT_FLD_TEXT = 0x1,
        FLT_FLD_MITT = 0x2,
        FLT_FLD_DEST = 0x4
      } SMSFilterFieldEnum;
    
    
      typedef 
      [ 
        uuid(BCB82C23-772D-4065-96A2-2D7495DFA74A), 
        version(1.0),
        helpstring("The type of the filter search")
      ]
      enum SMSFilterSearchTypeEnum
      {
        FLT_ST_CONTAINS   = 0x0,
        FLT_ST_EQUAL_TO   = 0x1,
        FLT_ST_BEGIN_WITH = 0x2,
        FLT_ST_END_WITH   = 0x4,
        FLT_ST_REGEX      = 0x8
      } SMSFilterSearchTypeEnum; 
    
    
      typedef 
      [ 
        uuid(717AE44C-5A94-4b56-AD28-C890CA271B93), 
        version(1.0),
        helpstring("An SMS")
      ]
      struct T_SMS
      {
        BSTR    CallingParty; ///< The sender
        BSTR    CalledParty;  ///< The receiver
        BSTR    Text;         ///< The SMS Text
      } T_SMS;
    
    
      typedef 
      [ 
        uuid(377908B4-979A-4885-8976-04B1D9888D99), 
        version(1.0),
        helpstring("An GPS record")
      ]
      struct T_GPSParamsMap
      {
        BSTR  Key;
        BSTR  Value;    
      }T_GPSParamsMap;
    
    
      typedef 
      [ 
        uuid(43982CA5-86C5-47f4-97B6-8FEF433D36B1), 
        version(1.0),
        helpstring("Defines the information for a specific client.")
      ]
      struct T_FwClient
      {
        BSTR                      Alias;
        SMSFilterActionEnum       Action;   ///< Action Specific to SMS
        BSTR                      Text;
        int                       ToCall;   ///< Call type into which the SMS will be transformed (optional)
        int                       ToState;  ///< State number into which the SMS will be transformed (optional)
        SAFEARRAY(T_GPSParamsMap) GpsData;  ///< Gps data into which the SMS will be transformed (optional)
      } T_FwClient;
    
    
      typedef 
      [ 
        uuid(4162E179-7E99-4783-95D9-DA9A0B3BE568), 
        version(1.0),
        helpstring("Defines the result of an SMS filter.")
      ]
      struct T_SMSAction
      {
        int                       ActionID;     ///< Id of the common action to take (TBL_ACTION)
        SMSFilterActionEnum       Action;       ///< Action Specific to SMS
        BSTR                      Text;         ///< Text transormed by the filter
        BSTR                      Folder;       ///< Folder on which the SMS will be moved/copied (optional)
        BSTR                      DestAddress;  ///< Destination address to which the SMS will be forwarded (optional)
        int                       ToCall;       ///< Call type into which the SMS will be transformed (optional)
        int                       ToState;      ///< State number into which the SMS will be transformed (optional)
        SAFEARRAY(T_GPSParamsMap) GpsData;      ///< Gps data into which the SMS will be transformed (optional)
    
        VARIANT_BOOL          forwardToNotListed;
        SAFEARRAY(T_FwClient) FwClients;
    
      } T_SMSAction;
    
    //-----------------------------------------------------------------------
    
    [
    	object,
    	uuid(F7942BCA-5122-46BB-94DB-89F5071842E4),
    	dual,
      oleautomation,
    	nonextensible,
    	helpstring("ISMSFilter Interface"),
    	pointer_default(unique)
    ]
    interface ISMSFilterWrapper : IDispatch{
      [id(1), helpstring("method GetFilterResult")] 
      HRESULT Init([in] BSTR schema_file_path, [out, retval] long* pVal);
      
      [id(2), helpstring("method GetFilterResult")] 
      HRESULT ApplyFilter([in] T_SMS* sms, [out, retval] long* pVal);
      
      [id(3), helpstring("method GetFilterResult")] 
      HRESULT GetFilterResult([in, out] T_SMSAction* ret_val, [out, retval] long* pVal);
    };
    
    //-----------------------------------------------------------------------
    
    
    [
    	uuid(F11FE49D-E131-4675-8DED-700075CDCC91),
    	version(1.0),
    	helpstring("Eurocom SMSFilter library")
    ]
    library SMSFilterLib
    {
    	importlib("stdole2.tlb");
    
    
    	[
    		uuid(FB10BD2B-396C-45DE-A944-7EC50E4839A7),
    		helpstring("_ISMSFilterEvents Interface")
    	]
    	dispinterface _ISMSFilterEvents
    	{
    		properties:
    		methods:
          [id(1), helpstring("method  OnRuleParsed")] 
          HRESULT OnRuleParsed([in] struct T_SMSAction * sms); //, [in, out] VARIANT_BOOL* stop);
    	};
    
    
    	[
    		uuid(BC330176-4EB4-4574-A73F-52610CEF1381),
    		helpstring("SMSFilter Class")
    	]
    	coclass SMSFilter
    	{
    		[default] interface ISMSFilterWrapper;
    		[default, source] dispinterface _ISMSFilterEvents;
    	};
    };
    

    I import it into a managed project and more or less all works but, when I run the following code:

    SMSFilterLib.T_SMS smsFilter = new SMSFilterLib.T_SMS();
    SMSFilterLib.T_SMSAction smsRule = new SMSFilterLib.T_SMSAction();
    
    smsFilter.CalledParty = Convert.ToString(RadioID);
    smsFilter.CallingParty = "1";
    smsFilter.Text = Text;
    
    try
    {
      if (m_smsFilter.ApplyFilter(ref smsFilter) == 0)
      {
        int RV = m_smsFilter.GetFilterResult(ref smsRule);
        
        if (RV == 0 m_smsFilterRules.Add(smsRule);
    
        foreach (SMSFilterLib.T_SMSAction rule in m_smsFilterRules)
        {
          //......
        }
      } 
    }

    I get an exception (translated):

    "Unable to marshal the GpsData filed of type 'SMSFilterLib.T_SMSAction': No support of marshalling available for this type.

    How can I marshall a SAFEARRAY(UDT)?

    Thanks in advance,
    Daniele.

    Friday, May 11, 2012 3:29 PM

Answers

  • Hello Daniele,

    1. No problem with m_smsFilterRules as long as T_SMSAction has been defined properly in C#.

    2. I wrote 2 test C# programs. In the first test program, T_SMSAction is imported and defined as :

        [Guid("4162E179-7E99-4783-95D9-DA9A0B3BE568")]
        public struct T_SMSAction
        {
            public SMSFilterActionEnum Action;
            public int ActionID;
            public string DestAddress;
            public string Folder;
            public short forwardToNotListed;
            public Array FwClients;
            public Array GpsData;
            public string Text;
            public int ToCall;
            public int ToState;
        }

    This definition is created by Visual Studio (via TLBIMP.EXE) when the type library for SMSFilter was referenced. The FwClients and GpsData members are defined as generic Array types by default. This is so because of the use of the /sysarray flag. This flag is used by the Visual Studio whenever it imports any SAFEARRAYs from a type library. Hence SAFEARRAYs are defined as generic Array types.

    3. In the other C# program, the same struct is defined as :

        [Guid("4162E179-7E99-4783-95D9-DA9A0B3BE568")]
        public struct T_SMSAction
        {
            public SMSFilterActionEnum Action;
            public int ActionID;
            public string DestAddress;
            public string Folder;
            public short forwardToNotListed;
            public T_FwClient[] FwClients;
            public T_GPSParamsMap[] GpsData;
            public string Text;
            public int ToCall;
            public int ToState;
        }

    The FwClients and GpsData are defined as more specialized arrays. This definition is a result of referencing an interop assembly that I created using TLBIMP.EXE. The reason why specialized typed arrays are defined is that when I used TLBIMP.EXE, I ommited the use of the /sysarray flag.

    4. Please show us how the T_FwClient, T_GPSParamsMap, T_SMS, T_SMSAction structures are defined in your C# code.

    5. >> About memory leaks, are they related to the std::string <-> BSTR convertion?

    No, the conversion is fine. the problem is with the way SafeArrayPutElement() works. Let's get to this later.

    - Bio.


    Please visit my blog : http://limbioliong.wordpress.com/

    • Marked as answer by DBarzo Tuesday, May 15, 2012 12:06 PM
    Tuesday, May 15, 2012 10:59 AM
  • Hello Daniele,

    1. >> Now I change it to point to the correct interop.SMSFilter and....it seems to work!

    1.1 Congratulations.

    2. >>  Instead for the memory leak, where is the problem?

    2.1 In your CreateGPSParamArray() helper function :

    static SAFEARRAY* CreateGPSParamArray(ISMSFilter::map_GPSParams& gps)
    {
      long            ix[1];
      int             i = 0;
      T_GPSParamsMap  gps_map;
      SAFEARRAY*      sa;
      sa = CreateSafeArray( gps.size(), IID_T_GPSParamsMap );
      if (!sa) return 0;
      for(ISMSFilter::map_GPSParams::iterator it = gps.begin();
          it != gps.end();
          it++)
      {
        gps_map.Key   = _bstr_t( (*it).first.c_str() ).Detach();;
        gps_map.Value = _bstr_t( (*it).second.c_str() ).Detach();;
        ix[0] = i++;
        ::SafeArrayPutElement(sa, ix, &gps_map);
      }
      return sa;
    }

    2.2 The "gps_map" structure (type T_GPSParamsMap) is re-used in the "for" loop where its "Key" and "Value" fields are assigned BSTR values.

    2.3 No problem with the BSTR assignments but when SafeArrayPutElement() is called, a complete copy of the "gps_map" structure is created and inserted into the "sa" SAFEARRAY.

    2.4 The structure copy will contain "Key" and "Value" BSTR fields which are also copies of the original ones from "gps_map". Note that by "copy" we mean a brand new BSTR allocated somewhere in memory with the exact same character values as the original.

    2.5 This is because the SAFEARRAY uses copy semantics. This is necessary so that the SAFEARRAY owns the memory of each of the elements of its internal array. Because of this memory ownership, it is free to destroy its elements in a call to SafeArrayDestroy().

    2.6 Hence after every iteration of the "for" loop, the memory for the current "gps_map.Key" and "gps_map.Value" BSTRs will be leaked.

    3. The same thing happens in the "for" loop of the ConvertTSMSAction() function :

      long        ix[1];
      int         i = 0;
      T_FwClient  cl;
      for(std::list<ISMSFilter::TFwClient>::iterator it = sms_act->FwClients.begin();
          it != sms_act->FwClients.end();
          it++)
      {
        cl.Action   = (SMSFilterActionEnum)((*it).Action);
        cl.Alias    = _bstr_t( (*it).Alias.c_str() ).Detach();
        cl.Text     = _bstr_t( (*it).Text.c_str() ).Detach();
        cl.ToCall   = (*it).ToCall;
        cl.ToState  = (*it).ToState;
        cl.GpsData  = CreateGPSParamArray((*it).GpsData);
        ix[0] = i++;
        ::SafeArrayPutElement(ret_val->FwClients, ix, &cl);
      }

    3.1 This time the "cl.Alias" and "cl.Text" BSTRs are leaked upon every iteration of the "for" loop.

    3.3 Each "cl" structure "inserted" into the "ret_val->FwClients" SAFEARRAY is actually a complete copy of "cl" including new BSTRs for its "Alias" and "Text" fields.

    4. Resolution.

    4.1 To resolve the memory leakage issue, call ::SysFreeString() just before the end of every "for" iteration, e.g. for the CreateGPSParamArray() function :

    static SAFEARRAY* CreateGPSParamArray(ISMSFilter::map_GPSParams& gps)
    {
      long            ix[1];
      int             i = 0;
      T_GPSParamsMap  gps_map;
      SAFEARRAY*      sa;
      sa = CreateSafeArray( gps.size(), IID_T_GPSParamsMap );
      if (!sa) return 0;
      for(ISMSFilter::map_GPSParams::iterator it = gps.begin();
          it != gps.end();
          it++)
      {
        gps_map.Key   = _bstr_t( (*it).first.c_str() ).Detach();;
        gps_map.Value = _bstr_t( (*it).second.c_str() ).Detach();;
        ix[0] = i++;
        ::SafeArrayPutElement(sa, ix, &gps_map);
        // Free the BSTRs.
        ::SysFreeString(gps_map.Key);
        ::SysFreeString(gps_map.Value);
      }
      return sa;
    }

    4.2 And for the ConvertTSMSAction() function, clear the "cl.Alias" and "cl.Text" BSTRs ate the end of every "for" iteration :

      long        ix[1];
      int         i = 0;
      T_FwClient  cl;
      for(std::list<ISMSFilter::TFwClient>::iterator it = sms_act->FwClients.begin();
          it != sms_act->FwClients.end();
          it++)
      {
        cl.Action   = (SMSFilterActionEnum)((*it).Action);
        cl.Alias    = _bstr_t( (*it).Alias.c_str() ).Detach();
        cl.Text     = _bstr_t( (*it).Text.c_str() ).Detach();
        cl.ToCall   = (*it).ToCall;
        cl.ToState  = (*it).ToState;
        cl.GpsData  = CreateGPSParamArray((*it).GpsData);
        ix[0] = i++;
        ::SafeArrayPutElement(ret_val->FwClients, ix, &cl);
        // Free the BSTRs.
        ::SysFreeString(cl.Alias);
        ::SysFreeString(cl.Text);
      }

    - Bio.


    Please visit my blog : http://limbioliong.wordpress.com/

    • Marked as answer by DBarzo Tuesday, May 15, 2012 2:30 PM
    Tuesday, May 15, 2012 2:25 PM

All replies

  • Hi Daniele,

    Did you been through this:

    http://limbioliong.wordpress.com/2011/07/17/marshaling-a-safearray-of-managed-structures-by-com-interop-part-3/


    Regards, http://shwetamannjain.blogspot.com

    Sunday, May 13, 2012 5:37 AM
  • Hi Shweta,

    thanks a lot for your reply...
    I'm reading the interesting articles but I cannot understand how to figure it out in my case.
    I have to modify my C++ COM code or I can still use it as is and apply some MarshalAs attribute on the managed side?
    And in this case, how can I import the IDL?

    Can you give me some little suggestion?

    thanks in advance,
    Daniele.

    Monday, May 14, 2012 10:02 AM
  • hi Daniele,

    Please see his example:

    In C, code is:

    short WINAPI DoInsert (SAFEARRAY **psa)

    In C#, it will be:

    [DllImport("APApply.dll",EntryPoint="DoInsert")]
    private static extern short DoInsert{
    [MarshalAs(UnmanagedType.SafeArray,SafeArraySubType=VarEnum.VT_USERDEFINED)]
        ref WID[] recordArray};
    Hope it will help :)

    Regards, http://shwetamannjain.blogspot.com


    Monday, May 14, 2012 10:11 AM
  • Hi Shweta,

    I understand the examples, but I think my case is a little bit different.
    I need to return a struct which contains SAFEARRAYs of UDT.

    struct T_GPSParamsMap
    {
      BSTR  Key;
      BSTR  Value;    
    }T_GPSParamsMap;
      
    
    struct T_FwClient
    {
      BSTR                      Alias;
      SMSFilterActionEnum       Action;
      BSTR                      Text;
      int                       ToCall;
      int                       ToState;
      SAFEARRAY(T_GPSParamsMap) GpsData;
    } T_FwClient;
      
    
    struct T_SMSAction
    {
      int                       ActionID;
      SMSFilterActionEnum       Action;
      BSTR                      Text;
      BSTR                      Folder;
      BSTR                      DestAddress;
      int                       ToCall;
      int                       ToState;
      SAFEARRAY(T_GPSParamsMap) GpsData;
    
      VARIANT_BOOL          forwardToNotListed;
      SAFEARRAY(T_FwClient) FwClients;
    
    } T_SMSAction;
      
    
    HRESULT GetFilterResult([in, out] T_SMSAction* ret_val, [out, retval] long* pVal);

    So, tell me if I wrong...
    I think I have to simply change the managed declarations using MarshalAs attribute but, if this is the case, I don't know to do it...usually, to use a COM library from managed code, I simply add it to references or (at least) I made a interop assembly using the tlbimp.exe utility.

    Regards,
    Daniele.

    Monday, May 14, 2012 10:56 AM
  • Hello Daniele,

    1. >> I get an exception (translated):..."Unable to marshal the GpsData filed of type 'SMSFilterLib.T_SMSAction': No support of marshalling available for this type...How can I marshall a SAFEARRAY(UDT)?...

    1.1 In order to be able to marshal a SAFEARRAY of UDTs to managed code, you must :

    • Use the IRecordInfo interface pointer that is associated with the UDT.
    • Create the SAFEARRAY using the SafeArrayCreateEx() API.

    1.2 When a UDT like T_SMSAction is marshaled to managed code, the interop marshaler will require the individual IRecordInfo interface pointers associated with the T_GPSParamsMap and the T_FwClient UDTs in order to unmarshal them from their individual SAFEARRAYs.

    2. Did you use the SafeArrayCreateEx() API and include the IRecordInfo interface pointers of the UDTs into the SAFEARRAY ? At this point it would be helpful if you could show us your implementation of the GetFilterResult() method.

    - Bio.


    Please visit my blog : http://limbioliong.wordpress.com/

    Tuesday, May 15, 2012 4:59 AM
  • Hi Bio,

    thanks very much for your answer!

    You're right, follow my GetFilterResult() implementation (comments below):

    static  const IID IID_T_FwClient = { 0x43982CA5, 
                                          0x86C5,
                                          0x47f4, {
                                                   0x97,
                                                   0xB6,
                                                   0x8F,
                                                   0xEF,
                                                   0x43,
                                                   0x3D,
                                                   0x36,
                                                   0xB1
                                                  }
                                        };
    
    //377908B4-979A-4885-8976-04B1D9888D99
    static  const IID IID_T_GPSParamsMap = { 0x377908B4, 
                                              0x979A,
                                              0x4885, {
                                                       0x89,
                                                       0x76,
                                                       0x04,
                                                       0xB1,
                                                       0xD9,
                                                       0x88,
                                                       0x8D,
                                                       0x99
                                                      }
                                            };
                                            
                                            
                                            
    static SAFEARRAY* CreateSafeArray(UINT size, const IID uid)
    {
      SAFEARRAYBOUND rgsabound[1];
      rgsabound[0].lLbound = 0;
      rgsabound[0].cElements = size;
    
      IRecordInfo *pUdtRecordInfo = NULL;
      HRESULT hr = GetRecordInfoFromGuids( LIBID_SMSFilterLib,  
                                           1, 0, 
                                           0,
                                           uid,
                                           &pUdtRecordInfo );
    
      if( FAILED( hr ) ) return NULL;
    
      SAFEARRAY *sa = ::SafeArrayCreateEx(VT_RECORD, 1, rgsabound, pUdtRecordInfo);
      pUdtRecordInfo->Release();
    
      return sa;
    }
    
    
    static SAFEARRAY* CreateGPSParamArray(ISMSFilter::map_GPSParams& gps)
    {
      long            ix[1];
      int             i = 0;
      T_GPSParamsMap  gps_map;
      SAFEARRAY*      sa;
    
      sa = CreateSafeArray( gps.size(), IID_T_GPSParamsMap );
      if (!sa) return 0;
    
      for(ISMSFilter::map_GPSParams::iterator it = gps.begin();
          it != gps.end();
          it++)
      {
        gps_map.Key   = _bstr_t( (*it).first.c_str() ).Detach();;
        gps_map.Value = _bstr_t( (*it).second.c_str() ).Detach();;
    
        ix[0] = i++;
    
        ::SafeArrayPutElement(sa, ix, &gps_map);
      }
    
      return sa;
    }
    
    
    static void ConvertTSMSAction(ISMSFilter::TSMSAction* sms_act, T_SMSAction* ret_val)
    {
      ret_val->ActionID            = sms_act->ActionID;
      ret_val->Action              = (SMSFilterActionEnum)sms_act->Action;
      ret_val->Text                = _bstr_t( sms_act->Text.c_str() ).Detach();
      ret_val->Folder              = _bstr_t( sms_act->Folder.c_str() ).Detach();
      ret_val->DestAddress         = _bstr_t( sms_act->DestAddress.c_str() ).Detach();
      ret_val->ToCall              = sms_act->ToCall;
      ret_val->ToState             = sms_act->ToState;
      ret_val->forwardToNotListed  = sms_act->forwardToNotListed;
      
      ret_val->GpsData             = CreateGPSParamArray(sms_act->GpsData);
    
      ret_val->FwClients = CreateSafeArray( sms_act->FwClients.size(), IID_T_FwClient );
      if (!ret_val->FwClients) return;
    
      long        ix[1];
      int         i = 0;
      T_FwClient  cl;
    
      for(std::list<ISMSFilter::TFwClient>::iterator it = sms_act->FwClients.begin();
          it != sms_act->FwClients.end();
          it++)
      {
        cl.Action   = (SMSFilterActionEnum)((*it).Action);
        cl.Alias    = _bstr_t( (*it).Alias.c_str() ).Detach();
        cl.Text     = _bstr_t( (*it).Text.c_str() ).Detach();
        cl.ToCall   = (*it).ToCall;
        cl.ToState  = (*it).ToState;
        cl.GpsData  = CreateGPSParamArray((*it).GpsData);
    
        ix[0] = i++;
    
        ::SafeArrayPutElement(ret_val->FwClients, ix, &cl);
      }
    
    }
    
    STDMETHODIMP CSMSFilter::GetFilterResult(T_SMSAction* ret_val, long* pVal)
    {
      ISMSFilter::TSMSAction sms_act;
      
      long rv = m_FilterPtr->GetFilterResult(&sms_act);
      if (pVal) *pVal = rv;
    
      if (rv == 0) ConvertTSMSAction(&sms_act, ret_val);
    
      return S_OK;
    }
    

    This is a wrapper around a plain C++ library. The GetFilterResult fetches a ISMSFilter::TSMSAction struct and then I convert it into an ATL struct through the ConvertSMSAction().

    As you can see into the CreateSafeArray() I use CreateSefaArrayEx, but maybe I wrong something?

    Thanks again,
    Daniele.

    Tuesday, May 15, 2012 6:51 AM
  • Hello Daniele,

    1. Your CreateSafeArray(), CreateGPSParamArray(), ConvertTSMSAction() helper functions seem OK albeit CreateGPSParamArray() and ConvertTSMSAction() will cause memory leaks.

    2. Do not worry about the memory leaks for now. We will get to that eventually.

    3. At the moment, please tell us what is "m_smsFilterRules". It has the method Add(). It looks like a collection class. But we really cannot tell what it is.

    - Bio.


    Please visit my blog : http://limbioliong.wordpress.com/

    Tuesday, May 15, 2012 10:01 AM
  • Hi Bio,

    thanks for your support...

    m_smsFilterRules is a List<T> declared as : 

    private List<T_SMSAction> m_smsFilterRules = new List<T_SMSAction>();

    But it is used in another scenario (here it simply contain the action struct returned from the filter).

    About memory leaks, are they related to the std::string <-> BSTR convertion?

    Regards,
    Daniele.

    Tuesday, May 15, 2012 10:30 AM
  • Hello Daniele,

    1. No problem with m_smsFilterRules as long as T_SMSAction has been defined properly in C#.

    2. I wrote 2 test C# programs. In the first test program, T_SMSAction is imported and defined as :

        [Guid("4162E179-7E99-4783-95D9-DA9A0B3BE568")]
        public struct T_SMSAction
        {
            public SMSFilterActionEnum Action;
            public int ActionID;
            public string DestAddress;
            public string Folder;
            public short forwardToNotListed;
            public Array FwClients;
            public Array GpsData;
            public string Text;
            public int ToCall;
            public int ToState;
        }

    This definition is created by Visual Studio (via TLBIMP.EXE) when the type library for SMSFilter was referenced. The FwClients and GpsData members are defined as generic Array types by default. This is so because of the use of the /sysarray flag. This flag is used by the Visual Studio whenever it imports any SAFEARRAYs from a type library. Hence SAFEARRAYs are defined as generic Array types.

    3. In the other C# program, the same struct is defined as :

        [Guid("4162E179-7E99-4783-95D9-DA9A0B3BE568")]
        public struct T_SMSAction
        {
            public SMSFilterActionEnum Action;
            public int ActionID;
            public string DestAddress;
            public string Folder;
            public short forwardToNotListed;
            public T_FwClient[] FwClients;
            public T_GPSParamsMap[] GpsData;
            public string Text;
            public int ToCall;
            public int ToState;
        }

    The FwClients and GpsData are defined as more specialized arrays. This definition is a result of referencing an interop assembly that I created using TLBIMP.EXE. The reason why specialized typed arrays are defined is that when I used TLBIMP.EXE, I ommited the use of the /sysarray flag.

    4. Please show us how the T_FwClient, T_GPSParamsMap, T_SMS, T_SMSAction structures are defined in your C# code.

    5. >> About memory leaks, are they related to the std::string <-> BSTR convertion?

    No, the conversion is fine. the problem is with the way SafeArrayPutElement() works. Let's get to this later.

    - Bio.


    Please visit my blog : http://limbioliong.wordpress.com/

    • Marked as answer by DBarzo Tuesday, May 15, 2012 12:06 PM
    Tuesday, May 15, 2012 10:59 AM
  • Hi Bio,

    you're right!!

    In my first tests I added the COM reference with the Visual Studio "Add Reference Windows"  that, like you say, uses the /sysarray param to tlbimp.

    Then I also created the interop.SMSFilter assembly by hand using the TBLIMP.exe without the switch but the reference in my project was not changed (maybe by a my mistake)!

    Now I change it to point to the correct interop.SMSFilter and....it seems to work!

    PS. Instead for the memory leak, where is the problem?

    -Daniele.

    Tuesday, May 15, 2012 12:06 PM
  • Hello Daniele,

    1. >> Now I change it to point to the correct interop.SMSFilter and....it seems to work!

    1.1 Congratulations.

    2. >>  Instead for the memory leak, where is the problem?

    2.1 In your CreateGPSParamArray() helper function :

    static SAFEARRAY* CreateGPSParamArray(ISMSFilter::map_GPSParams& gps)
    {
      long            ix[1];
      int             i = 0;
      T_GPSParamsMap  gps_map;
      SAFEARRAY*      sa;
      sa = CreateSafeArray( gps.size(), IID_T_GPSParamsMap );
      if (!sa) return 0;
      for(ISMSFilter::map_GPSParams::iterator it = gps.begin();
          it != gps.end();
          it++)
      {
        gps_map.Key   = _bstr_t( (*it).first.c_str() ).Detach();;
        gps_map.Value = _bstr_t( (*it).second.c_str() ).Detach();;
        ix[0] = i++;
        ::SafeArrayPutElement(sa, ix, &gps_map);
      }
      return sa;
    }

    2.2 The "gps_map" structure (type T_GPSParamsMap) is re-used in the "for" loop where its "Key" and "Value" fields are assigned BSTR values.

    2.3 No problem with the BSTR assignments but when SafeArrayPutElement() is called, a complete copy of the "gps_map" structure is created and inserted into the "sa" SAFEARRAY.

    2.4 The structure copy will contain "Key" and "Value" BSTR fields which are also copies of the original ones from "gps_map". Note that by "copy" we mean a brand new BSTR allocated somewhere in memory with the exact same character values as the original.

    2.5 This is because the SAFEARRAY uses copy semantics. This is necessary so that the SAFEARRAY owns the memory of each of the elements of its internal array. Because of this memory ownership, it is free to destroy its elements in a call to SafeArrayDestroy().

    2.6 Hence after every iteration of the "for" loop, the memory for the current "gps_map.Key" and "gps_map.Value" BSTRs will be leaked.

    3. The same thing happens in the "for" loop of the ConvertTSMSAction() function :

      long        ix[1];
      int         i = 0;
      T_FwClient  cl;
      for(std::list<ISMSFilter::TFwClient>::iterator it = sms_act->FwClients.begin();
          it != sms_act->FwClients.end();
          it++)
      {
        cl.Action   = (SMSFilterActionEnum)((*it).Action);
        cl.Alias    = _bstr_t( (*it).Alias.c_str() ).Detach();
        cl.Text     = _bstr_t( (*it).Text.c_str() ).Detach();
        cl.ToCall   = (*it).ToCall;
        cl.ToState  = (*it).ToState;
        cl.GpsData  = CreateGPSParamArray((*it).GpsData);
        ix[0] = i++;
        ::SafeArrayPutElement(ret_val->FwClients, ix, &cl);
      }

    3.1 This time the "cl.Alias" and "cl.Text" BSTRs are leaked upon every iteration of the "for" loop.

    3.3 Each "cl" structure "inserted" into the "ret_val->FwClients" SAFEARRAY is actually a complete copy of "cl" including new BSTRs for its "Alias" and "Text" fields.

    4. Resolution.

    4.1 To resolve the memory leakage issue, call ::SysFreeString() just before the end of every "for" iteration, e.g. for the CreateGPSParamArray() function :

    static SAFEARRAY* CreateGPSParamArray(ISMSFilter::map_GPSParams& gps)
    {
      long            ix[1];
      int             i = 0;
      T_GPSParamsMap  gps_map;
      SAFEARRAY*      sa;
      sa = CreateSafeArray( gps.size(), IID_T_GPSParamsMap );
      if (!sa) return 0;
      for(ISMSFilter::map_GPSParams::iterator it = gps.begin();
          it != gps.end();
          it++)
      {
        gps_map.Key   = _bstr_t( (*it).first.c_str() ).Detach();;
        gps_map.Value = _bstr_t( (*it).second.c_str() ).Detach();;
        ix[0] = i++;
        ::SafeArrayPutElement(sa, ix, &gps_map);
        // Free the BSTRs.
        ::SysFreeString(gps_map.Key);
        ::SysFreeString(gps_map.Value);
      }
      return sa;
    }

    4.2 And for the ConvertTSMSAction() function, clear the "cl.Alias" and "cl.Text" BSTRs ate the end of every "for" iteration :

      long        ix[1];
      int         i = 0;
      T_FwClient  cl;
      for(std::list<ISMSFilter::TFwClient>::iterator it = sms_act->FwClients.begin();
          it != sms_act->FwClients.end();
          it++)
      {
        cl.Action   = (SMSFilterActionEnum)((*it).Action);
        cl.Alias    = _bstr_t( (*it).Alias.c_str() ).Detach();
        cl.Text     = _bstr_t( (*it).Text.c_str() ).Detach();
        cl.ToCall   = (*it).ToCall;
        cl.ToState  = (*it).ToState;
        cl.GpsData  = CreateGPSParamArray((*it).GpsData);
        ix[0] = i++;
        ::SafeArrayPutElement(ret_val->FwClients, ix, &cl);
        // Free the BSTRs.
        ::SysFreeString(cl.Alias);
        ::SysFreeString(cl.Text);
      }

    - Bio.


    Please visit my blog : http://limbioliong.wordpress.com/

    • Marked as answer by DBarzo Tuesday, May 15, 2012 2:30 PM
    Tuesday, May 15, 2012 2:25 PM
  • Thanks very very very mutch Bio!!

    - Daniele.

    Tuesday, May 15, 2012 2:31 PM