请教各位兄台——>Come ON!!!如何在程序里发送邮件?

时间:2022-06-02 00:18:35
如何在程序里发送邮件(同时邮件地址、主题、内容也已经写好,点开后直接发送)?
我知道有打开ie并带上mailto的参数,但是那样的话很不好……

15 个解决方案

#1


我刚做过这方面的程序,但是由于公司产品保密问题,我只能给你一个大体方向.你可以根据POP3和SMTP协议,做两个类分别用于接收和发送程序.其中在这两个类中定义一个CSocket类成员变量(很重要),用这个成员变量和POP3或SMTP服务器对话,对话顺序根据POP3和SMTP协议.再用这两个类去做邮件接收和发送.我有一个简单的邮件发送程序代码,能满足你所提的要求.你要是需要,给我EMAIL.我的EMAIL地址:steven.t@263.net.

#2


ok,发过去了……

#3


用ShellExecute()或CreateProcess()
调用OutLook就行

#4


给我信箱。我发给你。呵呵。你运气好。我刚用过这样一个类。非常好用。

#5


是一个Smtp的类, 可以附件,不过不能太大,还可以用对方的信箱给对方发信,呵呵。病毒专用版...

#6


To Julienjut(秋水)
如何得到默认邮件程序的位置?

To KissGigi(KissGigi)
a_kun@etang.com,
谢谢大家!

#7


Ask Dr. GUI #41
July/August 1998

I Wanna Send Myself an E-mail . . .
Dear Dr. GUI,

I am trying to send a message using MAPISendMail function.

The program compiles correctly but gives a linker error (MainFrm.obj: error LNK2001: unresolved external symbol _MAPISendMail@20). (I have included the mapi.h header file.) I have checked the lib directory. It does contain mapi.lib and mapi32.lib files. I am using MAPISendMail in an MFCAppWizard-generated project. 

I am using Microsoft Developer Studio and Visual C++ 5.0 on Windows NT 4.0.

Thanks for your help,
Saayan Choudhury

Dr. GUI replies:

If you're not getting enough e-mail, send your own! Actually, sending e-mail to yourself is a great way to test e-mail functionality in your apps-and it keeps you from sending lots of e-mail to others.

The problem is that MAPI32.LIB does not export the Simple MAPI functions. Therefore, in order to use the Simple MAPI functions, you must make a call to LoadLibrary() to load the appropriate DLL (MAPI.DLL or MAPI32.DLL). Then you can call GetProcAddress() to obtain pointers to each of the functions you would like to use.

For more information, please see the topic "Initializing a Simple MAPI Client" in the Microsoft Developer Network Library. For sample code, please see the following article in the Microsoft Knowledge Base: Q171096, "FILE: Simple MAPI Console Application," located at http://support.microsoft.com/support/kb/articles/q171/0/96.asp.

If you're using Microsoft Foundation Classes (MFC) and are trying to send a copy of the current document, note that AppWizard has an option to add a Send command on the File menu. If you forgot, don't despair: there's a MAPI component in the component selection. In version 5.0, you get to this by selecting Add to Project from the Project menu, selecting Components and Controls, and then selecting Developer Studio Components to get to the Components and Controls Gallery. (This is one of the slickest features of Visual C++, and perhaps one of the most underused.)

By the way, if you ever need to know how different AppWizard options generate code, it's easy: just run AppWizard with the different options, and then run WinDiff (supplied with Visual C++) on the projects. The differences are the changes you'll have to apply to your program. Dr. GUI sometimes makes a copy of the AppWizard output for important projects just in case he needs to change some of the AppWizard options.

#8


 
How to Generate Accurate Error Reports Without Relying on Users
Walter Meerschaert

Users are notoriously bad at reporting your own error messages back to you. In this article, Walter Meerschaert provides you with a dialog box for your exception handling code that will allow the user to send an e-mail with vital system and program information. Alternatively, it will also allow users to print out that information to fax it to you if they're living in a stone cave and don't yet have access to e-mail on their system. (If they don't have a printer, you're on your own).

All programmers have this problem: You check your messages and there's an angry call from a frustrated user saying there was a bug in your stupid program and he can't work because of you (!), and by the way the message said something like there was an error or something. Okay, you call back and ask them to repeat the error, starting the error resolution procedure from the time that you actually get the real error message. Wouldn't it be nice if the user had a way to pop off an e-mail right from the place the error occurred? What if the e-mail message had the version of the program and some vital system statistics? That would probably speed the process along considerably. In fact, you might be calling the user back with instructions on how to download a new version of your software with the problem already solved, instead of asking for more information. 

Exception handlers
An exception handler is the place in your code where you handle conditions and events that, while you had the foresight to anticipate them, are nonetheless exceptional. For example, you might use a try/catch block to catch unexpected conditions like a full disk or running out of virtual memory:

{
  // perform some normal operations
  // access files
  // allocate memory
  // access databases
  // access third-party libraries
}
catch (CException* e)
{
  ReportError("Performing a useful function", e);
  e->Delete()
}
Normally, the code in the try block runs along fine. In a perfect world, you never execute the code in the catch block. In your world, however, it gets run all too often. In my world, when this code gets run, the user sees the dialog box in Figure 1.

If the user clicks the Print or Send button, the dialog box shown in Figure 2 comes up asking for more information.

When the user hits the OK button, the original dialog box formats a message and either prints it out or sends off a MAPI call to send e-mail to anyone you specify.

The starting point of the system is the ReportError function, which takes a text string and an exception pointer. In practice, the text string should contain a nicely formatted message telling you, the programmer, exactly where the error occurred. I've wrapped some of the more common exceptions because I didn't like the message returned from the default class as provided by Microsoft. For example, the error message returned by CFileException for an end-of-file violation is "endOfFile"; I changed mine to be "The end of file was reached." ReportError() is in Listing 1.

I also wrote a simple overload of ReportError() that takes a string resource id instead of a text string:

void ReportError(UINT nErrorID, CException* e)
{
  CString s;
  s.LoadString(nErrorID);
  ReportError(s,
e);
}
Notice that I'm formatting the error message before I call the Report Error dialog box. You can put anything you want into the dialog box error string. The Dialog box itself doesn't require an exception pointer, which is good since you might want to use the functionality in places where a try/catch block isn't appropriate.

The dialog box template contains a read-only edit control to display the error message. If the message is quite long, a vertical scroll bar appears and the user can scroll down to see the entire text. Here's the header file:

class CReportErrorDlg : public CDialog
{
// Construction
public:=
  // standard
constructor
  CReportErrorDlg(CWnd* pParent = NULL);

// Dialog Data
  //{{AFX_DATA(CReportErrorDlg)
  enum { IDD =
IDD_REPORT_ERROR };
  CButton  m_btnSend;
  CEdit  m_edErrorMessage;
  //}}AFX_DATA
  CString  m_strErrorMessage;
  CString   m_strWhatWereYouDoing;

// Overrides
// ClassWizard
generated virtual function overrides
  //{{AFX_VIRTUAL(CReportErrorDlg)
  protected:
  // DDX/DDV
support
  virtual void
DoDataExchange(CDataExchange* pDX);
  //}}AFX_VIRTUAL

// Implementation protected:

  void
PrepareErrorMessage(CString& s);
  // Generated
message map functions
  //{{AFX_MSG(CReport ErrorDlg)
  virtual BOOL OnInitDialog();
  afx_msg void
OnPrint();
  afx_msg void
OnSend();
  //}}AFX_MSG
  DECLARE_MESSAGE_MAP()
};
The constructor just sets the error message to an empty string:

CReportErrorDlg::CReportErrorDlg(CWnd* pParent 
    /*=NULL*/)
  :
CDialog(CReportErrorDlg::IDD, pParent)
{
  //{{AFX_DATA_INIT(CReportErrorDlg)
  m_strErrorMessage = _T("");
  //}}AFX_DATA_INIT
}
OnInitDialog() handles the real initialization, when the dialog is displayed. Notice the check to see if MAPI is available.

BOOL CReportErrorDlg::OnInitDialog() 
{
  CDialog::OnInitDialog();
  
  // format message for edit control
  CString s;
  for(int i = 0;  
  i <
m_strErrorMessage.GetLength();
  i++)
  {
  if(m_strErrorMessage[i] == '\n')
  s +=
"\r\n";
    else
  s +=
m_strErrorMessage[i];
  }

  m_edErrorMessage.SetWindowText(s);

  // can we send?
  
  BOOL
bMailAvailable = ::GetProfileInt(_T("MAIL"), 
  _T("MAPI"), 
     0) != 0
&&
  SearchPath(NULL, _T("MAPI32.DLL"), 
  NULL, 0,
  NULL, NULL) != 0;

  m_btnSend.EnableWindow(bMailAvailable);

// return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
  return
TRUE;  
}
Set up handlers for the two buttons. I wrote a utility function called PrepareErrorMessage() that-you guessed it-prepares the error message to be e-mailed or faxed:

void CReportErrorDlg::PrepareErrorMessage(CString& s)
{  
  CString ss =
m_strErrorMessage;
  int index =
ss.Find(' ', 80);
  while(index !=
-1)
  {
    s +=
ss.Left(index+1);
    s += '\n';
    ss =
ss.Mid(index);
    index =
ss.Find(' ', 80);
  }
  s += ss;
  
  char
szFileName[_MAX_PATH];
  GetModuleFileName(NULL, szFileName, _MAX_PATH);
  
  s +=
"\n\n";
  s +=
szFileName;
  DWORD dw;
  DWORD size =
GetFileVersionInfoSize(szFileName, 
  &dw);
  char* pVer =
new char[size];
  if(GetFileVersionInfo(szFileName, 0, size, pVer))
  {
    LPVOID
pBuff;
    UINT cbBuff;
  VerQueryValue(pVer, 
  TEXT(
  "\\StringFileInfo\\040904B0\\FileVersion"),
  &pBuff,
  &cbBuff); 
    s +=
"\n";
    s +=
(LPTSTR)pBuff;
  }

  delete [] pVer;


  // Fill
available memory
  MEMORYSTATUS
MemStat;
  MemStat.dwLength = sizeof(MEMORYSTATUS);
  GlobalMemoryStatus(&MemStat);
  CString strFmt,
strMem;
  strFmt.LoadString(CG_IDS_PHYSICAL_MEM);
  strMem.Format(strFmt, MemStat.dwTotalPhys / 1024L);
  s +=
"\n";
  s += strMem;

  // Fill disk
free information
  
  // root path,
could determine from a call to 
  //
GetModuleFileName
  char
*RootPathName = "C:";  
  DWORD
SectorsPerCluster;  // sectors per
cluster
  DWORD
BytesPerSector;     // bytes per
sector
  DWORD
NumberOfFreeClusters;  // free clusters
  DWORD
TotalNumberOfClusters; // total clusters
  if
(GetDiskFreeSpace(RootPathName,
  &SectorsPerCluster,
  &BytesPerSector,
  &NumberOfFreeClusters,
  &TotalNumberOfClusters))
  {
  strFmt.LoadString(CG_IDS_DISK_SPACE);
  strMem.Format(strFmt,
  NumberOfFreeClusters *
  SectorsPerCluster *
  BytesPerSector / 1024L,
  'C');
    s +=
"\n";
    s += strMem;
  }
}
This function just inserts some line breaks (at a space character) in long lines, then gets the application name and version. It finds our how much memory is still available and how much disk space is free. Those are probably two pieces of information you want with every bug report, but you can't count on the user to provide them. You might want to add some more information of your own-for instance, the name and location of vital files, current values of important global variables, the version of the operating system, and/or a list of open files.

To print something you'd like to receive by fax, just bring up a CPrintDialog, create a device context for the printer, set the parts of the DOCINFO structure, then print the string into which the user has confessed their actions.

void CReportErrorDlg::OnPrint() 
{
  CWhatWereYouDoingDlg dlg1(this);
  dlg1.m_strString = m_strWhatWereYouDoing;
  if(dlg1.DoModal() == IDOK)
  {
  m_strWhatWereYouDoing = dlg1.m_strString;
    CPrintDialog
dlg(FALSE,
  PD_NOPAGENUMS|PD_NOSELECTION,
  this);
  if(dlg.DoModal() == IDOK)
    {
  CDC dc;
  dc.Attach(dlg.GetPrinterDC());

  DOCINFO
di;
  di.cbSize
= sizeof(DOCINFO);
  di.lpszDocName = "Error message";
  di.lpszOutput = NULL;
  di.lpszDatatype = NULL;
  di.fwType
= 0;
  dc.StartDoc(&di);
      dc.StartPage();
    
  dc.SetMapMode(MM_HIENGLISH);
    
  CRect
rectPrint(0, 0,
  dc.GetDeviceCaps(HORZRES), 
  dc.GetDeviceCaps(VERTRES));
  dc.DPtoLP(&rectPrint);
  dc.SetWindowOrg(0, -rectPrint.bottom);

  CFont
font;
  VERIFY(font.CreatePointFont(120, 
  "Arial", 
  &dc));

  CFont*
def_font = dc.SelectObject(&font);
  dc.SetTextAlign(TA_TOP|TA_LEFT);
  CString s
= m_strWhatWereYouDoing;
  PrepareErrorMessage(s);

  // make sure there is a trailing newline
  s +=
"\n";
  CString
ss;
  int
index;
  CSize
size;
  int x =
300;
  int y =
9000;
  size =
dc.GetTextExtent("00", 2);
  while((index = s.Find("\n")) != -1)
  {
  ss =
s.Left(index);
  s =
s.Mid(index+1);
  dc.TextOut(x, y, ss);
  y -=
size.cy;
  }
    
    // clean up
  dc.SelectObject(def_font);
  font.DeleteObject();

  dc.EndPage();
  dc.EndDoc();
  DeleteDC(dc.Detach());
    }
  }
}
Sending the report by e-mail isn't much harder. I must confess that I stole most of the MAPI code right out of the MFC source. I like the fact that they do a runtime load of the MAPI library, since not all of the users out there have e-mail loaded on their system. If we had linked MAPI into the program, Windows would look for the MAPI.DLL file and refuse to load the program unless it was located.

CReportErrorDlg::OnSend() 
{
  CWhatWereYouDoingDlg dlg1(this);
  dlg1.m_strString = m_strWhatWereYouDoing;
  if(dlg1.DoModal() == IDOK)
  {
  m_strWhatWereYouDoing = dlg1.m_strString;
    HINSTANCE
hMail = NULL;
    hMail =
::LoadLibraryA("MAPI32.DLL");

    if (hMail ==
NULL)
    {
  AfxMessageBox(AFX_IDP_FAILED_MAPI_LOAD);
  return;
    }
    ASSERT(hMail
!= NULL);

    ULONG
(PASCAL *lpfnSendMail)(ULONG, ULONG, 
  MapiMessage*, 
  FLAGS, ULONG);
  (FARPROC&)lpfnSendMail = GetProcAddress(hMail, 
  "MAPISendMail");
    if
(lpfnSendMail == NULL)
    {
  AfxMessageBox(AFX_IDP_INVALID_MAPI_DLL);
  return;
    }
  ASSERT(lpfnSendMail != NULL);

    // make a recipient
  MapiRecipDesc rec;
  
    char
szRName[] = "Clever Programmer";
    char
szRAddress[] = 
  "SMTP:sendyourerrorsto@yourcompanynotmine.com";
  memset(&rec, 0, sizeof(rec));
  rec.ulRecipClass = MAPI_TO;
    rec.lpszName
= szRName;  
  rec.lpszAddress = szRAddress;
  
    char
szSubject[] = "An exception has occurred";
  
    // prepare the message
------------------------
    
    MapiMessage
message;
  memset(&message, 0, sizeof(message));
    
  message.nRecipCount = 1;
  message.lpRecips = &rec;
  message.lpszSubject = szSubject;

    CString s =
m_strWhatWereYouDoing;
    s +=
"\n";
  PrepareErrorMessage(s);

  message.lpszNoteText = 
  s.GetBuffer(s.GetLength());

    // prepare
for modal dialog box
  AfxGetApp()->EnableModeless(FALSE);
    HWND
hWndTop;
    CWnd* pParentWnd
= CWnd::GetSafeOwner(NULL, 
  &hWndTop);

    // some
extra precautions are required to use
    //
MAPISendMail as it tends to enable the parent
    // window in
between dialogs (after the login
    // dialog,
but before the send not dialog).
  pParentWnd->SetCapture();
  ::SetFocus(NULL);
  pParentWnd->m_nFlags |= WF_STAYDISABLED;



    int nError =
lpfnSendMail(0, 
  (ULONG)pParentWnd->GetSafeHwnd(),
  &message, MAPI_LOGON_UI|MAPI_DIALOG, 0);
  s.ReleaseBuffer();

    // after
returning from the MAPISendMail call, 
    // the
window must be re-enabled and focus 
    // returned
to the frame to undo the workaround
    // done
before the MAPI call.
     ::ReleaseCapture();
  pParentWnd->m_nFlags &= ~WF_STAYDISABLED;

  pParentWnd->EnableWindow(TRUE);
  ::SetActiveWindow(NULL);
  pParentWnd->SetActiveWindow();
  pParentWnd->SetFocus();
    if (hWndTop
!= NULL)

  ::EnableWindow(hWndTop, TRUE);
  AfxGetApp()->EnableModeless(TRUE);

    if (nError
!= SUCCESS_SUCCESS &&
  nError !=
MAPI_USER_ABORT && 
  nError !=
MAPI_E_LOGIN_FAILURE)
    {
  AfxMessageBox(AFX_IDP_FAILED_MAPI_SEND);
    }
    ::FreeLibrary(hMail);
  }
}
Make sure you fix the name and e-mail address to which the bug reports should go. Because the executable might be out in the world for a long time, it's best if this is a fairly generic address (like bugs@yourcompany.com) rather than the personal address of a certain developer.

The call to MAPISendMail() includes the flag MAPI_DIALOG, which brings up the usual new message window, with all of the fields filled in. You can save some code here if you want to just send the message directly. Simply remove the flag and you can cut out the code that disables and re-enables your dialog window. 

So, the next time your user does one of those user things that triggers your exception handler, you'll get the right message in a timely manner. That's almost as good as no errors at all! 

REPORT.ZIP from www.pinpub.com/vcd

Walter Meerschaert has been a programmer with Callan Associates Inc. for the past 11 years. He has written numerous programs including PEP for Windows and EdWin (Electronic Documents for Windows). walterm@callan.com.

Listing 1

. ReportError() converts exceptions to strings.

#include "stdafx.h"
#include "exceptional.h"
#include "ReportErrorDlg.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

void ReportError(LPCSTR szErrorString, CException* e)
{
  ASSERT(szErrorString != NULL || e != NULL);
  CString
sMessage;

  if(szErrorString != NULL)
  {
    sMessage =
szErrorString;
    sMessage +=
'\n';
  }

  if(e != NULL)
  {
  if(e->IsKindOf(RUNTIME_CLASS(CMemoryException)))
    {

  sMessage
+= "Out of memory.";
    }
    else
if(e->IsKindOf(RUNTIME_CLASS(CFileException)))
    {
  switch(((CFileException*)e)->m_cause)
  {
  case
CFileException::none:   
  sMessage += "No error occurred.";
  break;
  case
CFileException::generic:   
  sMessage += "An unspecified error occurred.";
   break;
  case
CFileException::fileNotFound:   
  sMessage += "The file could not be located.";
  break;
  case
CFileException::badPath:   
  sMessage += "All or part of the path is invalid.";
  break;
  case
CFileException::tooManyOpenFiles:   
  sMessage += "The permitted number of open files was
exceeded.";
  break;
  case
CFileException::accessDenied:   
  sMessage += "The file could not be accessed.";
  break;
  case
CFileException::invalidFile:   
  sMessage += "There was an attempt to use an invalid file
handle.";
  break;
  case
CFileException::removeCurrentDir:   
  sMessage += "The current working directory cannot be
removed.";
  break;
  case
CFileException::directoryFull:   
  sMessage += "There are no more directory entries.";
break;
  case
CFileException::badSeek:   
  sMessage += "There was an error trying to set the file
pointer.";
  break;
  case
CFileException::hardIO:   
  sMessage += "There was a hardware error.";
  break;
  case
CFileException::sharingViolation:   
  sMessage += "SHARE.EXE was not loaded, or a shared region was
locked.";
  break;
  case
CFileException::lockViolation:   
sMessage
+= "There was an attempt to lock a region that was already locked.";
  break;
  case
CFileException::diskFull:   
  sMessage += "The disk is full.";
  break;
  case
CFileException::endOfFile:   
  sMessage += "The end of file was reached.";
  break;
  }
    }
  }
  
  CReportErrorDlg
dlg;
  dlg.m_strErrorMessage = sMessage;
  dlg.DoModal();

// if you want to have the debugger kick in whenever
// you get here, uncomment this section
//#ifdef _DEBUG
//  DebugBreak();
//#endif

}
To find out more about Visual C++ Developer and Pinnacle Publishing, visit their website at http://www.pinpub.com/

Note: This is not a Microsoft Corporation website.Microsoft is not responsible for its content.

This article is reproduced from the March 2000 issue of Visual C++ Developer. Copyright 2000, by Pinnacle Publishing, Inc., unless otherwise noted. All rights are reserved. Visual C++ Developer is an independently produced publication of Pinnacle Publishing, Inc. No part of this article may be used or reproduced in any fashion (except in brief quotations used in critical articles and reviews) without prior consent of Pinnacle Publishing, Inc. To contact Pinnacle Publishing, Inc., please call 1-800-788-1900.



--------------------------------------------------------------------------------
Send feedback to MSDN.Look here for MSDN Online resources. 

#9


头疼

#10


不错

#11


我也要一份!
cloudliu2001@sohu.com

#12


各位大师,把答应的代码给我好吗?

#13


socket class

#14


给我一份好吗?  cnt_dave@china.com thanks

#15


用socket自己发吧,codeguru.com有完整的例子

#1


我刚做过这方面的程序,但是由于公司产品保密问题,我只能给你一个大体方向.你可以根据POP3和SMTP协议,做两个类分别用于接收和发送程序.其中在这两个类中定义一个CSocket类成员变量(很重要),用这个成员变量和POP3或SMTP服务器对话,对话顺序根据POP3和SMTP协议.再用这两个类去做邮件接收和发送.我有一个简单的邮件发送程序代码,能满足你所提的要求.你要是需要,给我EMAIL.我的EMAIL地址:steven.t@263.net.

#2


ok,发过去了……

#3


用ShellExecute()或CreateProcess()
调用OutLook就行

#4


给我信箱。我发给你。呵呵。你运气好。我刚用过这样一个类。非常好用。

#5


是一个Smtp的类, 可以附件,不过不能太大,还可以用对方的信箱给对方发信,呵呵。病毒专用版...

#6


To Julienjut(秋水)
如何得到默认邮件程序的位置?

To KissGigi(KissGigi)
a_kun@etang.com,
谢谢大家!

#7


Ask Dr. GUI #41
July/August 1998

I Wanna Send Myself an E-mail . . .
Dear Dr. GUI,

I am trying to send a message using MAPISendMail function.

The program compiles correctly but gives a linker error (MainFrm.obj: error LNK2001: unresolved external symbol _MAPISendMail@20). (I have included the mapi.h header file.) I have checked the lib directory. It does contain mapi.lib and mapi32.lib files. I am using MAPISendMail in an MFCAppWizard-generated project. 

I am using Microsoft Developer Studio and Visual C++ 5.0 on Windows NT 4.0.

Thanks for your help,
Saayan Choudhury

Dr. GUI replies:

If you're not getting enough e-mail, send your own! Actually, sending e-mail to yourself is a great way to test e-mail functionality in your apps-and it keeps you from sending lots of e-mail to others.

The problem is that MAPI32.LIB does not export the Simple MAPI functions. Therefore, in order to use the Simple MAPI functions, you must make a call to LoadLibrary() to load the appropriate DLL (MAPI.DLL or MAPI32.DLL). Then you can call GetProcAddress() to obtain pointers to each of the functions you would like to use.

For more information, please see the topic "Initializing a Simple MAPI Client" in the Microsoft Developer Network Library. For sample code, please see the following article in the Microsoft Knowledge Base: Q171096, "FILE: Simple MAPI Console Application," located at http://support.microsoft.com/support/kb/articles/q171/0/96.asp.

If you're using Microsoft Foundation Classes (MFC) and are trying to send a copy of the current document, note that AppWizard has an option to add a Send command on the File menu. If you forgot, don't despair: there's a MAPI component in the component selection. In version 5.0, you get to this by selecting Add to Project from the Project menu, selecting Components and Controls, and then selecting Developer Studio Components to get to the Components and Controls Gallery. (This is one of the slickest features of Visual C++, and perhaps one of the most underused.)

By the way, if you ever need to know how different AppWizard options generate code, it's easy: just run AppWizard with the different options, and then run WinDiff (supplied with Visual C++) on the projects. The differences are the changes you'll have to apply to your program. Dr. GUI sometimes makes a copy of the AppWizard output for important projects just in case he needs to change some of the AppWizard options.

#8


 
How to Generate Accurate Error Reports Without Relying on Users
Walter Meerschaert

Users are notoriously bad at reporting your own error messages back to you. In this article, Walter Meerschaert provides you with a dialog box for your exception handling code that will allow the user to send an e-mail with vital system and program information. Alternatively, it will also allow users to print out that information to fax it to you if they're living in a stone cave and don't yet have access to e-mail on their system. (If they don't have a printer, you're on your own).

All programmers have this problem: You check your messages and there's an angry call from a frustrated user saying there was a bug in your stupid program and he can't work because of you (!), and by the way the message said something like there was an error or something. Okay, you call back and ask them to repeat the error, starting the error resolution procedure from the time that you actually get the real error message. Wouldn't it be nice if the user had a way to pop off an e-mail right from the place the error occurred? What if the e-mail message had the version of the program and some vital system statistics? That would probably speed the process along considerably. In fact, you might be calling the user back with instructions on how to download a new version of your software with the problem already solved, instead of asking for more information. 

Exception handlers
An exception handler is the place in your code where you handle conditions and events that, while you had the foresight to anticipate them, are nonetheless exceptional. For example, you might use a try/catch block to catch unexpected conditions like a full disk or running out of virtual memory:

{
  // perform some normal operations
  // access files
  // allocate memory
  // access databases
  // access third-party libraries
}
catch (CException* e)
{
  ReportError("Performing a useful function", e);
  e->Delete()
}
Normally, the code in the try block runs along fine. In a perfect world, you never execute the code in the catch block. In your world, however, it gets run all too often. In my world, when this code gets run, the user sees the dialog box in Figure 1.

If the user clicks the Print or Send button, the dialog box shown in Figure 2 comes up asking for more information.

When the user hits the OK button, the original dialog box formats a message and either prints it out or sends off a MAPI call to send e-mail to anyone you specify.

The starting point of the system is the ReportError function, which takes a text string and an exception pointer. In practice, the text string should contain a nicely formatted message telling you, the programmer, exactly where the error occurred. I've wrapped some of the more common exceptions because I didn't like the message returned from the default class as provided by Microsoft. For example, the error message returned by CFileException for an end-of-file violation is "endOfFile"; I changed mine to be "The end of file was reached." ReportError() is in Listing 1.

I also wrote a simple overload of ReportError() that takes a string resource id instead of a text string:

void ReportError(UINT nErrorID, CException* e)
{
  CString s;
  s.LoadString(nErrorID);
  ReportError(s,
e);
}
Notice that I'm formatting the error message before I call the Report Error dialog box. You can put anything you want into the dialog box error string. The Dialog box itself doesn't require an exception pointer, which is good since you might want to use the functionality in places where a try/catch block isn't appropriate.

The dialog box template contains a read-only edit control to display the error message. If the message is quite long, a vertical scroll bar appears and the user can scroll down to see the entire text. Here's the header file:

class CReportErrorDlg : public CDialog
{
// Construction
public:=
  // standard
constructor
  CReportErrorDlg(CWnd* pParent = NULL);

// Dialog Data
  //{{AFX_DATA(CReportErrorDlg)
  enum { IDD =
IDD_REPORT_ERROR };
  CButton  m_btnSend;
  CEdit  m_edErrorMessage;
  //}}AFX_DATA
  CString  m_strErrorMessage;
  CString   m_strWhatWereYouDoing;

// Overrides
// ClassWizard
generated virtual function overrides
  //{{AFX_VIRTUAL(CReportErrorDlg)
  protected:
  // DDX/DDV
support
  virtual void
DoDataExchange(CDataExchange* pDX);
  //}}AFX_VIRTUAL

// Implementation protected:

  void
PrepareErrorMessage(CString& s);
  // Generated
message map functions
  //{{AFX_MSG(CReport ErrorDlg)
  virtual BOOL OnInitDialog();
  afx_msg void
OnPrint();
  afx_msg void
OnSend();
  //}}AFX_MSG
  DECLARE_MESSAGE_MAP()
};
The constructor just sets the error message to an empty string:

CReportErrorDlg::CReportErrorDlg(CWnd* pParent 
    /*=NULL*/)
  :
CDialog(CReportErrorDlg::IDD, pParent)
{
  //{{AFX_DATA_INIT(CReportErrorDlg)
  m_strErrorMessage = _T("");
  //}}AFX_DATA_INIT
}
OnInitDialog() handles the real initialization, when the dialog is displayed. Notice the check to see if MAPI is available.

BOOL CReportErrorDlg::OnInitDialog() 
{
  CDialog::OnInitDialog();
  
  // format message for edit control
  CString s;
  for(int i = 0;  
  i <
m_strErrorMessage.GetLength();
  i++)
  {
  if(m_strErrorMessage[i] == '\n')
  s +=
"\r\n";
    else
  s +=
m_strErrorMessage[i];
  }

  m_edErrorMessage.SetWindowText(s);

  // can we send?
  
  BOOL
bMailAvailable = ::GetProfileInt(_T("MAIL"), 
  _T("MAPI"), 
     0) != 0
&&
  SearchPath(NULL, _T("MAPI32.DLL"), 
  NULL, 0,
  NULL, NULL) != 0;

  m_btnSend.EnableWindow(bMailAvailable);

// return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
  return
TRUE;  
}
Set up handlers for the two buttons. I wrote a utility function called PrepareErrorMessage() that-you guessed it-prepares the error message to be e-mailed or faxed:

void CReportErrorDlg::PrepareErrorMessage(CString& s)
{  
  CString ss =
m_strErrorMessage;
  int index =
ss.Find(' ', 80);
  while(index !=
-1)
  {
    s +=
ss.Left(index+1);
    s += '\n';
    ss =
ss.Mid(index);
    index =
ss.Find(' ', 80);
  }
  s += ss;
  
  char
szFileName[_MAX_PATH];
  GetModuleFileName(NULL, szFileName, _MAX_PATH);
  
  s +=
"\n\n";
  s +=
szFileName;
  DWORD dw;
  DWORD size =
GetFileVersionInfoSize(szFileName, 
  &dw);
  char* pVer =
new char[size];
  if(GetFileVersionInfo(szFileName, 0, size, pVer))
  {
    LPVOID
pBuff;
    UINT cbBuff;
  VerQueryValue(pVer, 
  TEXT(
  "\\StringFileInfo\\040904B0\\FileVersion"),
  &pBuff,
  &cbBuff); 
    s +=
"\n";
    s +=
(LPTSTR)pBuff;
  }

  delete [] pVer;


  // Fill
available memory
  MEMORYSTATUS
MemStat;
  MemStat.dwLength = sizeof(MEMORYSTATUS);
  GlobalMemoryStatus(&MemStat);
  CString strFmt,
strMem;
  strFmt.LoadString(CG_IDS_PHYSICAL_MEM);
  strMem.Format(strFmt, MemStat.dwTotalPhys / 1024L);
  s +=
"\n";
  s += strMem;

  // Fill disk
free information
  
  // root path,
could determine from a call to 
  //
GetModuleFileName
  char
*RootPathName = "C:";  
  DWORD
SectorsPerCluster;  // sectors per
cluster
  DWORD
BytesPerSector;     // bytes per
sector
  DWORD
NumberOfFreeClusters;  // free clusters
  DWORD
TotalNumberOfClusters; // total clusters
  if
(GetDiskFreeSpace(RootPathName,
  &SectorsPerCluster,
  &BytesPerSector,
  &NumberOfFreeClusters,
  &TotalNumberOfClusters))
  {
  strFmt.LoadString(CG_IDS_DISK_SPACE);
  strMem.Format(strFmt,
  NumberOfFreeClusters *
  SectorsPerCluster *
  BytesPerSector / 1024L,
  'C');
    s +=
"\n";
    s += strMem;
  }
}
This function just inserts some line breaks (at a space character) in long lines, then gets the application name and version. It finds our how much memory is still available and how much disk space is free. Those are probably two pieces of information you want with every bug report, but you can't count on the user to provide them. You might want to add some more information of your own-for instance, the name and location of vital files, current values of important global variables, the version of the operating system, and/or a list of open files.

To print something you'd like to receive by fax, just bring up a CPrintDialog, create a device context for the printer, set the parts of the DOCINFO structure, then print the string into which the user has confessed their actions.

void CReportErrorDlg::OnPrint() 
{
  CWhatWereYouDoingDlg dlg1(this);
  dlg1.m_strString = m_strWhatWereYouDoing;
  if(dlg1.DoModal() == IDOK)
  {
  m_strWhatWereYouDoing = dlg1.m_strString;
    CPrintDialog
dlg(FALSE,
  PD_NOPAGENUMS|PD_NOSELECTION,
  this);
  if(dlg.DoModal() == IDOK)
    {
  CDC dc;
  dc.Attach(dlg.GetPrinterDC());

  DOCINFO
di;
  di.cbSize
= sizeof(DOCINFO);
  di.lpszDocName = "Error message";
  di.lpszOutput = NULL;
  di.lpszDatatype = NULL;
  di.fwType
= 0;
  dc.StartDoc(&di);
      dc.StartPage();
    
  dc.SetMapMode(MM_HIENGLISH);
    
  CRect
rectPrint(0, 0,
  dc.GetDeviceCaps(HORZRES), 
  dc.GetDeviceCaps(VERTRES));
  dc.DPtoLP(&rectPrint);
  dc.SetWindowOrg(0, -rectPrint.bottom);

  CFont
font;
  VERIFY(font.CreatePointFont(120, 
  "Arial", 
  &dc));

  CFont*
def_font = dc.SelectObject(&font);
  dc.SetTextAlign(TA_TOP|TA_LEFT);
  CString s
= m_strWhatWereYouDoing;
  PrepareErrorMessage(s);

  // make sure there is a trailing newline
  s +=
"\n";
  CString
ss;
  int
index;
  CSize
size;
  int x =
300;
  int y =
9000;
  size =
dc.GetTextExtent("00", 2);
  while((index = s.Find("\n")) != -1)
  {
  ss =
s.Left(index);
  s =
s.Mid(index+1);
  dc.TextOut(x, y, ss);
  y -=
size.cy;
  }
    
    // clean up
  dc.SelectObject(def_font);
  font.DeleteObject();

  dc.EndPage();
  dc.EndDoc();
  DeleteDC(dc.Detach());
    }
  }
}
Sending the report by e-mail isn't much harder. I must confess that I stole most of the MAPI code right out of the MFC source. I like the fact that they do a runtime load of the MAPI library, since not all of the users out there have e-mail loaded on their system. If we had linked MAPI into the program, Windows would look for the MAPI.DLL file and refuse to load the program unless it was located.

CReportErrorDlg::OnSend() 
{
  CWhatWereYouDoingDlg dlg1(this);
  dlg1.m_strString = m_strWhatWereYouDoing;
  if(dlg1.DoModal() == IDOK)
  {
  m_strWhatWereYouDoing = dlg1.m_strString;
    HINSTANCE
hMail = NULL;
    hMail =
::LoadLibraryA("MAPI32.DLL");

    if (hMail ==
NULL)
    {
  AfxMessageBox(AFX_IDP_FAILED_MAPI_LOAD);
  return;
    }
    ASSERT(hMail
!= NULL);

    ULONG
(PASCAL *lpfnSendMail)(ULONG, ULONG, 
  MapiMessage*, 
  FLAGS, ULONG);
  (FARPROC&)lpfnSendMail = GetProcAddress(hMail, 
  "MAPISendMail");
    if
(lpfnSendMail == NULL)
    {
  AfxMessageBox(AFX_IDP_INVALID_MAPI_DLL);
  return;
    }
  ASSERT(lpfnSendMail != NULL);

    // make a recipient
  MapiRecipDesc rec;
  
    char
szRName[] = "Clever Programmer";
    char
szRAddress[] = 
  "SMTP:sendyourerrorsto@yourcompanynotmine.com";
  memset(&rec, 0, sizeof(rec));
  rec.ulRecipClass = MAPI_TO;
    rec.lpszName
= szRName;  
  rec.lpszAddress = szRAddress;
  
    char
szSubject[] = "An exception has occurred";
  
    // prepare the message
------------------------
    
    MapiMessage
message;
  memset(&message, 0, sizeof(message));
    
  message.nRecipCount = 1;
  message.lpRecips = &rec;
  message.lpszSubject = szSubject;

    CString s =
m_strWhatWereYouDoing;
    s +=
"\n";
  PrepareErrorMessage(s);

  message.lpszNoteText = 
  s.GetBuffer(s.GetLength());

    // prepare
for modal dialog box
  AfxGetApp()->EnableModeless(FALSE);
    HWND
hWndTop;
    CWnd* pParentWnd
= CWnd::GetSafeOwner(NULL, 
  &hWndTop);

    // some
extra precautions are required to use
    //
MAPISendMail as it tends to enable the parent
    // window in
between dialogs (after the login
    // dialog,
but before the send not dialog).
  pParentWnd->SetCapture();
  ::SetFocus(NULL);
  pParentWnd->m_nFlags |= WF_STAYDISABLED;



    int nError =
lpfnSendMail(0, 
  (ULONG)pParentWnd->GetSafeHwnd(),
  &message, MAPI_LOGON_UI|MAPI_DIALOG, 0);
  s.ReleaseBuffer();

    // after
returning from the MAPISendMail call, 
    // the
window must be re-enabled and focus 
    // returned
to the frame to undo the workaround
    // done
before the MAPI call.
     ::ReleaseCapture();
  pParentWnd->m_nFlags &= ~WF_STAYDISABLED;

  pParentWnd->EnableWindow(TRUE);
  ::SetActiveWindow(NULL);
  pParentWnd->SetActiveWindow();
  pParentWnd->SetFocus();
    if (hWndTop
!= NULL)

  ::EnableWindow(hWndTop, TRUE);
  AfxGetApp()->EnableModeless(TRUE);

    if (nError
!= SUCCESS_SUCCESS &&
  nError !=
MAPI_USER_ABORT && 
  nError !=
MAPI_E_LOGIN_FAILURE)
    {
  AfxMessageBox(AFX_IDP_FAILED_MAPI_SEND);
    }
    ::FreeLibrary(hMail);
  }
}
Make sure you fix the name and e-mail address to which the bug reports should go. Because the executable might be out in the world for a long time, it's best if this is a fairly generic address (like bugs@yourcompany.com) rather than the personal address of a certain developer.

The call to MAPISendMail() includes the flag MAPI_DIALOG, which brings up the usual new message window, with all of the fields filled in. You can save some code here if you want to just send the message directly. Simply remove the flag and you can cut out the code that disables and re-enables your dialog window. 

So, the next time your user does one of those user things that triggers your exception handler, you'll get the right message in a timely manner. That's almost as good as no errors at all! 

REPORT.ZIP from www.pinpub.com/vcd

Walter Meerschaert has been a programmer with Callan Associates Inc. for the past 11 years. He has written numerous programs including PEP for Windows and EdWin (Electronic Documents for Windows). walterm@callan.com.

Listing 1

. ReportError() converts exceptions to strings.

#include "stdafx.h"
#include "exceptional.h"
#include "ReportErrorDlg.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

void ReportError(LPCSTR szErrorString, CException* e)
{
  ASSERT(szErrorString != NULL || e != NULL);
  CString
sMessage;

  if(szErrorString != NULL)
  {
    sMessage =
szErrorString;
    sMessage +=
'\n';
  }

  if(e != NULL)
  {
  if(e->IsKindOf(RUNTIME_CLASS(CMemoryException)))
    {

  sMessage
+= "Out of memory.";
    }
    else
if(e->IsKindOf(RUNTIME_CLASS(CFileException)))
    {
  switch(((CFileException*)e)->m_cause)
  {
  case
CFileException::none:   
  sMessage += "No error occurred.";
  break;
  case
CFileException::generic:   
  sMessage += "An unspecified error occurred.";
   break;
  case
CFileException::fileNotFound:   
  sMessage += "The file could not be located.";
  break;
  case
CFileException::badPath:   
  sMessage += "All or part of the path is invalid.";
  break;
  case
CFileException::tooManyOpenFiles:   
  sMessage += "The permitted number of open files was
exceeded.";
  break;
  case
CFileException::accessDenied:   
  sMessage += "The file could not be accessed.";
  break;
  case
CFileException::invalidFile:   
  sMessage += "There was an attempt to use an invalid file
handle.";
  break;
  case
CFileException::removeCurrentDir:   
  sMessage += "The current working directory cannot be
removed.";
  break;
  case
CFileException::directoryFull:   
  sMessage += "There are no more directory entries.";
break;
  case
CFileException::badSeek:   
  sMessage += "There was an error trying to set the file
pointer.";
  break;
  case
CFileException::hardIO:   
  sMessage += "There was a hardware error.";
  break;
  case
CFileException::sharingViolation:   
  sMessage += "SHARE.EXE was not loaded, or a shared region was
locked.";
  break;
  case
CFileException::lockViolation:   
sMessage
+= "There was an attempt to lock a region that was already locked.";
  break;
  case
CFileException::diskFull:   
  sMessage += "The disk is full.";
  break;
  case
CFileException::endOfFile:   
  sMessage += "The end of file was reached.";
  break;
  }
    }
  }
  
  CReportErrorDlg
dlg;
  dlg.m_strErrorMessage = sMessage;
  dlg.DoModal();

// if you want to have the debugger kick in whenever
// you get here, uncomment this section
//#ifdef _DEBUG
//  DebugBreak();
//#endif

}
To find out more about Visual C++ Developer and Pinnacle Publishing, visit their website at http://www.pinpub.com/

Note: This is not a Microsoft Corporation website.Microsoft is not responsible for its content.

This article is reproduced from the March 2000 issue of Visual C++ Developer. Copyright 2000, by Pinnacle Publishing, Inc., unless otherwise noted. All rights are reserved. Visual C++ Developer is an independently produced publication of Pinnacle Publishing, Inc. No part of this article may be used or reproduced in any fashion (except in brief quotations used in critical articles and reviews) without prior consent of Pinnacle Publishing, Inc. To contact Pinnacle Publishing, Inc., please call 1-800-788-1900.



--------------------------------------------------------------------------------
Send feedback to MSDN.Look here for MSDN Online resources. 

#9


头疼

#10


不错

#11


我也要一份!
cloudliu2001@sohu.com

#12


各位大师,把答应的代码给我好吗?

#13


socket class

#14


给我一份好吗?  cnt_dave@china.com thanks

#15


用socket自己发吧,codeguru.com有完整的例子