Delphi中的MAPI - 工作但邮件发送后崩溃

时间:2015-03-26 06:55:47

标签: delphi crash mapi

我一直在使用一些代码在我的Delphi XE7程序中使用MAPI发送邮件。 我设法让To,Cc和Bcc与fileattachment一起工作,但是当客户端发送邮件时(在这种情况下是Outlook 2010),程序崩溃了,我根本无法弄清楚我在做什么出错了 - 我可能会对代码视而不见。 我用Dropbox中的文件中的代码做了一个小例子 dl.dropboxusercontent.com/u/65392149/Mapi_MCVE.ZIP

MAPI函数看起来像这样

function SendMailMAPI(const aFrom, aTo, aCc, aBcc, aSubject, aBody: string; aMailFiles: TStringList; aReceipt: boolean): boolean;
var
  MapiStatus: DWord;
  MapiMessage: TMapiMessage;
  MapiOrigin: TMapiRecipDesc;
  MapiRecipient: array of TMapiRecipDesc;
  MapiFiles: PMapiFileDesc;

  RecipientsTo: TStringlist;
  RecipientsCc: TStringlist;
  RecipientsBcc: TStringlist;
  RecipientsCount: integer;
  FilesCount: Integer;
  i: integer;
  Filename: string;
begin
  MapiStatus := SUCCESS_SUCCESS;
  Result := True;
  MapiFiles := nil;

  FillChar(MapiMessage, Sizeof(TMapiMessage), 0);
  if aReceipt then
    MapiMessage.flFlags := MAPI_RECEIPT_REQUESTED;

  MapiMessage.lpszSubject := PAnsiChar(AnsiString(aSubject));
  MapiMessage.lpszNoteText := PAnsiChar(AnsiString(aBody));

  FillChar(MapiOrigin, Sizeof(TMapiRecipDesc), 0);
  MapiOrigin.lpszName := PAnsiChar(AnsiString(aFrom));
  MapiOrigin.lpszAddress := PAnsiChar(AnsiString(aFrom));
  MapiMessage.lpOriginator := nil;

  FilesCount := aMailFiles.Count;
  if FilesCount > 0 then
    begin
      GetMem(MapiFiles, SizeOf(TMapiFileDesc) * FilesCount);
      for i := 0 to FilesCount - 1 do
        begin
          FileName := aMailfiles[i];
          MapiFiles[i].ulReserved := 0;
          MapiFiles[i].flFlags := 0;
          MapiFiles[i].nPosition := ULONG($FFFFFFFF);
          MapiFiles[i].lpszPathName := PAnsiChar(AnsiString(FileName));
          MapiFiles[i].lpszFileName := PAnsiChar(AnsiString(ExtractFileName(FileName)));
          MapiFiles[i].lpFileType := nil;
        end;
      MapiMessage.nFileCount := FilesCount;
      MapiMessage.lpFiles := @MapiFiles^;
    end;

  RecipientsCount := 0;
  RecipientsTo  := TStringlist.Create;
  RecipientsCc  := TStringlist.Create;
  RecipientsBcc := TStringlist.Create;
  RecipientsTo.Delimiter := ';';
  RecipientsCc.Delimiter := ';';
  RecipientsBcc.Delimiter := ';';
  try
    if aTo <> '' then
      begin
        RecipientsTo.DelimitedText := aTo;
        RecipientsCount := RecipientsCount + RecipientsTo.Count;
      end;
    if aCc <> '' then
      begin
        RecipientsCc.DelimitedText := aCc;
        RecipientsCount := RecipientsCount + RecipientsCc.Count;
      end;
    if aBcc <> '' then
      begin
        RecipientsBcc.DelimitedText := aBcc;
        RecipientsCount := RecipientsCount + RecipientsBcc.Count;
      end;

    FillChar(MapiRecipient, Sizeof(TMapiRecipDesc) * RecipientsCount, 0);
    SetLength(MapiRecipient, RecipientsCount);
    RecipientsCount := 0;
    if RecipientsTo.Count > 0 then
      begin
        MapiRecipient[RecipientsCount].ulRecipClass := MAPI_TO;
        for i := 0 to RecipientsTo.Count - 1 do
          begin
            MapiRecipient[RecipientsCount].lpszName := PAnsiChar(AnsiString(RecipientsTo[i]));
            MapiRecipient[RecipientsCount].lpszAddress := PAnsiChar(AnsiString(RecipientsTo[i]));
            Inc(RecipientsCount);
          end;
      end;
    if RecipientsCc.Count > 0 then
      begin
        MapiRecipient[RecipientsCount].ulRecipClass := MAPI_CC;
        for i := 0 to RecipientsCc.Count - 1 do
          begin
            MapiRecipient[RecipientsCount].lpszName := PAnsiChar(AnsiString(RecipientsCc[i]));
            MapiRecipient[RecipientsCount].lpszAddress := PAnsiChar(AnsiString(RecipientsCc[i]));
            Inc(RecipientsCount);
          end;
      end;
    if RecipientsBcc.Count > 0 then
      begin
        MapiRecipient[RecipientsCount].ulRecipClass := MAPI_BCC;
        for i := 0 to RecipientsBcc.Count - 1 do
          begin
            MapiRecipient[RecipientsCount].lpszName := PAnsiChar(AnsiString(RecipientsBcc[i]));
            MapiRecipient[RecipientsCount].lpszAddress := PAnsiChar(AnsiString(RecipientsBcc[i]));
            Inc(RecipientsCount);
          end;
      end;
    MapiMessage.nRecipCount := RecipientsCount;
    MapiMessage.lpRecips:= Pointer(MapiRecipient);
  finally
    RecipientsTo.Free;
    RecipientsCc.Free;
    RecipientsBcc.Free;
  end;

  try
    MapiStatus := MapiSendMail(0, Application.MainForm.Handle, MapiMessage, MAPI_LOGON_UI + MAPI_DIALOG, 0);
  except
    on E:Exception do
      ShowMessage('U_Mailing.Mapi.SendMailMAPI: ' + E.Message);
  end;

  for i := 0 to FilesCount - 1 do
    begin
      System.AnsiStrings.StrDispose(MapiFiles[i].lpszPathName);
      System.AnsiStrings.StrDispose(MapiFiles[i].lpszFileName);
    end;
  for  i := 0 to RecipientsCount - 1 do
    begin
      System.AnsiStrings.StrDispose(MapiRecipient[i].lpszName);
      System.AnsiStrings.StrDispose(MapiRecipient[i].lpszAddress);
    end;

  case MapiStatus of
    MAPI_E_AMBIGUOUS_RECIPIENT:
      Showmessage('A recipient matched more than one of the recipient descriptor structures and MAPI_DIALOG was not set. No message was sent.');
    MAPI_E_ATTACHMENT_NOT_FOUND:
      Showmessage('The specified attachment was not found; no message was sent.');
    MAPI_E_ATTACHMENT_OPEN_FAILURE:
      Showmessage('The specified attachment could not be opened; no message was sent.');
    MAPI_E_BAD_RECIPTYPE:
      Showmessage('The type of a recipient was not MAPI_TO, MAPI_CC, or MAPI_BCC. No message was sent.');
    MAPI_E_FAILURE:
      Showmessage('One or more unspecified errors occurred; no message was sent.');
    MAPI_E_INSUFFICIENT_MEMORY:
      Showmessage('There was insufficient memory to proceed. No message was sent.');
    MAPI_E_LOGIN_FAILURE:
      Showmessage('There was no default logon, and the user failed to log on successfully when the logon dialog box was displayed. No message was sent.');
    MAPI_E_TEXT_TOO_LARGE:
      Showmessage('The text in the message was too large to sent; the message was not sent.');
    MAPI_E_TOO_MANY_FILES:
      Showmessage('There were too many file attachments; no message was sent.');
    MAPI_E_TOO_MANY_RECIPIENTS:
      Showmessage('There were too many recipients; no message was sent.');
    MAPI_E_UNKNOWN_RECIPIENT:
      Showmessage('A recipient did not appear in the address list; no message was sent.');
    MAPI_E_USER_ABORT:
      Showmessage('The user canceled the process; no message was sent.');
    else
      Showmessage('MAPISendMail failed with an unknown error code.');
    Result := False;
  end;
