delphi – 使用简单MAPI TEmail组件进行日志记录时的死锁

我们正在使用免费软件 MAPI/SMAPI实现间歇性死锁.我怀疑实现是否有问题但是可能将登录标志更改为Mapilogon或Exchange上的配置设置可以解决此问题.
Result := Mapilogon(0,logonProfile,logonPassword,fllogonFlags,@hSession);

为@J添加了cudo

不鼓励使用Simple MAPI.正确的操作是开始使用扩展MAPI或Outlook对象模型.虽然我同意这一说法,但我没有任何影响力来实现这一点.

当前设置的解决方案或理解为什么会发生死锁的解决方案仍然很难实现.

简而言之

>线程0b60调用Mapilogof
>在logof期间,它等待线程0894
>线程0894等待临界区036c
>临界区036c被线程0b60锁定

僵局

内核转储显示以下关键部分被线程b60锁定并拥有

    CritSec EMSMDB32!ScStatClose+17ac7 at 354650d0
    WaiterWoken        No
    LockCount          1
    RecursionCount     1
    OwningThread       b60
    EntryCount         0
    ContentionCount    1
    *** Locked

线程的0b60调用堆栈

内核线程对象88a53758
注意带参数87fc3c68的KeWaitForSingleObject是线程0894

    b8b4fcec 8093b2e4 87fc3c68 00000006 00000001 nt!KeWaitForSingleObject+0x346 (FPO: [Non-Fpo])
    b8b4fd50 8088b658 00000184 00000000 00000000 nt!NtWaitForSingleObject+0x9a (FPO: [Non-Fpo])
    b8b4fd50 7c82845c 00000184 00000000 00000000 nt!KiSystemServicePostCall (FPO: [0,0] TrapFrame @ b8b4fd64)
    0012f618 7c827b79 77e61d06 00000184 00000000 ntdll!KiFastSystemCallRet (FPO: [0,0])
    0012f61c 77e61d06 00000184 00000000 00000000 ntdll!NtWaitForSingleObject+0xc (FPO: [3,0])
    0012f68c 77e61c75 00000184 ffffffff 00000000 kernel32!WaitForSingleObjectEx+0xac (FPO: [Non-Fpo])
    0012f6a0 3540fc13 00000184 ffffffff 02102150 kernel32!WaitForSingleObject+0x12 (FPO: [Non-Fpo])
    0012f6b4 3540a226 7c81a1a8 3540546f 02102108 EMSMDB32!XPProviderInit+0x58d5
    0012f6bc 3540546f 02102108 00db29c0 0012f6f0 EMSMDB32!MSProviderInit+0x16af6
    0012f6d0 3553ce40 02102108 35411e97 02102108 EMSMDB32!MSProviderInit+0x11d3f
    00000000 00000000 00000000 00000000 00000000 MSMAPI32!UlRelease+0xe
    /* Reconstructed from MAP file */
    0012f878  00422b81 21B81      mailrequestserver+0x22b81      0001:00021B70       Mapilogoff
    0012f894  00423dde 22DDE      mailrequestserver+0x23dde      0001:00022DB0       TEmail.logoff

线程的0894调用堆栈是

内核线程对象87fc3c68
注意EMSMDB32!ScStatClose调用导致RtlpWaitOnCriticalSection,参数0000036c是线程0b60拥有的关键部分

    0231ff80 7c83d0f7 0000036c 00000004 00000000 ntdll!RtlpWaitOnCriticalSection+0x1a3 (FPO: [Non-Fpo])
    0231ffa0 3544d394 354650d0 00000000 00000001 ntdll!RtlEnterCriticalSection+0xa8 (FPO: [Non-Fpo])
    0231ff98 354650d0                            EMSMDB32!ScStatClose+0x17ac7
    0231ffa4 3544d394                            EMSMDB32!EcUnregisterPushNotification+0x12033
    0231ffa8 354650d0                            EMSMDB32!ScStatClose+0x17ac7
    0231ffb4 3544d114                            EMSMDB32!EcUnregisterPushNotification+0x11db3

