4

I've tried to write a simple mail sending via MS Outlook (2010) to allow content preview by user and I got memory leak 1x TServerEventDispatch if user has cancelled sending. Using answer from SO attaching event on _MAILITEM leaks on both user actions: sending and/or cancelling email.

Stack trace from FastMM in full debug mode is (almost the same for both cases):

4068DE [IB_Intf.pas][System][@GetMem$qqri][2097]
4090C3 [System][TObject.NewInstance$qqrv]
409826 [System][@ClassCreate$qqrpvzc]
633060 [Vcl.OleServer][Oleserver.TServerEventDispatch.$bctr$qqrp24Vcl.Oleserver.TOleServer]
6333D2 [Vcl.OleServer][Oleserver.TOleServer.$bctr$qqrp25System.Classes.TComponent]
B026D7 [Outlook2000][TOutlookApplication.$bctr$qqrp25System.Classes.TComponent]
B066F0 [DU_OutlookLeakTests.pas][DU_OutlookLeakTests][TestOutlookMemoryLeak.Item_OnSendMemoryLeakTest$qqrv][44]
622140 [TestFramework.pas][TestFramework][TTestCase.Invoke$qqrynpqqrv$v][2780]
622367 [TestFramework.pas][TestFramework][TTestCase.RunTest$qqrp25Testframework.TTestResult][2810]
61D0EA [TestFramework.pas][TestFramework][TTestResult.RunTestRun$qqr47System.%DelphiInterface$t19Testframework.ITest%][1473]
40F0AC [System][TInterfacedObject.QueryInterface$qqsrx5_GUIDpv]

Here is DUnit test demonstating my and Sertac's solution:

unit DU_OutlookLeakTests;

{$TYPEINFO ON} // Introspection is used

interface

uses
  TestFramework, Classes, Windows, SysUtils, Outlook2000;

type
  TestOutlookMemoryLeak = class(TTestCase)
  strict private
    FSendCnt: integer;
  strict private
    procedure HandleSend(Sender: TObject; var Cancel: WordBool);
    procedure HandleItemSend(Sender: TObject; const Item: IDispatch;
      var Cancel: WordBool);
  published
    procedure App_OnItemSendMemoryLeakTest;
    procedure Item_OnSendMemoryLeakTest;
  end;

implementation

/// <summary>
/// My original attempt to detect whether email has been sent or cancelled.
/// Leaks TServerEventDispatch if user cancels sending.
/// </summary>
procedure TestOutlookMemoryLeak.App_OnItemSendMemoryLeakTest;
var
  Outlook: TOutlookApplication;
  Mail: variant;
begin
  Outlook := TOutlookApplication.Create(nil);
  try
    Outlook.OnItemSend := HandleItemSend;
    Mail := Outlook.CreateItem(olMailItem);
    try
      Mail.Subject := 'A Subject';
      Mail.Recipients.Add('petr.fejfar@xxx.yy');
      Mail.Body := 'A Body';
      FSendCnt := 0;
      Mail.Display(True);
      CheckTrue(FSendCnt > 0, 'Mail has been cancelled by user');
    finally
      VarClear(Mail);
    end;
  finally
    Outlook.Free;
  end;
end;

/// <summary>
/// Another attempt following Sertac Akyuz's answer here:
/// https://stackoverflow.com/questions/5507342/outlook-object-model-detecting-if-email-has-been-sent
/// Leaks TServerEventDispatch if user sends and/or cancels email.
/// </summary>
procedure TestOutlookMemoryLeak.Item_OnSendMemoryLeakTest;
var
  Outlook: TOutlookApplication;
  MailItem: _MailItem;
  Mail: TMailItem;
begin
  Outlook := TOutlookApplication.Create(nil);
  try
    MailItem := Outlook.CreateItem(olMailItem) as _MailItem;
    try
      Mail := TMailItem.Create(nil);
      try
        Mail.ConnectTo(MailItem);
        Mail.OnSend := HandleSend;

        Mail.Subject := 'A Subject';
        Mail.Recipients.Add('petr.fejfar@xxx.yy');
        Mail.Body := 'A Body';
        FSendCnt := 0;
        Mail.Display(True);
        CheckTrue(FSendCnt > 0, 'Mail has been cancelled by user');
      finally
        Mail.Free;
      end;
    finally
      MailItem := nil;
    end;
  finally
    Outlook.Free;
  end;
end;

procedure TestOutlookMemoryLeak.HandleItemSend(Sender: TObject;
  const Item: IDispatch; var Cancel: WordBool);
begin
  inc(FSendCnt);
end;

procedure TestOutlookMemoryLeak.HandleSend(Sender: TObject;
  var Cancel: WordBool);
begin
  inc(FSendCnt);
end;

initialization
  RegisterTest(TestOutlookMemoryLeak.Suite);

end.

Does anybody has an idea, what is wrong with the both approaches above?

Community
  • 1
  • 1
pf1957
  • 987
  • 1
  • 5
  • 18
  • Please try to specify your problem, without details what you asking for is somebody to do your work. – sausagequeen Apr 01 '14 at 11:27
  • Sorry for that noise: I had to leave my computer for hours in the middle of the non-finished question in the memo on web page and it seems that the shitty browser has posted it :-O I'll continue with question ASAP. – pf1957 Apr 01 '14 at 12:34
  • it's possible that you've been hit by a rather nasty bug in OleCtrls.pas. can you post the contents of this function: `TOleControl.GetIDispatchProp`. I tested your code under Delphi XE and it doesn't leak. – whosrdaddy Apr 01 '14 at 15:06
  • In our national forum, one collegue could repeat the leak on Win8+XE3+Outlook2013, but on Win7+D2010+Outlook2010 not. I did additional test on Win7(64bit)+D2010Upd4+Outlook2010 and behaveour is the same se with XE3 on the same machine (there are leaks) – pf1957 Apr 01 '14 at 15:31
  • I have Win7(64-bit) Office2010 and XE(latest update). what do you do in outlook to reproduce the problem? you send the mail and that's it? – whosrdaddy Apr 01 '14 at 15:34
  • In my solution, the leak occurs only if I DO NOT send an email. Just close the window and answer NO on Save question. Then I quit testing application. In Sertac'c version I got the leak if email is sent or cancelled (dialog is closed without sending). – pf1957 Apr 01 '14 at 15:37
  • Since I can't reproduce the problem (your code and Sertac's code work fine), can you debug this part of the code? Compile with debug DCU's and put a breakpoint on the first line in `TOleServer.Destroy`. Then step into the line where `FEventDispatch._Release` is called and check the `InternalRefCount` variable. – whosrdaddy Apr 01 '14 at 15:39
  • FEventDispatch._Release=**4**, FServerData^.InstanceCount=1 (bkpt on if ... then ...Free). If I sent an email out, the FEventDispatch._Release=**0**, FServerData^.InstanceCount=1 – pf1957 Apr 01 '14 at 15:45
  • FEventDispatch.InternalRefCount=**5** in leaked state, **1** in correct state. – pf1957 Apr 01 '14 at 15:50
  • GetIDispatchProp looks like: `var Temp: TVarData; begin GetProperty(Index, Temp); Result := IDispatch(Temp.VDispatch); end; ` – pf1957 Apr 01 '14 at 15:51
  • next step would be seeing where the referencecount is increased. So this means putting a breakpoint on `TServerEventDispatch._AddRef` and looking at the call stack who is adding references. Two certain leaks comes from `TOleControl.GetIDispatchProp` and `TOleControl.GetIUnknown` but I am not sure if they are called in this case. – whosrdaddy Apr 01 '14 at 16:34
  • For reference here is the [QualityCentral report](http://qc.embarcadero.com/wc/qcmain.aspx?d=106829) about mentioned bugs in `Vcl.OleCtrls.pas`. But I am not sure this applies to your leak... – whosrdaddy Apr 01 '14 at 16:57
  • I tried to copy OleCtrls to my DUnit test and modify it as suggested by QC ticket, but memory leaks are still there. – pf1957 Apr 02 '14 at 10:29
  • I was looking to five stack traces on bkpt at _AddRef, but I have no good knowledge how OLE works, hence I'm not able to see something what could push me forward (I can update the question text and put stack traces there, but those traces are solid chunks of items) – pf1957 Apr 02 '14 at 10:34

0 Answers0