1

I have a COM object that reads and writes data. I need to read and write using the COM object as fast as possible.

while(!stopLoop)
{
  DateTime t1;

  foreach(Item in Data)
  {
    ReadData(...);
    //...
  }

  DateTime t2;
  TimeSpan loopTime = t2 - t1;
}

I've wrote some code and ran it. At first I have a loopTime of 200ms but it got slower and slower, after 45 minutes it took around 3 seconds. The memory usage doubled in the same time.

I checked with VMMap and: over time only the non-managed heap memory increased. So I guess I have a memory leak in the COM object.

Okay, googled it and tried following:

System.Runtime.InteropServices.Marshal.ReleaseComObject(myObject);

After creating a new object and initialization I could read again:

antiLeakCounter = 0; //as proposed in the comments by Hans Passant

public object ReadData(...)
{
  object obj = new object();
  myComObj.Read(..., ref obj);

  if(++antiLeakCounter == 100)
  {
    //TryRemoveLeak();
    antiLeakCounter = 0;
  }

  return obj;
}

private void TryRemoveLeak()
{
  ReleaseComObject(myComObj);
  myComObj = new MyComClass();
  InitMyComObj();
}

With ReleaseObject I get a continous time again. I am not sure if the COM object works now. myComObj.Read(..., ref obj); normally returns the ref object that I can check with is bool/byte[]/int, but after ReleaseComObject and recreation the ref object only returns object so I cannot cast it now. So... there must be something wrong with my recreation.

How can I recreate my COM object properly? Or is there a different way to prevent these leaks?


Edit:

I tested with GC.Collect, AddMemoryPressure, etc. but can't get the loop time stable:

private void TryRemoveLeak()
{
  //instead of ReleaseComObject...
}

Test 1:

GC.Collect();

Test 2:

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();

Test 3:

GC.Collect(GC.GetGeneration(myComObj), GCCollectionMode.Optimized);

Test 4:

public object ReadData(...)
{
  object obj = new object();
  GC.AddMemoryPressure(byteSizeOfObj);
  myComObj.Read(..., ref obj);

  GC.RemoveMemoryPressure(byteSizeOfObj); //after returning, the obj is dead, right?
  return obj;
}

The COM object interacts with another application called PLCSIM.

MyApplication <-COM-> PLCSIM

I checked PLCSIM and can see a memory leak too. It's leaking nearly the same amount like my application in the same time.

Community
  • 1
  • 1
fedab
  • 978
  • 11
  • 38
  • It's going to be hard to tell what might be leaking from the code you have posted, what is calling / hosting the com object? Where is the data from? SQL? Files? Something else? How are you disposing? Essentially we need more information. – BenjaminPaul Oct 23 '14 at 16:10
  • @BenjaminPaul I use the S7ProSim COM object to read data from a PLC. Here you can find the [documentation](https://cache.automation.siemens.com/dnl/DE/DE2NTA2OQAA_1139855_HB/S7WSPSCB.pdf). I don't dispose anything. – fedab Oct 23 '14 at 16:58
  • Lots of questions about this. It is rarely a problem to get lots of unmanaged memory usage from a synthetic test program like this, it doesn't consume enough managed memory to get the garbage collector to run. ReleaseComObject() is a workaround that rarely works for non-trivial COM object models. FinalReleaseComObject() is too dangerous, causing very hard to diagnose exceptions. The safe way is to count objects you create and call GC.Collect() when it reaches, say, 1000. The best way is to watch out for artificial tests, keep on eye on the collect counters, visible in Perfmon.exe. – Hans Passant Oct 23 '14 at 16:59
  • @HansPassant I did a test for every loop (see edit). I guess this should work too but it will be slower, right? I added .NET CLR-memory indicators in Perfmon (hope that are the right indicators?), but no changes. I checked RAM and loop time: still increasing for all tested methods. – fedab Oct 24 '14 at 10:00
  • 1
    No, incrementing a counter and testing it takes a fraction of nanosecond. Pretty important to use that counter. And read [this post](http://stackoverflow.com/a/17131389/17034) about the dangers of doing this test with a debugger attached. – Hans Passant Oct 24 '14 at 10:23
  • @HansPassant I created a counter (edit). I also tried to switch to Release and uncheck suppress JIT optimization. No change in memory usage, still leaking. GC is collecting items of all generations every time GC is collecting. Maybe the unmanaged memory is not collected even when the created managed objects are collected? – fedab Oct 27 '14 at 09:13
  • Sounds to me it is about time to talk to the software vendor. Get in touch with their support group, send them a small program that exhibits this problem. – Hans Passant Oct 27 '14 at 09:16
  • @HansPassant Got a solution for my problem (see my answer) but this is not really an answer to the question. Your comments where helpful and answered my question. I am not sure what to do now... do you want to write an answer? – fedab Nov 03 '14 at 10:16

1 Answers1

0

My problem was the wrong usage of the COM object.

Instead of:

object obj = new object();
myComObj.Read(..., ref obj);

I had to use the right type:

object obj;

switch(...)
{
  case(...): obj = new int(); break;
  case(...): obj = new short(); break;
  case(...): obj = new byte(); break;
  ...
}
myComObj.Read(..., ref obj);

So in this case I didn't needed to recreate my COM object and didn't needed to make a ReleaseComObject or a GC.Collect().

fedab
  • 978
  • 11
  • 38