Author Topic: Limit on creating ResultBuffers containing Selection Sets?  (Read 3592 times)

0 Members and 1 Guest are viewing this topic.

zoltan

  • Guest
Limit on creating ResultBuffers containing Selection Sets?
« on: December 07, 2012, 12:00:20 PM »
Run the Test1 command, select some objects and then keep pressing enter until AutoCAD crashes or until you get bored.  Then, run the Test2 command and do it again.

After trying to create 128 ResultBuffer objects, the Test2 command crashes but the Test1 command does not (or at least, for me, that is).


In AutoCAD 2013, I get a Autodesk.AutoCAD.Runtime.Exeption with ErrorStatus of InvalidInput at the SelectioSet GetAdsName() method that has been called by the ResultBuffer TypedValueToResbuf(TypedValue value) method.

Does anybody seem to know what is going on here?

Code - C#: [Select]
  1. [CommandMethod("Test1")]
  2.         public static void Test1()
  3.         {
  4.             Editor acadEditor = Application.DocumentManager.MdiActiveDocument.Editor;
  5.  
  6.             PromptSelectionResult result = acadEditor.GetSelection();
  7.  
  8.             if (result.Status == PromptStatus.OK)
  9.             {
  10.                 SelectionSet selectionSet = result.Value;
  11.  
  12.                 int cnt = 1;
  13.  
  14.                 do
  15.                 {
  16.                     using (ResultBuffer resultBuffer = new ResultBuffer())
  17.                     {
  18.                         TypedValue typedValue = new TypedValue((int)LispDataType.SelectionSet, selectionSet);
  19.  
  20.                         resultBuffer.Add(typedValue);
  21.  
  22.                         acadEditor.WriteMessage(string.Format("\n{0}", cnt++));
  23.                     }
  24.                 }
  25.                 while (acadEditor.GetString("\nPress Enter").Status == PromptStatus.OK);
  26.             }
  27.         }
  28.  
  29.         [CommandMethod("Test2")]
  30.         public static void Test2()
  31.         {
  32.             Editor acadEditor = Application.DocumentManager.MdiActiveDocument.Editor;
  33.  
  34.             PromptSelectionResult result = acadEditor.GetSelection();
  35.  
  36.             if (result.Status == PromptStatus.OK)
  37.             {
  38.                 SelectionSet selectionSet = result.Value;
  39.  
  40.                 ObjectId[] objectIds = new ObjectId[selectionSet.Count];
  41.  
  42.                 for (int i = 0; i < selectionSet.Count; i++)
  43.                 {
  44.                     objectIds[i] = selectionSet[i].ObjectId;
  45.                 }
  46.  
  47.                 SelectionSet newSelectionSet = SelectionSet.FromObjectIds(objectIds);
  48.  
  49.                 int cnt = 1;
  50.  
  51.                 do
  52.                 {
  53.                     using (ResultBuffer resultBuffer = new ResultBuffer())
  54.                     {
  55.                         TypedValue typedValue = new TypedValue((int)LispDataType.SelectionSet, newSelectionSet);
  56.  
  57.                         resultBuffer.Add(typedValue);
  58.  
  59.                         acadEditor.WriteMessage(string.Format("\n{0}", cnt++));
  60.                     }
  61.                 }
  62.                 while (acadEditor.GetString("\nPress Enter").Status == PromptStatus.OK);
  63.             }
  64.         }
  65.  



Jeff H

  • Needs a day job
  • Posts: 6144
Re: Limit on creating ResultBuffers containing Selection Sets?
« Reply #1 on: December 07, 2012, 12:20:03 PM »
...
Quote from: docs

 
An ARX application cannot have more than 128 selection sets open at once. (The limit may be different on your system.) If the limit is reached, AutoCAD refuses to create more selection sets. Simultaneously managing a large number of selection sets is not recommended. Instead, keep a reasonable number of sets open at any given time, and call acedSSFree() to free unused selection sets as soon as possible. Unlike AutoLISP, the ARX environment has no automatic garbage collection to free selection sets after they have been used. An application should always free its open selection sets when it receives an kUnloadDwgMsg, kEndMsg, or kQuitMsg message.

