Artifact 5c7483c15eb1be36d937c6f3042677d23d3bceed561f8f7baace21ad591f962c:
- Executable file
r36/cslbase/c_applic.cpp
— part of check-in
[f2fda60abd]
at
2011-09-02 18:13:33
on branch master
— Some historical releases purely for archival purposes
git-svn-id: https://svn.code.sf.net/p/reduce-algebra/code/trunk/historical@1375 2bfe0521-f11c-4a00-b80e-6202646ff360 (user: arthurcnorman@users.sourceforge.net, size: 25576) [annotate] [blame] [check-ins using] [more...]
// c_applic.cpp // The "application" part of my program. This is concerned with // initial creation of windows and the capture of any command-line // or other options. // // Copyright (C)/©/¸ Codemist Ltd, 1995-96 /* Signature: 28af1293 30-Nov-1997 */ #include "cwin.hpp" extern "C" { extern int Lstop(int a, int b); } // // Windows enters my code at WinMain, but with MFC I arrive via all sorts // of jolly initialisation code. To support old code I read the command // line that invoked me, and parse it into words which I store in argv[], // much as per the regular C startup process. // char programName[64]; char *cwin_full_program_name; static int argc; static char **argv; static int set_up_argv() // This sets up argc and argv[] as expected for a regular C application. // It arranges that argv[0] is an unqualified name, forced into upper case. // Ie argv[0] does not have a path-name on the front of it or any ".EXE" // suffix after it. // I return a flag that indicates whether "--" was found among the arguments. // (and now I will return the same flag if "-f" was among the arguments) { int doubledashFound = 0; int i = 0, c, len = 0; char *w = GetCommandLine(); // The string I obtained there may be in UniCode, but I will suppose that it // does not contain any funny characters. In particular I will demand // that this module is compiled with Unicode mapped onto ordinary 8-bit // chars. char *w1 = w, *argbuf; // I scan the command line once to assess its length. I treat any item // that STARTS with a double-quote mark as running on until the following // double quote (even if there are blanks in the way), and in that case I // remove the quotes before passing on to the user's program. Something // like this appears to be essential for dealing with Windows-95 with // its long filenames: it puts the first item on the command line (the name // of the program that is being executed) in double quotes in case it is long // and in case it has embedded whitespace, and unless I strip those quotes // later on bits of code crash. Note now the subtle distinction between // -W "yy" // and -W"yy" // where -W is some option that user code tries to interpret. In the first // case the quotes are gobbled up and removed here, in the second they // remain for the user to see. Note that // -W"yy is here" // returns three words, two of which have embedded quote marks. It is probably // impossible to guarantee to win all cases here! do { while ((c = *w++) == ' ' || c == '\t');// Blank at start of an item if (c == 0) break; i++; // Count of words if (c == '"') // items in double quotes? { c = *w++; while (c != 0 && c != '"') c = *w++, len++; if (c == '"') c = *w++; } else while (c != 0 && c != ' ' && c != '\t') c = *w++, len++; } while (c != 0); // Now I can allocate space for the argument vector and a copy of the data. // I grab a little more space than I am going to use as a matter of caution. argv = (char **)malloc((i+1)*sizeof(char *)); argbuf = (char *)malloc(i+len); argc = 0; if (argv==NULL || argbuf==NULL) return 0; // Re-scan the command line copying characters into buffers w = w1; do { while ((c = *w++) == ' ' || c == '\t'); if (c == 0) break; argv[argc++] = argbuf; if (c == '"') // I strip the quotes while I tokenise { c = *w++; while (c != 0 && c != '"') *argbuf++ = c, c = *w++; if (c == '"') c = *w++; } else while (c != 0 && c != ' ' && c != '\t') *argbuf++ = c, c = *w++; *argbuf++ = 0; if (argv[argc-1][0] == '-' && (argv[argc-1][1] == '-' || argv[argc-1][1] == 'f' || argv[argc-1][1] == 'F')) doubledashFound = 1; } while (c != 0); // Put a NULL pointer at the end of argv[], just to be safe argv[argc] = NULL; // Now I want to trim argv[0] so that even if it started with a full // path or with an extension (eg. "\bin\csl.exe") it is passed on trimmed // down to just its root (eg. "csl" in the above case). This string will // be left in programName too. w = w1 = argv[0]; cwin_full_program_name = NULL; while ((c = *w++) != 0) { if (c == '\\') w1 = w; // I take the view that if argv[0] contains a ":" character then it can be // presumed to be a fully rooted file name, including a drive specification. // In such cases I will use it when I want the full name of the executable // I am running. Well I will also require in that case that it should end // in a ".exe" suffix. This final test is certainly needed under Windows // NT 4.0 when one launches CSL from a command line but specifying // a drive to find it on. What I want is the text // "D:\xxxx.exe" else if (c == ':' && *w=='\\') { i = strlen(w)-4; if (i > 0 && w[i]=='.' && tolower(w[i+1])=='e' && tolower(w[i+2])=='x' && tolower(w[i+3])=='e') cwin_full_program_name = argv[0]; } } if (*w1 == 0) w1 = "CWIN"; // Final char of argv[0] was '\': use default w = programName; while ((c = *w1++) != 0 && c != '.') *w++ = toupper(c); *w = 0; argv[0] = programName; if (cwin_full_program_name == NULL) // Now I would like to get a full path to the program name... The // SearchPath function looks first in the directory from which the // application was fetched, and provided that the ".exe" extension // I specify here is correct the file really ought to be located! { int nameLength = SearchPath(NULL, programName, ".EXE", 0, argbuf, &w); // There is one critically important case where "SearchPath" will fail here, // and that is when the program has been started from a debugger and the // real name of the program is just not available. In that case tough // luck, you will have to make resources available by some means NOT // dependent on the program name or the directory it lives in. Maybe in some // cases with DOS extenders the program will appear to have been loaded from // a directory distinct from the one that the obvious ".EXE" file lives in. // In those cases I had better hope that argv[0] gave me a completely // rooted file name. cwin_full_program_name = (char *)malloc(nameLength+1); if (cwin_full_program_name == NULL) cwin_full_program_name = "cwin.exe"; else { if (SearchPath(NULL, programName, ".EXE", nameLength+1, cwin_full_program_name, &w) == 0) cwin_full_program_name = "cwin.exe"; } } return doubledashFound; } CTheApp theApp; CString mainWindowClass; UINT clipboardformat; BOOL CTheApp::InitInstance() { // I find the explanations about m_nCmdShow and GetStartupInfo jolly // confusing! However the code as given here will be tested with CSL launched // from a command line with // start csl ... // start /min csl ... // start /max csl ... // and start csl -- logfile.log // // The last of these ought to start CSL minimised even if /min or // /max is given as well. // // Initial testing is on NT. Next I will need to try Windows 95. The // issue of win32s (on Windows 3.1x) will be gently ignored now. int nShow = m_nCmdShow; #ifdef __WATCOM_CPLUSPLUS__ #if __WATCOM_CPLUSPLUS__ > 1060 // Oh calamity. The following 3 lines seem necessary to make the initial // window behave properly, but when I inserted them I had reports that the // code would not link on a system with just the previous version of // Watcom C. While I investigate this I will try to disable this facility // unless I have version 11.0 installed, and maybe this will allow us to // keep moving forward. STARTUPINFO su; GetStartupInfo(&su); if (su.dwFlags & 1) nShow = su.wShowWindow; #endif #endif // I will grab information out of the registry as soon as the application // is started. SetRegistryKey("Codemist"); // Use registry rather than ".ini" file int left = GetProfileInt("MainWindow", "ScreenLeft", -1000000); int width = GetProfileInt("MainWindow", "ScreenWidth", -1000000); int top = GetProfileInt("MainWindow", "ScreenTop", -1000000); int height = GetProfileInt("MainWindow", "ScreenHeight", -1000000); int fsize = GetProfileInt("MainWindow", "FontSize", 0); int fweight= GetProfileInt("MainWindow", "FontWeight", 0); int linel = GetProfileInt("MainWindow", "LineLength", -1000000); CString fname(GetProfileString("MainWindow", "FontName", "Courier New")); int doubledashFound = set_up_argv(); mainWindow = NULL; mainWindowClass = ::AfxRegisterWndClass(CS_DBLCLKS, LoadStandardCursor(IDC_ARROW), (HBRUSH)(COLOR_WINDOW+1), LoadIcon("CWIN")); /* * I introduce a private clipboard format here. It will be just like * simple text (to start with) except that certain control characters * will be used to separate off the places that prompt strings occus. This * can be exploited in PASTE operations so that prompts issued by this * system do not get re-entered when previous input is copies and pasted. */ clipboardformat = RegisterClipboardFormat("Codemist Text"); /* * clipboardformat is left zero if anything went wrong, so in such cases * I must not attempt to use it. */ if ((mainWindow = new CMainWindow()) == NULL) return FALSE; m_pMainWnd = mainWindow; CClientDC dc(mainWindow); mainWindow->windowFonts.InitFont(&dc, (LPCTSTR)fname, fweight, fsize); // If there was "--" given as an argument I start off with the window // minimized. This is because I then expect an application to use this // flag to enable output to a file rather than to the screen. WINDOWPLACEMENT wp; mainWindow->GetWindowPlacement(&wp); // Here I will see where the window is about to be placed, and adjust its // width (and maybe its left hand side) in an attempt to make the client // are just big enough for 80 columns. I am a bit unhappy about the // calculation here using system metrics, and have added in one more // CXBORDER as a fudge to bring experimental reality on MY system into // line. Hope it is OK on other systems and configurations too. // The "+5" on the end is an attempt to leave room for a caret to the right // of the last sensible character on a line... if (left!=-1000000 && width!=-1000000 && top!=-1000000 && height!=-1000000 && linel!=-1000000) { mainWindow->SetWindowPos(NULL, left, top, width, height, SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOZORDER); cwin_linelength = linel; } else { int screenWidth = GetSystemMetrics(SM_CXSCREEN); RECT *wr = &wp.rcNormalPosition; int left = wr->left; int cwidth = 80*mainWindow->windowFonts.HCourier.across['X'] + 3*GetSystemMetrics(SM_CXBORDER) + 2*GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(SM_CXVSCROLL) + 5; // Try to get the whole window onto the screen. if (left + cwidth > screenWidth) { left = screenWidth - cwidth; if (left < 0) left = 0; } mainWindow->SetWindowPos(NULL, left, wr->top, cwidth, (wr->bottom - wr->top), SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOZORDER); cwin_linelength = 80; WriteProfileInt("MainWindow", "ScreenLeft", left); WriteProfileInt("MainWindow", "ScreenWidth", cwidth); WriteProfileInt("MainWindow", "ScreenTop", wr->top); WriteProfileInt("MainWindow", "ScreenHeight", wr->bottom-wr->top); WriteProfileInt("MainWindow", "LineLength", cwin_linelength); } mainWindow->ShowWindow(doubledashFound ? SW_SHOWMINNOACTIVE : nShow); mainWindow->UpdateWindow(); return TRUE; } class CenteredDialogBox : public CDialog { public: void CreateAndDisplay(HGLOBAL h); }; void CenteredDialogBox::CreateAndDisplay(HGLOBAL h) { InitModalIndirect(h); DoModal(); } // In-store dialog-box templates need some of their strings in 16-bit // Unicode form. This code stretches out a simple string. It also round // the length of the data written to a multiple of 8 bytes, which seems to // be an unpublished (?) requirement for the dialog box template structures. static LPWORD WidenString(LPWORD p, char *q) { int n = 0; while ((*p++ = *q++) != 0) n++; if (n & 1) *p++ = 0; return p; } // The following function fills in details about one control within a // dialog box template. static LPWORD PlantDlgItem(LPWORD p3, int x, int y, int cx, int cy, int id, DWORD style, int type, char *text) { LPDLGITEMTEMPLATE p2 = (LPDLGITEMTEMPLATE)p3; p2->x = x; p2->y = y; p2->cx = cx, p2->cy = cy; p2->id = id; p2->style = style; p3 = (LPWORD)(p2 + 1); *p3++ = 0xffff; *p3++ = type; int n = 1; while ((*p3++ = *text++) != 0) n++; if (n & 1) *p3++ = 0; *p3++ = 0; return p3; } // I make the "ABOUT" dialog box from an in-memory template, and // this makes it possible to make the text that is included depend on // strings that the user can potentially reconfigure. The strings put here // are somewhat generic. Note also that if a user hits the HELP menu // during system start-up before the regular user code at main() has been // entered than the messages shown here will appear, even though later on // the user's properly selected messages will be the ones that show up. I // think that on balance I almost count that to be a positive advantage! It // means that the "CWIN" information and credits are at least just about // available to all users! char about_box_title[32] = "About CWIN"; char about_box_description[32] = "The CWIN window driver"; char about_box_rights_1[32] = "Copyright Codemist Ltd."; char about_box_rights_2[32] = "A C Norman 1994-6"; void CTheApp::OnAbout() { HGLOBAL h = GlobalAlloc(GMEM_ZEROINIT, 1024); if (!h) return; LPDLGTEMPLATE p1 = (LPDLGTEMPLATE)GlobalLock(h); WORD *p0 = (WORD *)p1; p1->style = WS_POPUP | WS_CAPTION | WS_SYSMENU | DS_MODALFRAME; p1->cdit = 5; p1->cx = 167; p1->cy = 86; LONG units = ::GetDialogBaseUnits(); int dlgx = units & 0xffff, dlgy = (units >> 16) & 0xffff; p1->x = ((4*mainWindow->clientWidth)/dlgx - p1->cx)/2; p1->y = ((8*mainWindow->clientHeight)/dlgy - p1->cy)/2; LPWORD p2 = (LPWORD)(p1 + 1); *p2++ = 0; // no menu *p2++ = 0; // a predefined box class p2 = WidenString(p2, about_box_title); p2 = PlantDlgItem(p2, 0, 4, 167, 8, -1, WS_CHILD | WS_VISIBLE | SS_CENTER, 0x0082, about_box_description); p2 = PlantDlgItem(p2, 0, 45, 167, 8, -1, WS_CHILD | WS_VISIBLE | SS_CENTER, 0x0082, about_box_rights_1); p2 = PlantDlgItem(p2, 0, 53, 167, 8, -1, WS_CHILD | WS_VISIBLE | SS_CENTER, 0x0082, about_box_rights_2); p2 = PlantDlgItem(p2, 74, 22, 0, 0, -1, WS_CHILD | WS_VISIBLE | SS_ICON, 0x0082, "CWIN"); p2 = PlantDlgItem(p2, 66, 65, 32, 14, IDOK, WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 0x0080, "OK"); GlobalUnlock(h); CenteredDialogBox dlg; dlg.CreateAndDisplay(h); GlobalFree(h); } // When I want to pop up a box that says "Press OK to exit" I make the // structure that defines the dialog box here in memory rather than putting // it into my resource file. The reason for taking this step is that it // allows to to keep the resource file as spartan and simple as possible. // It also provides a place for me to ensure that the dialog box is central // in the area that my window occupies. static void DoFinishBox() { HGLOBAL h = GlobalAlloc(GMEM_ZEROINIT, 1024); if (!h) return; LPDLGTEMPLATE p1 = (LPDLGTEMPLATE)GlobalLock(h); WORD *p0 = (WORD *)p1; //DEBUG p1->style = WS_POPUP | WS_CAPTION | WS_SYSMENU | DS_MODALFRAME; p1->cdit = 2; p1->cx = 95; p1->cy = 52; // I want the box to appear in the centre of where my window is. This // causes extra fun because of the special coordinate system used with // dialog boxes - I have to convert units. LONG units = ::GetDialogBaseUnits(); int dlgx = units & 0xffff, dlgy = (units >> 16) & 0xffff; p1->x = ((4*theApp.mainWindow->clientWidth)/dlgx - p1->cx)/2; p1->y = ((8*theApp.mainWindow->clientHeight)/dlgy - p1->cy)/2; LPWORD p2 = (LPWORD)(p1 + 1); *p2++ = 0; // no menu *p2++ = 0; // a predefined box class *p2++ = 0; // no title p2 = PlantDlgItem(p2, 1, 10, 94, 12, -1, WS_CHILD | WS_VISIBLE | SS_CENTER, 0x0082, "Press OK to exit"); p2 = PlantDlgItem(p2, 28, 23, 40, 14, IDOK, WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 0x0080, "OK"); GlobalUnlock(h); CenteredDialogBox dlg; dlg.CreateAndDisplay(h); GlobalFree(h); } // Here I override the Run member of my application so that I take control // of the way in which the underlying window system is polled. This is // somewhat delicate! The code here has to be a close enough shadow of // what MFC does that I do not cause conflict. int cwin_pause_at_end; int CTheApp::Run() // Main running routine until application exits { // If I had not managed to open a main window then I should give up. if (m_pMainWnd == NULL // && AfxOleGetUserCtrl() // Embedding or Automation invocation? ) AfxPostQuitMessage(0); // The message handler needs to access m_msgCur which is a private member // of the class, so I can not use just (theApp.m_msgCur) to get at it. To // work around the problem I just dump a reference to it in the variable // msgPtr. msgPtr = &m_msgCur; // Now the real fun! I call cwin_main() which fires up my application code // Remember that cwin_main() should either return to me or do a cwin_exit() // and it should NOT call exit(). cwin_pause_at_end = FALSE; int returnCode; // try { returnCode = cwin_main(argc, argv); // } // catch (int rc) { returnCode = rc; } if (cwin_pause_at_end) { cwin_maximize(); cwin_ensure_screen(); DoFinishBox(); } if (m_pMainWnd != NULL) delete m_pMainWnd; m_pMainWnd = mainWindow = NULL; int returnCode1 = ExitInstance(); if (returnCode1 > returnCode) returnCode = returnCode1; if (m_pMainWnd != NULL) m_pMainWnd->SendMessage(WM_CLOSE); // Although I go to some trouble to collect a return code here I find such // things somewhat unhelpful under Windows, and so at the last minute I // check away the information and return zero. return 0; // returnCode; } //void cwin_exit(int r) //{ // throw r; //} BEGIN_MESSAGE_MAP(CTheApp, CWinApp) ON_COMMAND(IDM_HELPCONTENTS, OnHelpContents) ON_COMMAND(IDM_HELPSEARCH, OnHelpSearch) ON_COMMAND(IDM_HELP_ON_HELP, OnHelpUsing) ON_COMMAND(IDM_ABOUT, OnAbout) #ifdef DEMOVERSION ON_COMMAND(IDM_EUPRICES, OnEUPrices) ON_COMMAND(IDM_WORLDPRICES, OnWorldPrices) ON_COMMAND(IDM_ORDERFORM, OnOrderform) #endif ON_COMMAND_RANGE(IDM_DYNAMIC_ITEMS, IDM_LAST_DYNAMIC, OnDynamic) END_MESSAGE_MAP() // At various times I will want to go back and poll the window manager // to ensure that mouse activity is responded to, the screen is re-drawn and // other applications get a share of the CPU. To do that I will arrange that // 'cwin_poll_window_manager()' is called from time to time in the middle of // whatever else I am doing. This grabs a message from the window manager // and dispatches it to whatever handler is relevant. static void timer_processing() { SYSTEMTIME t1; GetSystemTime(&t1); //- if (t1.wHour != lastFlushTime.wHour || //- (t1.wMinute - lastFlushTime.wMinute)*60 + //- (t1.wSecond - lastFlushTime.wSecond) > 3) //- cwin_almost_ensure_screen(); if (!theApp.mainWindow->leftSetByUser) { // Here I arrange to update the title-bar clock about once every 5 secs. It // seems that every second it too frequent, especially since it often flashes // the title-bar while re-drawing it. But 10 seconds is too long and lets // the user feel things may be stuck. // If the user explicitly sets anv value in the left part of the title bar // then this action is disabled. I do not set titleUpdateTime at the start // of a run but that does not matter - it will fall into line within a few // seconds whatever its initial value (however junky) is. if (theApp.mainWindow->titleUpdateTime.wHour != t1.wHour || theApp.mainWindow->titleUpdateTime.wMinute != t1.wMinute || theApp.mainWindow->titleUpdateTime.wSecond/5 != t1.wSecond/5) { theApp.mainWindow->titleUpdateTime = t1; theApp.mainWindow->titleUpdateTime.wSecond = 5*(theApp.mainWindow->titleUpdateTime.wSecond/5); theApp.mainWindow->cwin_display_date(); } } } // This task will busy-wait in its idle-state watching time go by and // occasionally updating the screen etc. BOOL CTheApp::OnIdle(LONG lCount) { BOOL r = CWinApp::OnIdle(lCount); if (!r) timer_processing(); return r; } void cwin_poll_window_manager(void) { // If the application has registered an idle-time handler then that gets // invoked until it has finished or until a real message arrives whenever // I call cwin_poll_window_manager(). I also process ALL the window messages // that have stacked up and only go back to the user when otherwise idle. // Note that for my application I always want a stream of idle processing // to be going on, so that timer-related activity can be handled. LONG Idle = 0; // Now I do any "idle tasks" that have been registered. while (!::PeekMessage(theApp.msgPtr, NULL, 0, 0, PM_NOREMOVE)) { if (!theApp.OnIdle(Idle++)) return; } // I only drop through if there is a message waiting for me, so that // PumpMessage will not block. do { if (!theApp.PumpMessage()) { Lstop(0, 1); // the "1" here actually means "0"! return; } // If the user selects CLOSE from the system menu it causes PumpMessage to // return FALSE, so in that case I close things down. } while (::PeekMessage(theApp.msgPtr, NULL, 0, 0, PM_NOREMOVE)); } void cwin_minimize() { WINDOWPLACEMENT wp; if (!::GetWindowPlacement(theApp.mainWindow->m_hWnd, &wp)) return; wp.showCmd = SW_SHOWMINIMIZED; ::SetWindowPlacement(theApp.mainWindow->m_hWnd, &wp); } void cwin_maximize() { WINDOWPLACEMENT wp; if (!::GetWindowPlacement(theApp.mainWindow->m_hWnd, &wp)) return; wp.showCmd = SW_RESTORE; ::SetWindowPlacement(theApp.mainWindow->m_hWnd, &wp); } void CTheApp::OnHelpContents() // Start on contents page. { WinHelp(0L, HELP_CONTENTS); } void CTheApp::OnHelpSearch() { WinHelp((DWORD)"", HELP_PARTIALKEY); // Search through keywords. } void CTheApp::OnDynamic(unsigned int commandId) { char hah[100]; // sprintf(hah, "Help message %d %s\n", commandId, // dynamic_files[commandId-IDM_DYNAMIC_ITEMS]); // DisplayMsg(hah); ::WinHelp(mainWindow->m_hWnd, dynamic_files[commandId-IDM_DYNAMIC_ITEMS], HELP_CONTENTS, 0); } void CTheApp::cwin_set_help_file(const char *key, const char *path) { char key1[8]; int i; if (key == NULL) { WriteProfileInt("HelpItems", "HowMany", 0); for (i=0; i<dynamicCount; i++) { sprintf(key1, "T%.3d", i); WriteProfileString("HelpItems", key1, NULL); sprintf(key1, "P%.3d", i); WriteProfileString("HelpItems", key1, NULL); } return; } for (i=0; i<dynamicCount; i++) { if (strcmp(key, dynamic[i]) == 0) break; } if (i == dynamicCount) // not found { if (path == NULL) return; else { dynamic[dynamicCount] = key; dynamic_files[dynamicCount++] = path; } } else { if (path == NULL) { dynamicCount--; for (;i<dynamicCount; i++) { dynamic[i] = dynamic[i+1]; dynamic_files[i] = dynamic_files[i+1]; } } else dynamic_files[i] = path; } WriteProfileInt("HelpItems", "HowMany", dynamicCount); for (i=0; i<dynamicCount; i++) { sprintf(key1, "T%.3d", i); WriteProfileString("HelpItems", key1, dynamic[i]); sprintf(key1, "P%.3d", i); WriteProfileString("HelpItems", key1, dynamic_files[i]); } if (path == NULL) { sprintf(key1, "T%.3d", i); WriteProfileString("HelpItems", key1, NULL); sprintf(key1, "P%.3d", i); WriteProfileString("HelpItems", key1, NULL); } } void cwin_set_help_file(const char *key, const char *path) { theApp.cwin_set_help_file(key, path); } // DisplayMsg is used a bit like fprintf(stderr, ...) but ONLY for debugging. // It pops up a modal dialog box each time it is called. This is easy to // code, but a bit clumsy in the way it disturbs the screen. void #ifdef _MSC_VER __cdecl #endif DisplayMsg(char *msg, ...) { char buffer[256]; va_list a; va_start(a, msg); vsprintf(buffer, msg, a); va_end(a); AfxMessageBox(buffer); } // End of c_applic.cpp