>调用堆栈显示涉及UnregisterPushNotifications.搜索推送通知,我找不到任何理由为什么我们需要它(我们只是登录,发送邮件和日志)但是,因为这完全发生在MAPI中,我不知道如何阻止呼叫发生.
>如果不能以某种方式忽略/禁用推送通知,那么非常欢迎任何可能导致/解决此问题的指针.

一些额外的信息

>两个线程都属于同一个进程
> MSMAPI32.dll是版本10.0.6861.0
> EMSMDB32.dll是版本10.0.6742.0

来自SMapi.pas的相关代码

function Mapilogoff(lhSession  : LHANDLE;
                    ulUIParam  : ULONG;
                    flFlags    : ULONG;
                    ulReserved : ULONG): ULONG;

function Mapilogon(ulUIParam   : ULONG;
                   lpszName    : PChar;
                   lpszPassword: PChar;
                   flFlags     : ULONG;
                   ulReserved  : ULONG;
                   lplhSession : LPLHANDLE): ULONG;

function MapiSendMail(lhSession  : LHANDLE;
                      ulUIParam  : ULONG;
                      lpMessage  : lpMapiMessage;
                      flFlags    : ULONG;
                      ulReserved : ULONG): ULONG;

procedure InitializeSMAPI;
var
  OldErrorMode: Word;
  OSVersionInfo: TOSVersionInfo;
  RegHandle: HKEY;
  MapiDetectBuf: array[0..8] of Char;
  MapiDetectBufSize: Windows.DWORD;
  RegValueType: Windows.DWORD;
begin
  { first check wether MAPI is available on the system; this is done
    as described in the MS MAPI docs }

  OSVersionInfo.dwOSVersionInfoSize := SizeOf(OSVersionInfo);
  GetVersionEx(OSVersionInfo);
  if (OSVersionInfo.dwMajorVersion > 3) or { NT 4.0 and later }
     { earlier than NT 3.51 }
     ((OSVersionInfo.dwMajorVersion = 3) and (OSVersionInfo.dwMinorVersion > 51)) then
  begin
    if RegOpenKeyEx( HKEY_LOCAL_MACHINE,'SOFTWARE\Microsoft\Windows Messaging Subsystem',KEY_READ,RegHandle) <> ERROR_SUCCESS then
    begin
      exit;
    end;

    MAPIDetectBufSize := SizeOf(MAPIDetectBuf);
    if RegQueryValueEx( RegHandle,'MAPI',nil,@RegValueType,PByte(@MAPIDetectBuf),@MAPIDetectBufSize) <> ERROR_SUCCESS then
    begin
      exit;
    end;

    RegCloseKey(RegHandle);

    { "boolean" integer --> is == "1"? }
    if not ((MAPIDetectBuf[0] = '1') and (MAPIDetectBuf[1] = #0)) then
      exit;
  end
  else
    if GetProfileInt('Mail',0) = 0 then { 16 bit and NT 3.51 detection logic }
      Exit;

  OldErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS + SEM_NOOPENFILEERRORBox);
    DLLHandle := LoadLibrary(DLLName32); { start without .DLL attached }
  { OldErrorMode := } SetErrorMode(OldErrorMode);

  if DLLHandle = 0 then { got an error }
  begin
    OldErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS + SEM_NOOPENFILEERRORBox);
    try
      DLLHandle := LoadLibrary(DLLName32DLL);

      if DLLHandle = 0 then
      begin
        exit; { second attempt did not work out either }
      end;

    finally
      { OldErrorMode := } SetErrorMode(OldErrorMode);
    end;
  end;
  begin
    DllInitialized := true;

    @FnMapiFindNext :=    GetProcAddress(DLLHandle,'MAPIFindNext');
    @FnMapilogoff :=      GetProcAddress(DLLHandle,'MAPIlogoff');
    @FnMapilogon :=       GetProcAddress(DLLHandle,'MAPIlogon');
    @FnMapiSendMail :=    GetProcAddress(DLLHandle,'MAPISendMail');
    @FnMapiReadMail :=    GetProcAddress(DLLHandle,'MAPIReadMail');
    @FnMapiDeleteMail :=  GetProcAddress(DLLHandle,'MAPIDeleteMail');
    @FnMapiResolveName := GetProcAddress(DLLHandle,'MAPIResolveName');
    @FnMapiFreeBuffer :=  GetProcAddress(DLLHandle,'MAPIFreeBuffer');
    @FnMapiAddress :=     GetProcAddress(DLLHandle,'MAPIAddress');
    @FnMapiSaveMail :=    GetProcAddress(DLLHandle,'MAPISaveMail');

    if    (@FnMapiAddress     = nil)
       or (@FnMapiFreeBuffer  = nil)
       or (@FnMapiResolveName = nil)
       or (@FnMapiDeleteMail  = nil)
       or (@FnMapiReadMail    = nil)
       or (@FnMapiSendMail    = nil)
       or (@FnMapilogon       = nil)
       or (@FnMapilogoff      = nil)
       or (@FnMapiFindNext    = nil)
       or (@FnMapiSaveMail    = nil) then
    begin
      raise EMAPIdllerror.Create(SMapiGetProcAdressFailed);
    end;
  end;
end;

来自Email.pas的相关代码

destructor TEmail.Destroy;
begin
  ...
  try
    if hSession <> 0 then
      logoff;
  except
  end;
end;
function TEmail.logon: Integer;
const
  ProfileKey95 = 'Software\Microsoft\Windows Messaging Subsystem\Profiles';
  ProfileKeyNT = 'Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles';
var
  logonProfile : PChar;
  logonPassword: PChar;

  ProfileKey   : PChar;

  Reg : TRegistry;
begin
  CheckMapi;
  Result := SUCCESS_SUCCESS;
  { Check if already logged in. }
  if hSession = 0 then
  begin
    if FUseDefProfile then
    begin
      Reg := TRegistry.Create;
      try
        { get platform (Win95/NT) dependent profile key                }
        {  code added by Ulrik Schoth schoth@krohne.mhs.compuserve.com }
        if Reg.KeyExists(ProfileKeyNT) then
        begin
          ProfileKey := ProfileKeyNT;
        end
        else
        begin
          ProfileKey := ProfileKey95;
        end;

        Reg.Rootkey := HKEY_CURRENT_USER;
        if Reg.OpenKey(ProfileKey,False) then
        begin
          try
            FProfile := Reg.Readstring('DefaultProfile');
          except
            FProfile := '';
          end;
        end;
      finally
        Reg.Free;
      end;
    end;

    logonProfile := nil;
    logonPassword := nil;

    try
      if Length(FProfile) > 0 then
      begin
        logonProfile := StrPCopy(StrAlloc(Length(FProfile)+1),FProfile);
      end;

      if Length(FPassword) > 0 then
      begin
        logonPassword := StrPCopy(StrAlloc(Length(FPassword)+1),FPassword);
      end;

      DoBeforelogon;

      Result := Mapilogon(0,@hSession);
      if Result <> SUCCESS_SUCCESS then
        Result := Mapilogon(0,fllogonFlags or MAPI_logon_UI,@hSession);

      if Result = SUCCESS_SUCCESS then
        DoAfterlogon
      else
        DoMapiError(Result);

    finally
      StrDispose(logonProfile);
      StrDispose(logonPassword);
    end;
  end;
end;

function TEmail.SendMailEx(DoSave: boolean): Integer;
var
  MapiMessage   : TMapiMessage;
  MapiRecipDesc : TMapiRecipDesc;
  MapiFileDesc  : TMapiFileDesc;
  lpRecipArray  : TlpRecipArray;
  lpAttachArray : TlpAttachArray;
  lpszPathname  : TlpszPathname;
  lpszFileName  : TlpszFileName;

  szSubject     : PChar;
  szText        : PChar;
  szMessageId   : PChar;
  szMessageType : PChar;

  Attachment    : SString;
  flFlags       : ULONG;
  fllogoff      : Boolean;

  i             : Integer;
  nRecipients   : Integer;
  nAttachments  : Integer;

