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);
        }
    }

  • 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. – Michael Stimson Jan 14 '20 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. – Michael Stimson Jan 14 '20 at 2:22
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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.