zoltan

  • Guest
Re: Limit on creating ResultBuffers containing Selection Sets?
« Reply #2 on: December 07, 2012, 01:27:37 PM »
That seems to make sense, Jeff, but it does not account for why Test1 works.  It would seem as thought Test1 is only keeping one SelectionSet in memory and Test2 is keeping two in memory.  Aside from how the SelectionSet being used in the loop is created, the code inside the loop is the same.

So why does Test1 work?

Jeff H

  • Needs a day job
  • Posts: 6144
Re: Limit on creating ResultBuffers containing Selection Sets?
« Reply #3 on: December 07, 2012, 02:19:32 PM »
Sorry that really does not help but I will have to look later today when I have a little more time but I think has to do with using FromObjectIds which uses SelectionSetDelayMarshalled class to return the new SelectionSet.

Jeff H

  • Needs a day job
  • Posts: 6144
Re: Limit on creating ResultBuffers containing Selection Sets?
« Reply #4 on: December 07, 2012, 06:14:52 PM »
Okay I think I see now,
If you want a complete step through of the process I will post it if wanted but a quick overview.
 
In test one the selectionset you are using have a valid ename and sname
In the second test you create the SelectionSet with SelectionSet.FromObjectIds its ename and sname are null.
 
A call to acedSSadd is called when adding to the result buffer so the first test sees that it has a ename and sname and the entity is already included in the set so it ignores it.
 
The second test for each call to acedSSadd the ename and sname are null so it creates a new selection set.
 
 

TheMaster

  • Guest
Re: Limit on creating ResultBuffers containing Selection Sets?
« Reply #5 on: December 08, 2012, 03:36:28 PM »
Run the Test1 command, select some objects and then keep pressing enter until AutoCAD crashes or until you get bored.  Then, run the Test2 command and do it again.

After trying to create 128 ResultBuffer objects, the Test2 command crashes but the Test1 command does not (or at least, for me, that is).


In AutoCAD 2013, I get a Autodesk.AutoCAD.Runtime.Exeption with ErrorStatus of InvalidInput at the SelectioSet GetAdsName() method that has been called by the ResultBuffer TypedValueToResbuf(TypedValue value) method.

Does anybody seem to know what is going on here?


Yes. You're calling GetAdsName(), which creates a native selection set that must be freed by calling acedSSFree().

Use reflector to search for acedSSFree(), and then use Analyze to see what code is calling it. Without calling acedSSFree() on a native selection set, you will hit the wall at 128.

Here's the implementation of the Editor's RunCommand() method that illustrates it.