begin
  CheckMapi;

  {make sure the cleanup does not free garbage }
  lpRecipArray  := nil;
  lpAttachArray := nil;

  fllogoff      := False;

  {check our built-in limits - which have effectively been removed }
  nRecipients := Frecip.Count + FCC.Count + FBCC.Count;
  if nRecipients > RECIP_MAX then
  begin
    Result := MAPI_E_TOO_MANY_RECIPIENTS;

    DoMapiError(Result);

    exit;
  end;

  nAttachments := FAttachment.Count;
  if nAttachments > ATTACH_MAX then
  begin
    Result := MAPI_E_TOO_MANY_FILES;

    DoMapiError(Result);

    exit;
  end;

  { begin the work }
  try

    fllogoff := (hSession = 0);

    { logon to mail server if not already logged on. }

    if logon <> SUCCESS_SUCCESS then
    begin
      Result := MAPI_E_LOGIN_FAILURE;

      DoMapiError(Result);

      exit;
    end;


    { Initialise MAPI structures and local arrays. }

    FillChar(MapiMessage,SizeOf(TMapiMessage),0);
    FillChar(MapiRecipDesc,SizeOf(TMapiRecipDesc),0);
    FillChar(MapiFileDesc,SizeOf(TMapiFileDesc),0);

    lpRecipArray  := TlpRecipArray(StrAlloc(nRecipients*SizeOf(TMapiRecipDesc)));
    FillChar(lpRecipArray^,StrBufSize(PChar(lpRecipArray)),0);

    lpAttachArray := TlpAttachArray(StrAlloc(nAttachments*SizeOf(TMapiFileDesc)));
    FillChar(lpAttachArray^,StrBufSize(PChar(lpAttachArray)),0);

    { Fill in subject & message text. }

    szSubject      := nil;
    szText         := nil;
    szMessageId    := nil;
    szMessageType  := nil;

    try
      if Length(FSubject) > 0 then
      begin
        szSubject := StrAlloc(length(FSubject) + 1);
        StrPCopy(szSubject,FSubject);
      end;
      MapiMessage.lpszSubject  := szSubject;

      if Length(FText) > 0 then
      begin
        szText := StrAlloc(length(FText) + 1);
        StrPCopy(szText,FText);
      end;
      MapiMessage.lpszNoteText := szText;

      { for non-IPM messages }
      if Length(FMessageType) > 0 then
      begin
        szMessageType := StrAlloc(Length(FMessageType) + 1);
        StrPCopy(szMessageType,FMessageType);
      end;
      MapiMessage.lpszMessageType := szMessageType;

      if FpLongText <> nil then
        MapiMessage.lpszNoteText := FpLongText;

      { check and fill in recipients if any}
      nRecipients := 0;
      ListToRecipArray(FRecip,MAPI_TO,lpRecipArray,nRecipients);
      ListToRecipArray(FCC,MAPI_CC,nRecipients);
      ListToRecipArray(FBcc,MAPI_BCC,nRecipients);


      MapiMessage.nRecipCount := nRecipients;

      flFlags := 0; { Don't display MAPI Dialog if recipient specified. }

        MapiMessage.lpRecips := @lpRecipArray^;

      { Process file attachments. }

      nAttachments := 0;

      for i := 0 to (Fattachment.Count - 1) do
      begin
        Attachment := CheckAttachment(Fattachment.Strings[i]);
        if Length(Attachment) = 0 then
        begin
          Result := MAPI_E_ATTACHMENT_NOT_FOUND;

          DoMapiError(Result);

          exit;
        end;
        lpAttachArray^[i].nPosition    := Integer($FFFFFFFF);  {Top of message. }
        lpszPathname                   := new(TlpszPathname);
        lpAttachArray^[i].lpszPathName := StrPcopy(lpszPathname^,Attachment);


      { begin code added by MJK }
        lpszFileName                   := new(TlpszFileName);

        { truncate attachment filename if desired }
        if FTruncAttFN then
        begin
          { truncate }
          lpAttachArray^[i].lpszFileName :=
            StrPCopy(lpszFileName^,TruncAttachmentFN(ExtractFileName(Attachment)))
        end
        else
        begin
          { leave alone }
          lpAttachArray^[i].lpszFileName :=
            StrPCopy(lpszFileName^,ExtractFileName(Attachment));
        end;
      {end code added by MJK}

        Inc(nAttachments);
      end;

      MapiMessage.nFileCount := nAttachments;
      if nAttachments > 0 then
      begin
        MapiMessage.lpFiles := @lpAttachArray^;
      end
      else
      begin
        MapiMessage.lpFiles := nil;
      end;

      { receipt requested ? }
      if FAcknowledge then
        MapiMessage.flFlags := MapiMessage.flFlags or MAPI_RECEIPT_REQUESTED;

      { finally send the email message }

      DoBeforeSendMail;

      Result := MapiSendMail(hSession,@MapiMessage,flFlags,0);
      if Result = SUCCESS_SUCCESS then
        DoAfterSendMail
      else
        DoMapiError(Result);


    finally
      StrDispose(szSubject);
      StrDispose(szText);
      StrDispose(szMessageID);
      StrDispose(szMessageType);
    end;

  finally
    { dispose of the recipient & CC name strings }
    if Assigned(lpRecipArray) then
      for i := 0 to (nRecipients - 1) do
      begin
        if Assigned(lpRecipArray^[i].lpszName) then
          Dispose(lpRecipArray^[i].lpszName);

        if Assigned(lpRecipArray^[i].lpszAddress) then
          Dispose(lpRecipArray^[i].lpszAddress);
      end;

    { dispose of the recipient/CC/BCC array }
    StrDispose(PChar(lpRecipArray));

    { dispose of the attachment file name strings }
    if Assigned(lpAttachArray) then
      for i := 0 to (nAttachments - 1) do
      begin
        Dispose(lpAttachArray^[i].lpszPathname);
        Dispose(lpAttachArray^[i].lpszFileName);
      end;

    { dispose of the attachment array }
    StrDispose(PChar(lpAttachArray));

    { Auto logoff,if no session was active. }
    if fllogoff = True then
      logoff;
  end;

end;

PS.我无法发布转储,它是4GB,但随时可以询问我可能遗漏的任何其他所需细节

解决方法

我有机会通过一个友好的派对来分析转储.

结论是(我的解释)

>这可能是EMSMDB32.dll中的一个错误,但代码太旧了,很可能没有任何支持或意图来修复它.
> EMSMDB32中的偏移量!EcUnregisterPushNotification 0x11db3非常大,可能无法执行EcUnregisterPushNotification方法.无法确定可能执行的方法.

可能的解决方

由于我们无法修复错误(假设它是一个错误),因此在我们的案例中可以使用以下解决方法

>每x分钟重启一次服务.>将邮件分组以通过配置文件发送并作为批处理发送,而不是为每封邮件登录/注销.>启动辅助线程以观察主线程,并在检测到死锁时重新启动服务.

相关文章

ffmpeg 是一套强大的开源的多媒体库 一般都是用 c/c++ 调用, 抽空研究了一下该库的最新版 ,把...
32位CPU所含有的寄存器有:4个数据寄存器(EAX、EBX、ECX和EDX)2个变址和指针寄存器(ESI和EDI) 2个指针寄...
1 mov dst, src dst是目的操作数,src是源操作数,指令实现的功能是:将源操作数送到目的操作数中,即:...
有三个API函数可以运行可执行文件WinExec、ShellExecute和CreateProcess。 1.CreateProcess因为使用复杂...
API原型: Declare Function MoveFileEx& Lib "kernel32" Alias "MoveFileExA" (By...