0

I'm a noob in C# and ArcObjects (started two weeks ago).

I'm trying to write a stand-alone console program that will change label placement quality in all data frames for all MXD files in a given folder. What I've got now kind of works, but after 40 or 50 MXDs that are changed, my program throws "out of memory exception". I suppose that standard garbage collection fails in this case. When I watch memory usage in task manager for this process, it fluctuates (so some memory is freed from time to time) but with a steady tendency upwards.

I have searched Google and SDK help as well for this topic. In general they say that all the COM objects created by oneself should be explicitly destroyed, as well as cursors and enumerators.

See the code below. The only time I use new explicitly is when creating MapDocument object and I immediately add it to ComReleaser - so, the way I understand it, it should be taken care of at the end of using block. I don't have any enumerators nor cursors, as I deal with layout only. What else should I release?

foreach (System.IO.FileInfo file in files)
 {
 counter++;
 if (!log.Exists(file.FullName))
 {
 using (ComReleaser cr = new ComReleaser())
 {
 bool changed = false;
 Console.WriteLine("({0}/{1}) Opening: " + file.FullName, counter, total);
 //Open mxd
 MapDocument mapDoc = new MapDocument();
 cr.ManageLifetime(mapDoc);
 mapDoc.Open(file.FullName);
 //Iterate data frames
 for (int i = 0; i < mapDoc.MapCount; i++)
 {
 Map map = (Map)mapDoc.Map[i];
 Console.WriteLine("Dataframe {0}", map.Name);
 IAnnotateMap2 annoMap = (IAnnotateMap2)map.AnnotationEngine;
 IMapOverposter overposter = (IMapOverposter)map;
 //check if Maplex labeling engine
 if (overposter.OverposterProperties is IMaplexOverposterProperties)
 {
 IMaplexOverposterProperties overposterProps = (IMaplexOverposterProperties)overposter.OverposterProperties;
 Console.WriteLine("Current placement quality: {0}", overposterProps.PlacementQuality.ToString());
 //change placement quality if necessary
 if (overposterProps.PlacementQuality != quality)
 {
 Console.WriteLine("Changing...");
 overposterProps.PlacementQuality = quality;
 changed = true;
 }
 else
 {
 Console.WriteLine("No need to change.");
 }
 Console.WriteLine("Current placement quality: {0}", overposterProps.PlacementQuality.ToString());
 }
 else
 {
 Console.WriteLine("Dataframe does not use Maplex.");
 }
 }
 //Save document if needed
 if (changed)
 {
 mapDoc.Save();
 Console.WriteLine("Saving...");
 }
 //close mxd
 Console.WriteLine("Closing...");
 mapDoc.Close();
 log.Add(file.FullName);
 }//ComReleaser
 } else
 {
 Console.WriteLine("({0}/{1}) Skipping: " + file.FullName, counter, total);
 }
 }
Kadir Şahbaz
78.6k57 gold badges260 silver badges407 bronze badges
asked Jan 3, 2020 at 9:16
2
  • I just noticed in passing Map map = (Map)mapDoc.Map[i]; is redundant and incorrect, you want the interface not the class. IMapOverposter overposter = (IMapOverposter)mapDoc.Map[i]; is the correct way to get the IMapOverposter. Out of memory doesn't mean you've run out of memory it means the garbage collector has cleaned up an object you are trying to use, to fix call GC.Collect() when you're opening/closing a new map object to dispel the ghost of the previous handle. Did you use the ArcObjects template to create your code? If not I would strongly advise doing so to get the foundation right. Commented Jan 14, 2020 at 2:16
  • Have a read of community.esri.com/thread/66863 where Esri indicates that you shouldn't be using ComReleaser but dispelling properly with Marshal.ReleaseComObject (I think it's System.Runtime.Marshal.ReleaseComObject) - the GC has a hard time ageing Esri interfaces so as memory pressure increases the GC will clean up objects that it thinks aren't in use, all old and current Esri objects seem to have the same age and are cleaned up indiscriminately eventually releasing the one you're currently using... this is a distillation of an enormous number of facepalm moments. Commented Jan 14, 2020 at 2:22

1 Answer 1

2

Nothing specific is jumping out to me, so I'm going to list a few solutions I find can help when encountering odd ArcObjects errors:

  • Are you running your code in a Single Threaded Apartment (STA) thread? If not try creating a new Thread and settings the apartment state to STA and executing your code on that thread.
  • Set 'Embed Interop Types' for all Esri references in Visual Studio to false
  • Explicitly call GC.Collect() followed by GC.WaitForPendingFinalizers() after mapDoc.Close().
  • Explicitly set the program to an x86 architecture in Visual Studio instead of the default which is AnyCPU.

A couple additional notes:

  • For ComReleaser, unless it's a cursor or otherwise called out in the documentation, you don't need it.
  • You should cast your Map as an IActiveView and call IActiveView.Activate() before doing anything else with the map.
  • Check the output window in visual studio for other errors and messages that may point to a specific root cause.
answered Jan 3, 2020 at 22:02

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.