Code - C#: [Select]
  1. internal unsafe PromptStatus RunCommand(params object[] parameter)
  2. {
  3.    int length;
  4.    if (parameter != null)
  5.    {
  6.       length = parameter.Length;
  7.    }
  8.    else
  9.    {
  10.       length = 0;
  11.    }
  12.    int num4 = length;
  13.    int num = 5100;
  14.    if (length == 0)
  15.    {
  16.       resbuf resbuf;
  17.       *((short*) (&resbuf + 8)) = 5000;
  18.       num = acedCmd((resbuf modopt(IsConst)*) &resbuf);
  19.    }
  20.    int index = 0;
  21.    while (true)
  22.    {
  23.       if ((index >= num4) || (num != 5100))
  24.       {
  25.          break;
  26.       }
  27.       object o = parameter[index];
  28.       if ((o != null) && (o.GetType() == typeof(ResultBuffer)))
  29.       {
  30.          num = acedCmd((resbuf modopt(IsConst)*) (o as ResultBuffer).UnmanagedObject.ToPointer());
  31.       }
  32.       else
  33.       {
  34.          resbuf* resbufPtr = acutNewRb(5000);
  35.          if (resbufPtr == null)
  36.          {
  37.             throw new OutOfMemoryException();
  38.          }
  39.          try
  40.          {
  41.             if (o != null)
  42.             {
  43.                IntPtr rb = new IntPtr((void*) resbufPtr);
  44.                Marshaler.ObjectToResbuf(o, rb);
  45.             }
  46.             num = acedCmd((resbuf modopt(IsConst)*) resbufPtr);
  47.          }
  48.          finally
  49.          {
  50.             if (resbufPtr[8L] == 5007)
  51.             {
  52.                acedSSFree((long modopt(IsConst)* modopt(IsConst) modopt(IsConst)) (resbufPtr + 16L));
  53.             }
  54.             acutRelRb(resbufPtr);
  55.          }
  56.       }
  57.       index++;
  58.    }
  59.    if (num != -5002)
  60.    {
  61.       return ((num != 5100) ? PromptStatus.Error : PromptStatus.OK);
  62.    }
  63.    return PromptStatus.Cancel;
  64. }
  65.  

In the 2013 API, there is a class called DisposableSelectionSet, which will call acedSSFree() from it's implementation of Finalize(), and you can use that to avoid having to P/Invoke acedSSFree():

Code - C#: [Select]
  1. public sealed class DisposableSelectionSet : IDisposable
  2. {
  3.    // Fields
  4.    private AdsName m_adsName;
  5.  
  6.    // Methods
  7.    public DisposableSelectionSet(SelectionSetDelayMarshalled selectionSet)
  8.    {
  9.       AdsName name = selectionSet.Name;
  10.       this.m_adsName = name;
  11.    }
  12.  
  13.    private unsafe void ~DisposableSelectionSet()
  14.    {
  15.       $ArrayType$$$BY01_J e$$$by_j;
  16.       *((long*) &e$$$by_j) = this.m_adsName.name1;
  17.       *((long*) (&e$$$by_j + 8)) = this.m_adsName.name2;
  18.       if ((*(((long*) &e$$$by_j)) != 0) || (*(((long*) (&e$$$by_j + 8))) != 0))
  19.       {
  20.          Interop.CheckAds(acedSSFree((long modopt(IsConst)* modopt(IsConst) modopt(IsConst)) &e$$$by_j));
  21.       }
  22.    }
  23.  
  24.    public sealed override void Dispose()
  25.    {
  26.       this.Dispose(true);
  27.       GC.SuppressFinalize(this);
  28.    }
  29.  
  30.    protected void Dispose([MarshalAs(UnmanagedType.U1)] bool flag1)
  31.    {
  32.       if (flag1)
  33.       {
  34.          this.~DisposableSelectionSet();
  35.       }
  36.       else
  37.       {
  38.          base.Finalize();
  39.       }
  40.    }
  41. }
  42.  
  43.  


zoltan

  • Guest
Re: Limit on creating ResultBuffers containing Selection Sets?
« Reply #6 on: December 09, 2012, 10:40:59 AM »
In the 2013 API, there is a class called DisposableSelectionSet, which will call acedSSFree() from it's implementation of Finalize(), and you can use that to avoid having to P/Invoke acedSSFree():

I see.. It is interesting that the docs don't make any mention of a DisposableSelectionSet...would have been nice to know!

TheMaster

  • Guest
Re: Limit on creating ResultBuffers containing Selection Sets?
« Reply #7 on: December 10, 2012, 07:26:04 PM »
In the 2013 API, there is a class called DisposableSelectionSet, which will call acedSSFree() from it's implementation of Finalize(), and you can use that to avoid having to P/Invoke acedSSFree():

I see.. It is interesting that the docs don't make any mention of a DisposableSelectionSet...would have been nice to know!

Who do you think they are, Microsoft ?   :laugh: