尝试读取或写入受保护的内存

4
我遇到了下面的MAPI类问题。(原始来源http://www.codeproject.com/KB/IP/SendFileToNET.aspx)
当用户尝试使用SendMailPopup方法发送电子邮件时,电子邮件程序会正确打开,但在邮件窗口关闭后,该程序有时会崩溃并显示以下消息: Attempted to read or write protected memory. This is often an indication that other memory is corrupt. 我怀疑这个错误是由清理方法引起的,并且我成功地通过使用Int64来存储指针而不是原始版本中使用的int来减少了崩溃的频率。然而,我的.NET不安全编程知识相当有限,所以有人可以帮助我找出其余崩溃的原因吗?
更新:显然,清理方法并没有引起异常,因为即使将其注释掉,程序仍会崩溃,因此唯一可能的原因就是MAPI32.ddl sendmail方法。可能是参数传递给它的指针有问题。
用户正在使用具有64位处理器和32位winxp的系统。
更新:
本线程提供的解决方案确实降低了崩溃的频率,但并没有完全解决问题。唯一有效的解决方案是编写一个使用c++实际进行MAPI调用的小型控制台应用程序。我们的.NET应用程序通过启动控制台应用程序并使用参数将数据传递给它来与mapi接口。
 public class MAPI
    {
        public bool AddRecipientTo(string email)
        {
           return AddRecipient(email, HowTo.MAPI_TO);
        }

        public bool AddRecipientCC(string email)
        {
           return AddRecipient(email, HowTo.MAPI_CC);
        }

        public bool AddRecipientBCC(string email)
        {
          return AddRecipient(email, HowTo.MAPI_BCC);
        }

        public void AddAttachment(string strAttachmentFileName)
        {
            m_attachments.Add(strAttachmentFileName);
        }

        public int SendMailPopup(string strSubject, string strBody)
        {
            return SendMail(strSubject, strBody, MAPI_LOGON_UI | MAPI_DIALOG);
        }

        public int SendMailDirect(string strSubject, string strBody)
        {
            return SendMail(strSubject, strBody, MAPI_LOGON_UI);
        }


        [DllImport("MAPI32.DLL")]
        static extern int MAPISendMail(IntPtr sess, IntPtr hwnd, MapiMessage message, int flg, int rsv);

        int SendMail(string strSubject, string strBody, int how)
        {
            MapiMessage msg = new MapiMessage();
            msg.subject = strSubject;
            msg.noteText = strBody;

            msg.recips = GetRecipients(out msg.recipCount);
            msg.files = GetAttachments(out msg.fileCount);

            m_lastError = MAPISendMail(new IntPtr(0L), new IntPtr(0L), msg, how, 0);
            if (m_lastError > 1)
                MessageBox.Show("MAPISendMail failed! " + GetLastError(), "MAPISendMail");

            Cleanup(ref msg);
            return m_lastError;
        }

        bool AddRecipient(string email, HowTo howTo)
        {
           if (!String.IsNullOrEmpty(email))
            {
            MapiRecipDesc recipient = new MapiRecipDesc();
            recipient.recipClass = (int)howTo;
            recipient.name = email;
            m_recipients.Add(recipient);
            return true;
            }
            else
            {
            return false;
            }
        }

        IntPtr GetRecipients(out int recipCount)
        {
            recipCount = 0;
            if (m_recipients.Count == 0)
                return IntPtr.Zero;

            int size = Marshal.SizeOf(typeof(MapiRecipDesc));
            IntPtr intPtr = Marshal.AllocHGlobal(m_recipients.Count * size);

            int ptr = (int)intPtr;
            foreach (MapiRecipDesc mapiDesc in m_recipients)
            {
                Marshal.StructureToPtr(mapiDesc, (IntPtr)ptr, false);
                ptr += size;
            }

            recipCount = m_recipients.Count;
            return intPtr;
        }

        IntPtr GetAttachments(out int fileCount)
        {
            fileCount = 0;
            if (m_attachments == null)
                return IntPtr.Zero;

            if ((m_attachments.Count <= 0) || (m_attachments.Count > maxAttachments))
                return IntPtr.Zero;

            int size = Marshal.SizeOf(typeof(MapiFileDesc));
            IntPtr intPtr = Marshal.AllocHGlobal(m_attachments.Count * size);

            MapiFileDesc mapiFileDesc = new MapiFileDesc();
            mapiFileDesc.position = -1;
            int ptr = (int)intPtr;

            foreach (string strAttachment in m_attachments)
            {
                mapiFileDesc.name = Path.GetFileName(strAttachment);
                mapiFileDesc.path = strAttachment;
                Marshal.StructureToPtr(mapiFileDesc, (IntPtr)ptr, false);
                ptr += size;
            }

            fileCount = m_attachments.Count;
            return intPtr;
        }

        void Cleanup(ref MapiMessage msg)
        {
            try
            {
                int size = Marshal.SizeOf(typeof(MapiRecipDesc));
                Int64 ptr = 0;

                if (msg.recips != IntPtr.Zero)
                {
                    ptr = msg.recips.ToInt64();
                    for (int i = 0; i < msg.recipCount; i++)
                    {
                        Marshal.DestroyStructure((IntPtr)ptr, typeof(MapiRecipDesc));
                        ptr += size;
                    }
                    Marshal.FreeHGlobal(msg.recips);
                }

                if (msg.files != IntPtr.Zero)
                {
                    size = Marshal.SizeOf(typeof(MapiFileDesc));

                    ptr = msg.files.ToInt64();
                    for (int i = 0; i < msg.fileCount; i++)
                    {
                        Marshal.DestroyStructure((IntPtr)ptr, typeof(MapiFileDesc));
                        ptr += size;
                    }
                    Marshal.FreeHGlobal(msg.files);
                }
                m_recipients.Clear();
                m_attachments.Clear();
            }
            catch (Exception e)
            {
                SmtpSender errorSender = new SmtpSender();
                errorSender.SendAutomaticError(e.StackTrace + e.Message, "Virhe mapi sähköpostin lähetyksessä" + MySession.ProjectName + " Käyttäjä:" + MySession.LoginName);
            }


        }

        public string GetLastError()
        {
            if (m_lastError <= 26)
                return errors[m_lastError];
            return "MAPI error [" + m_lastError.ToString() + "]";
        }

        readonly string[] errors = new string[] {
        "OK [0]", "User abort [1]", "Yleinen virhe sähköpostin lähettämisessä [2]", "MAPI login failure [3]",
        "Disk full [4]", "Insufficient memory [5]", "Access denied [6]", "-unknown- [7]",
        "Too many sessions [8]", "Too many files were specified [9]", "Too many recipients were specified [10]", "A specified attachment was not found [11]",
        "Attachment open failure [12]", "Attachment write failure [13]", "Unknown recipient [14]", "Bad recipient type [15]",
        "No messages [16]", "Invalid message [17]", "Text too large [18]", "Invalid session [19]",
        "Type not supported [20]", "A recipient was specified ambiguously [21]", "Message in use [22]", "Network failure [23]",
        "Invalid edit fields [24]", "Asiakkaalle ei ole määritetty sähköpostiosoitetta.", "Not supported [26]" 
        };


        List<MapiRecipDesc> m_recipients = new List<MapiRecipDesc>();
        List<string> m_attachments = new List<string>();
        int m_lastError = 0;

        const int MAPI_LOGON_UI = 0x00000001;
        const int MAPI_DIALOG = 0x00000008;
        const int maxAttachments = 20;

        enum HowTo { MAPI_ORIG = 0, MAPI_TO, MAPI_CC, MAPI_BCC };
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public class MapiMessage
    {
        public int reserved;
        public string subject;
        public string noteText;
        public string messageType;
        public string dateReceived;
        public string conversationID;
        public int flags;
        public IntPtr originator;
        public int recipCount;
        public IntPtr recips;
        public int fileCount;
        public IntPtr files;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public class MapiFileDesc
    {
        public int reserved;
        public int flags;
        public int position;
        public string path;
        public string name;
        public IntPtr type;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public class MapiRecipDesc
    {
        public int reserved;
        public int recipClass;
        public string name;
        public string address;
        public int eIDSize;
        public IntPtr entryID;
    }

以下是异常的堆栈跟踪信息。它部分使用芬兰语,但您仍然可以从中看到方法名称。

在 System.Windows.Forms.UnsafeNativeMethods.DispatchMessageA(MSG& msg) 处 在 System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32 dwComponentID, Int32 reason, Int32 pvLoopData) 处 在 System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context) 处 在 System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context) 处 在 System.Windows.Forms.Application.Run(Form mainForm) 处

2个回答

3

您没有一致地使用Int64(包括GetRecipients和GetAttachments)。我怀疑问题就在这里,但是我没有详细检查。以下是所需更改。请注意,我使用了稍微不那么容易出错的IntPtr递增方法。

已修改为使用Joerg的建议增加文件附件路径的缓冲区大小。

public class MAPI
{
    public bool AddRecipientTo(string email)
    {
        return AddRecipient(email, HowTo.MAPI_TO);
    }

    public bool AddRecipientCC(string email)
    {
        return AddRecipient(email, HowTo.MAPI_CC);
    }

    public bool AddRecipientBCC(string email)
    {
        return AddRecipient(email, HowTo.MAPI_BCC);
    }

    public void AddAttachment(string strAttachmentFileName)
    {
        m_attachments.Add(strAttachmentFileName);
    }

    public int SendMailPopup(string strSubject, string strBody)
    {
        return SendMail(strSubject, strBody, MAPI_LOGON_UI | MAPI_DIALOG);
    }

    public int SendMailDirect(string strSubject, string strBody)
    {
        return SendMail(strSubject, strBody, MAPI_LOGON_UI);
    }

    int SendMail(string strSubject, string strBody, int how)
    {
        MapiMessage msg = new MapiMessage();
        msg.subject = strSubject;
        msg.noteText = strBody;

        msg.recips = GetRecipients(out msg.recipCount);
        msg.files = GetAttachments(out msg.fileCount);

        m_lastError = MAPISendMail(new IntPtr(0L), new IntPtr(0L), msg, how, 0);
        if (m_lastError > 1)
            MessageBox.Show("MAPISendMail failed! " + GetLastError(), "MAPISendMail");

        Cleanup(ref msg);
        return m_lastError;
    }

    bool AddRecipient(string email, HowTo howTo)
    {
        if (!String.IsNullOrEmpty(email))
        {
            MapiRecipDesc recipient = new MapiRecipDesc();
            recipient.recipClass = (int)howTo;
            recipient.name = email;
            m_recipients.Add(recipient);
            return true;
        }
        else
        {
            return false;
        }
    }

    IntPtr GetRecipients(out int recipCount)
    {
        recipCount = 0;
        if (m_recipients.Count == 0)
            return IntPtr.Zero;

        int size = Marshal.SizeOf(typeof(MapiRecipDesc));
        IntPtr blockPtr = Marshal.AllocHGlobal(m_recipients.Count * size);
        IntPtr currentPtr = blockPtr;

        foreach (MapiRecipDesc mapiDesc in m_recipients)
        {
            Marshal.StructureToPtr(mapiDesc, currentPtr, false);
            currentPtr = (IntPtr)((long)currentPtr + size);
        }

        recipCount = m_recipients.Count;
        return blockPtr;
    }

    IntPtr GetAttachments(out int fileCount)
    {
        fileCount = 0;
        if (m_attachments == null)
            return IntPtr.Zero;

        if ((m_attachments.Count <= 0) || (m_attachments.Count > maxAttachments))
            return IntPtr.Zero;

        int size = Marshal.SizeOf(typeof(MapiFileDesc));
        IntPtr blockPtr = Marshal.AllocHGlobal(m_attachments.Count * size);
        IntPtr currentPtr = blockPtr;

        MapiFileDesc mapiFileDesc = new MapiFileDesc();
        mapiFileDesc.position = -1;

        foreach (string strAttachment in m_attachments)
        {
            mapiFileDesc.name = Path.GetFileName(strAttachment);
            mapiFileDesc.path = Marshal.AllocHGlobal(MAX_PATH);
            CopyStringAnsi(mapiFileDesc.path, strAttachment);
            Marshal.StructureToPtr(mapiFileDesc, currentPtr, false);
            currentPtr = (IntPtr)((long)currentPtr + size);
        }

        fileCount = m_attachments.Count;
        return blockPtr;
    }

    void Cleanup(ref MapiMessage msg)
    {
        try
        {
            if (msg.recips != IntPtr.Zero)
            {
                IntPtr currentPtr = msg.recips;
                int size = Marshal.SizeOf(typeof(MapiRecipDesc));

                for (int i = 0; i < msg.recipCount; i++)
                {
                    Marshal.DestroyStructure(currentPtr, typeof(MapiRecipDesc));
                    currentPtr = (IntPtr)((long)currentPtr + size);
                }
                Marshal.FreeHGlobal(msg.recips);
            }

            if (msg.files != IntPtr.Zero)
            {
                IntPtr currentPtr = msg.files;
                int size = Marshal.SizeOf(typeof(MapiFileDesc));

                for (int i = 0; i < msg.fileCount; i++)
                {
                    Marshal.DestroyStructure(currentPtr, typeof(MapiFileDesc));
                    currentPtr = (IntPtr)((long)currentPtr + size);
                }
                Marshal.FreeHGlobal(msg.files);
            }
            m_recipients.Clear();
            m_attachments.Clear();
        }
        catch (Exception e)
        {
            SmtpSender errorSender = new SmtpSender();
            errorSender.SendAutomaticError(e.StackTrace + e.Message, "Virhe mapi sähköpostin lähetyksessä" + MySession.ProjectName + " Käyttäjä:" + MySession.LoginName);
        }
    }

    public string GetLastError()
    {
        if (m_lastError <= 26)
            return errors[m_lastError];
        return "MAPI error [" + m_lastError.ToString() + "]";
    }

    readonly string[] errors = new string[] {  
        "OK [0]", "User abort [1]", "Yleinen virhe sähköpostin lähettämisessä [2]", "MAPI login failure [3]",  
        "Disk full [4]", "Insufficient memory [5]", "Access denied [6]", "-unknown- [7]",  
        "Too many sessions [8]", "Too many files were specified [9]", "Too many recipients were specified [10]", "A specified attachment was not found [11]",  
        "Attachment open failure [12]", "Attachment write failure [13]", "Unknown recipient [14]", "Bad recipient type [15]",  
        "No messages [16]", "Invalid message [17]", "Text too large [18]", "Invalid session [19]",  
        "Type not supported [20]", "A recipient was specified ambiguously [21]", "Message in use [22]", "Network failure [23]",  
        "Invalid edit fields [24]", "Asiakkaalle ei ole määritetty sähköpostiosoitetta.", "Not supported [26]"   
        };


    List<MapiRecipDesc> m_recipients = new List<MapiRecipDesc>();
    List<string> m_attachments = new List<string>();
    int m_lastError = 0;

    const int MAPI_LOGON_UI = 0x00000001;
    const int MAPI_DIALOG = 0x00000008;
    const int maxAttachments = 20;

    const int MAX_PATH = 256;

    enum HowTo { MAPI_ORIG = 0, MAPI_TO, MAPI_CC, MAPI_BCC };

    [DllImport("MAPI32.DLL")]
    static extern int MAPISendMail(IntPtr sess, IntPtr hwnd, MapiMessage message, int flg, int rsv);

    [DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory", CharSet = CharSet.Ansi)]
    static extern void RtlMoveStringAnsi(IntPtr pdst, string psrc, IntPtr sizetcb);

    private void CopyStringAnsi(IntPtr intPtr, string str)
    {
        int length = (str.Length + 1) * Marshal.SystemMaxDBCSCharSize;
        RtlMoveStringAnsi(intPtr, str, (IntPtr)length);
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    class MapiMessage
    {
        public int reserved;
        public string subject;
        public string noteText;
        public string messageType;
        public string dateReceived;
        public string conversationID;
        public int flags;
        public IntPtr originator;
        public int recipCount;
        public IntPtr recips;
        public int fileCount;
        public IntPtr files;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    class MapiFileDesc
    {
        public int reserved;
        public int flags;
        public int position;
        public IntPtr path;
        public string name;
        public IntPtr type;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    class MapiRecipDesc
    {
        public int reserved;
        public int recipClass;
        public string name;
        public string address;
        public int eIDSize;
        public IntPtr entryID;
    }
}

它仍然会在少数用户的计算机上随机崩溃,我为了测试目的注释掉了清理方法,但它并没有解决问题。如果用户在发送电子邮件之前从电子邮件弹出窗口打开附件文件,则崩溃似乎更容易发生。是否有意义以某种方式将电子邮件发送与主进程分离,以便崩溃不会带走整个应用程序?如果是,请提供任何实现此目标的指针。 - Jargo
如果您自己为路径缓冲区分配内存(Joerg的建议),则必须释放内存。 DestroyStructure不会为您执行此操作,因为成员是IntPtr。 - Mattias S
http://blogs.msdn.com/b/mstehle/archive/2007/10/03/fyi-why-are-mapi-and-cdo-1-21-not-supported-in-managed-net-code.aspx - Tergiver
所以,根据那篇文章,你可以使用MAPI或.NET,但不能同时使用。 - Tergiver
上面的GetAttachments代码似乎不能正常工作。 如果我将其更改为以下内容,它对我来说是有效的: mapiFileDesc.path = Path.GetFullPath(strAttachment); - James_UK_DEV
显示剩余8条评论

-1

我注意到调用MAPISendMail时,包含附件完整文件名的缓冲区(msg.files)将被MAPI服务提供程序覆盖。提供更长的缓冲区可以解决这些问题。


如何增加 MAPI 缓冲区大小? - Jargo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接