end;

1 个答案:

答案 0 :(得分:0)

我可以看到两个明显的错误。首先,您使用了大量隐含的临时AnsiString变量,这些变量的生命周期不清楚。只要你这样做就会发生这种情况

PAnsiChar(AnsiString(...))

编译器生成一个临时的AnsiString局部变量。你依靠编译器制作足够的,每个不同的字符串一个。

当你在循环中执行此操作时,编译器将在所有迭代之间共享一个隐式局部变量。这还不够。你可能会因为内存管理器没有重新使用内存而逃脱这一点。但是你的代码仍然是错误的。

我会创建一个TList<AnsiString>来保存众多AnsiString变量。每次你需要PAnsiChar这样做:

astr := AnsiString(...);
TempAnsiStrings.Add(astr);
... := PAnsiChar(astr);

其中TempAnsiStrings是包含临时AnsiString个对象的列表。将其包装在嵌套函数中以便于使用。

function GetPAnsiChar(const str: string): PAnsiChar;
var
  astr: AnsiString;
begin
  astr := AnsiString(str);
  TempAnsiStrings.Add(astr);
  Result := PAnsiChar(astr);
end;

显然你需要实例化列表并销毁它,但我相信你已经知道如何做到这一点。

您的另一个问题是对StrDispose的来电。您必须删除它们,因为编译器正在管理生命周期。致电StrDispose以释放分配了StrNewStrAlloc的内存。您没有这样分配,因此没有StrDispose的位置。

动态数组不是使用显式内存分配文件列表,而是更清晰。这样做的另一个好处是可以避免由于缺少finally块而导致代码泄漏的可能性。