SDK - Avoiding Reference Count Errors caused by Developing TRIM COM Addins with .Net

  • KM507184
  • 20-Sep-2008
  • 20-Sep-2008

Archived Content: This information is no longer maintained and is provided "as is" for your convenience.

Reference

Avoiding Reference Count Errors caused by Developing TRIM COM Addins with .Net

Return to General Articles page

This article applies to the developers using Microsoft .Net to develop the following TRIM COM Addins.

TRIM add-ins developed using C# or VB.net can suffer from memory management issues if not developed with care. Parameters to the addin methods are marshaled across the COM/.Net boundary and can become orphans. The resources are not correctly released causing products integrated with TRIM to either spawn or stay in memory after shutdown, eventually causing system failure due to insufficent resources.

This is avoidable.

Basic TRIMRecordAddin

The following is how a TRIMRecordAddin should be implemented in order to avoid these Reference count issues. This add-in will compile and run without causing reference count issues. Even though there is no ‘work’ being done by this add-in, ReleaseComObject must be called for every entry point that has a TRIM object as a parameter.

  using TRIMSDK;
  using System.Runtime.InteropServices;
  ...
  ...
  public class MyAddIn : TRIMRecordAddIn
  {
      public string ErrorMessage
      {
          get
          {
              string TRIMRecordAddIn_ErrorMessage = "";
              return TRIMRecordAddIn_ErrorMessage;
          }
      }

      public TRIMSDK.ppPluginPageType PropertyPageStyle
      {
          get
          {
              return 0;
          }
      }

      // AddPropertyPage is not functional and longer implemented for TRIM 6.x
      public bool AddPropertyPage(System.Int32 ParentSheetHWND, TRIMSDK.Record ForRecord)
      {
          Marshal.ReleaseComObject(ForRecord);
          return false; 
      }

      public string LinkCmdDescription(int LinkNumber)
      {
          // TRIM supports three different external links, indicated with a 
          // Link numbers 0,1 or 2.
          return "LinkCmdDescription: " + LinkNumber;
      }

      public string LinkCmdName(int LinkNumber)
      {
          return "LinkCmdName: " + LinkNumber;
      }

      public bool LinkExecute(int ParentHWND, int LinkNumber, TRIMSDK.Record ForRecord)
      {
          Marshal.ReleaseComObject(ForRecord);
          return true;
      }

      public void PostDelete(TRIMSDK.Record NewRecord)
      {
          Marshal.ReleaseComObject(NewRecord);
      }

      public void PostSave(TRIMSDK.Record NewRecord, System.Boolean RecordWasJustCreated)
      {
          Marshal.ReleaseComObject(NewRecord);
      }

      public bool PreDelete(TRIMSDK.Record NewRecord)
      {
          Marshal.ReleaseComObject(NewRecord); 
          return false;
      }

      public bool PreSave(TRIMSDK.Record NewRecord)
      {
          Marshal.ReleaseComObject(NewRecord);
          return true;
      }

      public void Setup(TRIMSDK.Record NewRecord)
      {
          Marshal.ReleaseComObject(NewRecord);
      }
  }

Release Local Variables

It is also important to call ReleaseComObject for COM variables declared within your code such as the following example.

  public void Setup(TRIMSDK.Record NewRecord)
  {
          //See the link below on Avoiding Marshalling Errors
          Database db = NewRecord.Database;
          Location loc = db.CurrentUser;

          String s = "No Name";

          if (loc != null)
          {
              s = loc.SortName; 
          }                                    
          NewRecord.Title = "Title " + s;

          // ALL COM objects must be released to avoid Reference count errors. 
          // Omitting this RCO(loc) also causes a Reference count error.
          if (MessageBox.Show("Yes or No?", "Would you like to avoid a Reference count error?", MessageBoxButtons.YesNo) == DialogResult.Yes)
          {
              Marshal.ReleaseComObject(loc);
              Marshal.ReleaseComObject(db);
          }
          Marshal.ReleaseComObject(NewRecord);
  }

Do Not Query properties of Objects which are themselves Objects

Avoid writing code where you query the property of an object which is also the property of another object. In the case below CurrentUser is a TRIMSDK.Location object. It is a property of the TRIMSDK.Database object, which in turn is the property of the NewRecord Object. The Location object is three levels deep. There is no handle on the object property (NewRecord.Database or NewRecord.Database.CurrentUser [Type:Location]) and it is therefore not possible to release the handle to either of these objects. Do not do this.

  public void Setup(TRIMSDK.Record NewRecord)
  {
      // DO NOT DO THIS
      String s = NewRecord.Database.CurrentUser.SortName;
      // DO NOT DO THIS
      
      NewRecord.Title = "Title " + s;
      
      //This does NOT work!!
      Marshal.ReleaseComObject(NewRecord.Database);
      Marshal.ReleaseComObject(NewRecord.Database.CurrentUser);
      //
      
      Marshal.ReleaseComObject(NewRecord);
  }

References