Thread: Control handle declaration conventions in large WinAPI projects

  1. #31
    Registered User
    Join Date
    Dec 2010
    Location
    Trinidad, CO (log cabin in middle of nowhere)
    Posts
    148
    With regards to developing your own tiny C and C++ standard library replacements, are you minimising executable sizes by eliminating functionality that you, personally, do not use? Or does the lack of bloat stem from optimised internal algorithms and the lack of SEH? Either way, the output sizes that you have achieved are incredible! Do you plan on publishing your libraries at any point or are they only for personal use? How's your character encoding support?

    You had asked how my TCLib.likb minimizes program size. Matt Pietrek covers this in detail in his Microsoft Systems Journal Article on LibCTiny.lib, which is still downloadable I think from his website. But I’ll go over it a bit. Try to follow along and I’ll show you how to create a 2,560 byte stand alone (no runtimes required) x64 GUI executable. First, open a Visual Studio command prompt where your build environment is configured to produce x64 binaries. There should be a shortcut on your Start Menu to it. Then put the following two files in whatever directory you want to use for this work, and change the directory to that directory…

    Code:
    // crt_win_a.cpp
    //========================================================================================
    //                 Developed As An Addition To Matt Pietrek's LibCTiny.lib
    //                              By Fred Harris, January 2016
    //
    // cl crt_win_a.cpp /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN 
    //========================================================================================
    #include <windows.h>
    #pragma comment(linker, "/defaultlib:kernel32.lib")
    #pragma comment(linker, "/nodefaultlib:libc.lib")
    #pragma comment(linker, "/nodefaultlib:libcmt.lib")
    int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int);
    
    extern "C" void __cdecl WinMainCRTStartup(void)
    {
     int iReturn = WinMain(GetModuleHandle(NULL),NULL,NULL,SW_SHOWDEFAULT);
     ExitProcess(iReturn);
    }


    Code:
    // Form1.cpp
    // cl Form1.cpp /O1 /Os /GS- /link crt_win_a.obj kernel32.lib user32.lib    //  2,560 bytes
    // cl Form1.cpp /O1 /Os /MT /GS- /link kernel32.lib user32.lib              // 38,912 bytes
    #include <windows.h>
    #include "tchar.h"
    
    LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
    {
     if(msg==WM_DESTROY)
     {
        PostQuitMessage(0);
        return 0;
     }
    
     return (DefWindowProc(hwnd, msg, wParam, lParam));
    }
    
    int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevIns, LPTSTR lpszArgument, int iShow)
    {
     WNDCLASSEX wc;
     MSG messages;
     HWND hWnd;
    
     wc.lpszClassName = _T("Form1");
     wc.lpfnWndProc   = fnWndProc;
     wc.cbSize        = sizeof(WNDCLASSEX);
     wc.hInstance     = hInstance;
     wc.hbrBackground = (HBRUSH)COLOR_BTNSHADOW;
     wc.style         = 0;
     wc.cbClsExtra    = 0;
     wc.cbWndExtra    = 0;
     wc.hIcon         = NULL;
     wc.hCursor       = NULL;
     wc.lpszMenuName  = NULL;
     wc.hIconSm       = NULL;
     RegisterClassEx(&wc);
     hWnd=CreateWindowEx(0,_T("Form1"),_T("Form1"),WS_OVERLAPPEDWINDOW|WS_VISIBLE,200,100,325,300,HWND_DESKTOP,0,hInstance,0);
     while(GetMessage(&messages,NULL,0,0))
     {
        TranslateMessage(&messages);
        DispatchMessage(&messages);
     }
    
     return messages.wParam;
    }
    The top file you can compile to crt_win_a.obj using the command line string listed in the header. After you’ve compiled that to an obj you can build Form1.cpp using the alternate WinMainCRTStartUp() code and the top command line string to a 2,560 byte executable with VC19 from Visual Studio Community 2015. Next thing to do (after you see it works) would be invoke Dumpbin.exe on Form1.exe like so…

    Dumpbin.exe /all Form1.exe [ENTER]

    A good bit of stuff will come flying out, but among that stuff will be the functions imported into the executable. Here is a list of them…

    Code:
      Section contains the following imports:
    
        KERNEL32.dll
                 140002000 Import Address Table
                 1400020B8 Import Name Table
                         0 time date stamp
                         0 Index of first forwarder reference
    
                             1F6 GetModuleHandleA
                             105 ExitProcess
    
        USER32.dll
                 140002018 Import Address Table
                 1400020D0 Import Name Table
                         0 time date stamp
                         0 Index of first forwarder reference
    
                              95 DefWindowProcA
                             224 PostQuitMessage
                             14C GetMessageA
                              A8 DispatchMessageA
                             2DD TranslateMessage
                              67 CreateWindowExA
                             238 RegisterClassExA
    My count is nine functions imported into the executable. Now lets build the same program loading the C Runtime. Here is the command line string for that, but its listed above in Form1.cpp too…

    cl Form1.cpp /O1 /Os /MT /GS- /link kernel32.lib user32.lib

    I’m seeing 38,912 bytes for that. Actually, I thought it would be a lot bigger. Anyway, do a dumpbin on that now and be prepared for a pretty long wait as everything scrolls by. Buried in all that stuff will be this listing of imported functions…

    Code:
      Section contains the following imports:
    
        USER32.dll
                 1400071E0 Import Address Table
                 140008B88 Import Name Table
                         0 time date stamp
                         0 Index of first forwarder reference
    
                             238 RegisterClassExA
                              67 CreateWindowExA
                             2DD TranslateMessage
                              A8 DispatchMessageA
                             14C GetMessageA
                             224 PostQuitMessage
                              95 DefWindowProcA
    
        KERNEL32.dll
                 140007000 Import Address Table
                 1400089A8 Import Name Table
                         0 time date stamp
                         0 Index of first forwarder reference
    
                             266 GetTickCount
                             170 GetCommandLineA
                             239 GetStartupInfoA
                             419 SetUnhandledExceptionFilter
                             1F9 GetModuleHandleW
                             425 Sleep
                             220 GetProcAddress
                             105 ExitProcess
                             491 WriteFile
                             23B GetStdHandle
                             1F4 GetModuleFileNameA
                             396 RtlUnwindEx
                             14B FreeEnvironmentStringsA
                             1C0 GetEnvironmentStrings
                             14C FreeEnvironmentStringsW
                             47E WideCharToMultiByte
                             1E6 GetLastError
                             1C2 GetEnvironmentStringsW
                             3EC SetHandleCount
                             1D8 GetFileType
                              BF DeleteCriticalSection
                              D6 EncodePointer
                              B8 DecodePointer
                             13F FlsGetValue
                             140 FlsSetValue
                             13E FlsFree
                             3F0 SetLastError
                             1AE GetCurrentThreadId
                             13D FlsAlloc
                             2A5 HeapSetInformation
                             29F HeapCreate
                             34E QueryPerformanceCounter
                             1AB GetCurrentProcessId
                             24F GetSystemTimeAsFileTime
                             2E9 LeaveCriticalSection
                              DA EnterCriticalSection
                             431 TerminateProcess
                             1AA GetCurrentProcess
                             442 UnhandledExceptionFilter
                             2CB IsDebuggerPresent
                             397 RtlVirtualUnwind
                             390 RtlLookupFunctionEntry
                             389 RtlCaptureContext
                             2EB LoadLibraryA
                             2B5 InitializeCriticalSectionAndSpinCount
                             15C GetCPInfo
                             153 GetACP
                             213 GetOEMCP
                             2D5 IsValidCodePage
                             2A1 HeapFree
                             2A6 HeapSize
                             1E8 GetLocaleInfoA
                             2DB LCMapStringA
                             314 MultiByteToWideChar
                             2DD LCMapStringW
                             23D GetStringTypeA
                             240 GetStringTypeW
                             29D HeapAlloc
                             2A4 HeapReAlloc
    AS you can see, there are a lot more of them, and by far most of them weren’t used in the app. So that should give you an inkling of why the compile without loading all that extra stuff ended up producing a smaller executable.

    I first became familiar with the work of Matt Pietrek on this topic of minimizing the sizes of executables by eliminating the C Runtime in October 2014. Matt was a luminary in software development who used to write the popular “Under The Hood” column in “Microsoft Systems Journal”. His article on this was named…

    “Reduce EXE and DLL Size with LIBCTINY.LIB”

    …and it was published in the January 2001 issue of “Microsoft Systems Journal”. From Matt’s website…

    Matt Pietrek’s Website
    Wheaty Productions Inc.

    … I was able to download his source and the LibCTiny.lib file. I was able to run nmake on that and build his lib myself. It worked great. Then I wanted to see if there was any possibility of getting it to work in x64. There was. There was only one error that came up and it was easy to fix. I posted my explorations of this topic on my board under “Discussion” at …

    Reducing Executable Size In C/C++ Programs By Eliminating The C Runtime (October 16, 2014)
    Reducing Executable Size In C/C++ Programs By Eliminating The C Runtime

    The topic so interested me that I wanted to find time in my work at some future point to explore it further and see if I couldn’t get the code working in wide character builds, so that I could do both x86 and x64 in ansi and wide. That time didn’t come for about a year and a half but it did come beginning of 2016. Here are links to my postings on it in Jose Roca’s forum…

    Reducing Windows Executable File Size With Matt Pietrek’s LibCTiny.Lib Revisited (February 12, 2016)
    Reducing Windows Executable File Size With Matt Pietrek's LIBCTINY.LIB Revisited

    Reducing Program Size In UNICODE And x64 Builds With Matt Pietrek’s LibCTiny.LIB February 26, 2016)
    Reducing Program Size In UNICODE And X64 Builds With Matt Pietrek’s LibCTiny.lib

    Minimize Program Size By Eliminating The C Runtime Library (March 23, 2016)
    Minimize Program Size By Eliminating The C Runtime Library

    Moving TCLib Into Mainstream
    http://www.jose.it-berater.org/smffo...p?topic=5132.0

    Also, there is a lot more can be found out about this topic. Here is a noteworthy article from Dr. Dobbs Journal by Matthew Wilson…

    Avoiding The Visual C++ Runtime Library (Matthew Wilson, February 1, 2003)
    http://www.drdobbs.com/avoiding-the-...rary/184416623

    The latter article was particularly interesting to me because I found it right after spending like a month wrestling with floating point math issues and having lost a lot of sleep, blood, and hide getting it to work. And in that article Matt Wilson stated that if your app needs floating point support your only alternative is standard linking with the C and C++ Runtimes. So I guess its good I hadn’t read his article before I accomplished it. Maybe I wouldn’t even have tried! I will say though that getting everything to work was easier in x64 than x86 when these foundational papers were written (the issues involve casting of 64 bit quantities to 32 bits and vice versa).

    Also noteworthy was a post at CodeProvect by a Mike_V in around 2007…

    http://www.codeproject.com/Articles/...untime-Library

    Through his work I got a clue on how to modify Matt Pietrek’s work for wide character support. I also got some clues on file routines from him. I did have some problems with his code however, and didn’t use much of it.

    Last but by far not least was Martins Mozeiko of HandmadeHero…

    https://hero.handmade.network/forums...iscussion/t/94

    Martins helped me when I was about to totally give up on floating point math. He is incredible. That’s all I can say about him. I don’t do computer games but in witnessing his know-how I have a new respect for gamers!

    But everything is there at those Jose Roca links above. Because my postings of it were a ‘work in progress’ type thing, to put it all together in final finished form pretty much necessitates starting at the beginning and copying the code, creating the libs, etc., and agonizing through it like I did. I suppose I could post what I have now, but its still not done I guess, as I’m still wrestling with printf, sprintf, fprintf, etc., and of course the issues are what they always have been, i.e., floating point support.

    I have had some major successes with it though. What elated me to no end, and what was so wonderful I still can’t believe its true, is that after a number of days of struggle I was able to get my ActiveX Grid Control working perfectly! The code and discussion for that is at the above links. That grid control I original wrote about 5 years ago in PowerBASIC. I used all very low level code to create it, i.e., I built the virtual function tables by hand using just struct/types and memory allocations, and it built in PowerBASIC to 49 k which is incredible (and compacts to 22k!). Probably the smallest full featured grid control anywhere. Sometime after that I rewrote it in C++ so I could create 32/64 bit versions. It was a good bit larger in C++ - like about 90 k, but I was able to compact it with UPX to 50 k. However, my PowerBASIC created version was 22 k when compacted. When I finally got my C++ code to build with my TCLib.lib I was at about 22 k in x64! And that compacted with UPX to 17k! So I would call that a mammoth success! And it works perfectly far as I can tell. I’ve tested quite a bit.

    For the past couple weeks I have been attempting to re-build a large mission critical enterprise level application with my TCLib.lib. I have not had any difficulties getting it to build, but the app contains literally thousands of fprintf calls with floating point numbers. My TCLib is awkward to use for this because my printfs from Matt Pietrek’s original work didn’t work with floats/doubles. I can use my FltToCh/FltToWch functions, which work perfectly, but I have to modify the original code substantially, use an extra output buffer, and make two function calls whereas there were originally only one. So that’s why I’m presently working on a fully compatible printf that works with floats/doubles in exactly the same manner as the C Runtime.

    So yes, the minimization achieved by any such libraries as my TCLib.lib, Matt Pietrek’s LIBCTINY.LIB, etc., come from the elimination of code that isn’t being used. For example, in that simple example I gave above which compiled to 2,560 bytes in x64 there is no support for command line argument processing, and no support for global/static Constructors/Destructors, i.e., classes instantiated outside of any function at global scope. I very, very seldom do either of those things, so I eliminated them. However, in Matt Pietrek’s code he didn’t, but still his executable would have only been 4 k instead of the 2.5 k of mine. So those two things don’t add that much. The big hit with linking with the C Standard Library comes from all the extra functions imported from it that aren’t used, plus its own heap code, startup code, etc. It all adds up.

  2. #32
    Registered User
    Join Date
    Dec 2010
    Location
    Trinidad, CO (log cabin in middle of nowhere)
    Posts
    148
    deleted Somehow it duplicated
    Last edited by freddie; 06-24-2016 at 03:19 PM.

  3. #33
    Registered User
    Join Date
    Jul 2015
    Posts
    64
    Wow! Ok, so it appears that I have a lot of information to digest right now; thank you for all the material. (I also have about 20 lengthy Code Project articles open and 7 tabs pertaining to RPC and pipe security in Windows, so please forgive me for my late reply.)

    First of all, the grid control demo that you provided compiled wonderfully (short of a few function deprecation warnings,) so I shall leave that on my laptop as a reference for proper windows application structure in the future. Many thanks for the resource Freddie! Just a few more questions and I think I'll be set

    Another very common way I use that last param of the CreateWindowEx() call that might not be overly evident from the example I posted is that in major applications that have lots of modules or visual interface elements... is to pass the HWND of the creating object into the creation code of the object being created. Then that new object of a different class stores that HWND of the other object in the cbWndExtra bytes. In that way, it will have access to any data stored there in that other object. In a major application there always are piecies of data that are used by all the different modules. This technique allows access to them without creating globals or statics.
    Could you possibly go into a little more detail on this paragraph Freddie? I'm not too sure what you mean; is the "creating object" the parent window to the being-created control? Surely we must pass this information to the new object as the 9th parameter (HWND hWndParent) of CreateWindowEx, otherwise we would not be creating a child window. Am I mistaken?

    Whenever I've created controls in the past, I have always passed the parent HWND as the 9th parameter to CreateWindowEx. What I believe you're referring to is that the information stored in the ::cbWndExtra bytes field of the parent window class can be accessed from external window procedures. Am I following you correctly? Are we talking about a call to GetWindowLongPtr(hwnd, GWLP_USERDATA)here?

    Shame about the lack of a x64 PowerBASIC compiler, it does indeed sound like a truly well equipped language. To be honest, I had never even heard of the language prior to you first mentioning it, but it sounds like it genuinely does encompasses some of my personal programming ideals (i.e. smaller/faster). Still as a relative newcomer to the field, I feel I should be following along with current trends in the software engineering arena... although I'm pretty set in my ways when it comes to C/C++, it might be time to make the leap to C# or, God forbid, Java some time soon. We'll see.

    I might check out the Discussion sub-topic on the Jose Roca forum, cheers for the pointer. I've always been very much interested in the fundamental internals of the CRT, stdio and stdlib: essentially hardware/software interface code that maintains a healthy level of abstraction. I find that truly fascinating, although I am not as well versed in the science behind it as I would like to be.

    My goal is to be able to take any piece of my source code which I've developed using the standard default C and C++ linkages and rebuild it with my custom library.
    That's the dream! Hopefully, for you, it isn't a pipe dream like it would be for myself. I have faith in you! However, I see from your latest post and the posts between adeyblue and yourself that this is wayyyy out of my depth. Whilst I think it is important to minimise the size of binaries (even these days when disk space and RAM are surplus), I see that it really isn't a simple affair. This is something I shall definitely consider in the future, but I think for now I shall have to put it on the back burner. In fact, I'll probably just bookmark this thread for future reference; I never thought a thread of mine could ever generate such an intelligent discussion!

    Thank you though, for putting such effort into your replies! It is highly appreciated and I am learning more now than I pretty much ever have.

  4. #34
    Registered User
    Join Date
    Dec 2010
    Location
    Trinidad, CO (log cabin in middle of nowhere)
    Posts
    148
    Sorry it took so long in replying Abyssion. Was busy.

    Anyway, about my comments above you didn’t quite ‘get’, I admit they were a bit terse. Below is an app explaining what I mean, and actually I consider this to be an important app because it shows how to handle multiple module programs in the context of the use of my message routing scheme which it sounds to me like you like and might adopt. The program presents a small main window with three buttons on it named respectively “Option #1’, “Option #2” and “Option #3”. It simulates a situation where you have a multiple module program with a number of ‘main’ windows – each with some different modality or program function related enough to some main program purpose to be part of the same program.

    When you click the top button for “Option #1” the program creates a “Modal Dialog Box” without any resource script. This is quite unusual coding and I doubt there are many folks that know how to do this. The program actually shows how to manage multiple ‘message pumps’. So the effect of this is that you can’t interact with the main program window or startup form when the Dialog Box is open because the main form is disabled. Here is the code that creates the dialog box programmatically when the top button (Form1) is clicked…

    Code:
    long btnForm1_Click(WndEventArgs& Wea)                     //This is an 'Event Handler' for a click of the top button on the
    {                                                          //main Form.  It uses CreateWindowEx() to instantiate a new Window
     DWORD dwStyle,dwExStyle;                                  //of class "Form1"  Note that the last parameter of the call is
     HWND hDlg;                                                //Wea.hWnd.  That is the HWND of the main program Window.  The
     MSG Msg;                                                  //last parameter is the Creation Parameters parameter.  It can be
                                                               //retrieved (as can all the others) through the CREATIONSTRUCT a
     dwStyle   = WS_CAPTION | WS_POPUPWINDOW | WS_VISIBLE;     //pointer to which is received in the lParam of the WM_CREATE
     dwExStyle = WS_EX_DLGMODALFRAME | WS_EX_CONTROLPARENT;    //message for the newly instantiating Window.
     hDlg=CreateWindowEx(dwExStyle,_T("Form1"),_T("Form1"),dwStyle,50,25,310,185,Wea.hWnd,(HMENU)0,GetModuleHandle(0),Wea.hWnd);
     ShowWindow(hDlg,SW_SHOWNORMAL);
     UpdateWindow(hDlg);
     while(GetMessage(&Msg,NULL,0,0))    // The IsDialogMessage function determines whether a message is intended for the specified
     {                                   // dialog box and, if it is, processes the message.  If the message has been processed, the
        if(!IsDialogMessage(hDlg,&Msg))  // return value is nonzero.  If the message has not been processed, the return value is zero.
        {                                // Because the IsDialogMessage function performs all necessary translating and dispatching of
           TranslateMessage(&Msg);       // messages, a message processed by IsDialogMessage must not be passed to the TranslateMessage
           DispatchMessage(&Msg);        // or DispatchMessage function.If the return value is zero, its not a message for hDlg.  So
        }                                // process it.
     }
    
     return 0;
    }
    Moving on to “Option #2” you have to dismiss the ‘Dialog Box’ and click ‘Option #2’ on the main startup form. What happens there is that in the WM_CREATE handler for the “Form2” Class the visibility of the main startup form is set to FALSE. So this shows a way to prevent the user from interacting with any part of a program other than the window that is showing (Form2). Note the CreateWindowEx() calls that create these various windows. For Form2 it is this…

    Code:
    hWnd=CreateWindowEx(0,_T("Form2"),_T("Form2"),WS_OVERLAPPEDWINDOW,200,250,310,185,0,(HMENU)0,GetModuleHandle(0),Wea.hWnd);
    In the call above note the 4th parameter is WS_OVERLAPPEDWINDOW – same as the main startup form’s style. So it’s a ‘top level’ window – not a child window or child window control as was the case in my last ‘grid’ custom control example. In accordance with that the 9th parameter, i.e., the ‘hwndParent’ is set to zero. That is, the window doesn’t have a parent. Using GetParent() on the hWnd of that window won’t return anything. But note I passed the HWND of the creating window in the last parameter of the CreateWindowEx() call so that objects of the Form2 Class will have access to the HWND of the main startup Form. Objects of the Form2 Class use that HWND to set the visibility of the main startup form to TRUE/FALSE.

    Form3 objects are different yet. They don’t render the main startup form inactive or invisible, so you can create as many of these as you like by continually clicking the bottom button on the main startup form.

    I hope you find this application worthwhile to study.

    In terms of your comments about having to fix a few deprecation warnings on that last example, I don’t see any of that as I don’t use the Visual Studio IDE at all anymore and that’s where those deprecation warnings are coming from (setting the warning level to 1 or 2 will stop them). I really like IDEs but only if they help. When IDEs first came out in the 1980s they were like ‘manna from heaven’. Everybody started using them as they saved one from the monotony of having to use command line tools for everything. And that worked great for a long time. But they kept getting more and more complicated. And a whole generation of programmers in the somewhat ‘cushioned Microsoft platform) came onto the scene that never learned how the command line tools underlying the IDEs worked. And the IDEs kept getting more and more features and more and more involved – as I said. At some indeterminate date within the past ten years it finally reached the point where the IDEs became more of a hindrance than a help in writing code – at least in my opinion. So that’s why I’ve largely abandoned them – at least the ones from Microsoft. I still use the CodeBlocks IDE some but that has been following the pattern of Visual Studio also and I expect I’ll have to abandon that soon too or use the older versions. The Notepad++ editor is lightweight and functions pretty well and I use that mostly.

    The other thing about the deprecation warnings and the string safe versions of the string functions is that in my C++ work I do I code for embedded systems where security and ‘buffer overrun attacks’ aren’t an issue. So to me its just a hassle to have to either use the string safe functions, or insert “CRT_SECURE_NO_WARNINGS” in the Visual Studio IDE or change the warning level to prevent it from throwing millions of warnings at me. Just another reason I gave up on the Visual Studio IDE.

    What had me busy the past week was that Codeplug’s gave me the hint that msvcrt.dll is loaded into every Windows process. I downloaded Mark Russinovich’s Sysinternals “Process Explorer” and saw that that was largely the case. But not exactly. It appears that Windows Console processes are somewhat simpler than GUI processes. Using my TCLib replacement for the C Runtime Libraries I noted that msvcrt.dll wasn’t being loaded. But it was for even the most basic GUI process. I feel really dumb for not knowing that. That changes everything!

    I had been struggling since the beginning of my C Runtime replacement work to come up with satisfactory replacements for the really important stdio functions. Due to floating point issues I even finally coded a full replacement for the family of printf functions. But it takes a lot of code. Then I discovered that msvcrt.dll, which contains all the stdio C Runtime functionality I needed, was being loaded in every GUI process I created whether I used it or not.

    So I altered my TCLib code to use the msvcrt.dll functionality for the stdio functions I needed through function pointers. I simply defined all of them I needed such as printf, sprintf, fprintf, fopen, fclose, gets, etc., as function pointers, whose use is indistinguishable from the function versions. Worked like a charm! The string.h functions I left as they were as none of them involved much code. They are easy to implement. The net result was largely even smaller executables – especially if floating points were needed in the app. The above described and soon to be posted app came to 92,160 Bytes x64 VC19 (VS 2015) asci libcmt linked and 10,752 Bytes x64 VC19 (VS 2015) wide character TCLib linked.

    Here’s the code for this MultipleForms project. There are a lot of small code piecies as the Main startup form has a Main.h and Main.cpp, then each of the Form1, Form2 and Form3 objects have their associated *.cpp and *.h files. In a large coding project that is how I set it up. Each GUI element has its own *.cpp code file with its own Window Procedure for the Class of object it is…

    continued...

  5. #35
    Registered User
    Join Date
    Dec 2010
    Location
    Trinidad, CO (log cabin in middle of nowhere)
    Posts
    148
    Code:
    //Main.h
    #ifndef MAIN_H
    #define MAIN_H
    
    #define dim(x)                (sizeof(x) / sizeof(x[0]))
    #define IDC_BUTTON_FORM1      1600
    #define IDC_BUTTON_FORM2      1605
    #define IDC_BUTTON_FORM3      1610
    
    struct                        WndEventArgs
    {
     HWND                         hWnd;
     WPARAM                       wParam;
     LPARAM                       lParam;
     HINSTANCE                    hIns;
    };
    
    struct EVENTHANDLER
    {
     unsigned int                 iMsg;
     LRESULT                      (*fnPtr)(WndEventArgs&);
    };
    
    LRESULT fnWndProc_OnCreate    (WndEventArgs&);  //We need foreward declarations of
    LRESULT fnWndProc_OnCommand   (WndEventArgs&);  //the various functions in a main
    LRESULT fnWndProc_OnClose     (WndEventArgs&);  //header file so that the event
                                                    //handlers can be attached below.
    LRESULT fnForm1_OnCreate      (WndEventArgs&);
    LRESULT fnForm1_OnPaint       (WndEventArgs&);
    LRESULT fnForm1_OnDestroy     (WndEventArgs&);
    
    LRESULT fnForm2_OnCreate      (WndEventArgs&);
    LRESULT fnForm2_OnPaint       (WndEventArgs&);
    LRESULT fnForm2_OnDestroy     (WndEventArgs&);
    
    LRESULT fnForm3_OnCreate      (WndEventArgs&);
    LRESULT fnForm3_OnPaint       (WndEventArgs&);
    LRESULT fnForm3_OnClose       (WndEventArgs&);
    
    const EVENTHANDLER            Form1EventHandler[]=
    {
     {WM_CREATE,                  fnForm1_OnCreate},
     {WM_PAINT,                   fnForm1_OnPaint},
     {WM_DESTROY,                 fnForm1_OnDestroy}
    };
    
    const EVENTHANDLER            Form2EventHandler[]=
    {
     {WM_CREATE,                  fnForm2_OnCreate},
     {WM_PAINT,                   fnForm2_OnPaint},
     {WM_DESTROY,                 fnForm2_OnDestroy}
    };
    
    const EVENTHANDLER            Form3EventHandler[]=
    {
     {WM_PAINT,                   fnForm3_OnPaint},
     {WM_CLOSE,                   fnForm3_OnClose}
    };
    
    #endif
    Code:
    // Main.cpp
    // cl Main.cpp Form1.cpp Form2.cpp Form3.cpp /O1 /Os /GS- /W2 /FeMultipleForms.exe TCLib.lib kernel32.lib user32.lib gdi32.lib
    // cl Main.cpp Form1.cpp Form2.cpp Form3.cpp /O1 /Os /GS- /MT /FeMultipleForms.exe kernel32.lib user32.lib gdi32.lib 
    // 92,160 Bytes x64 VC19 (VS 2015) asci libcmt linked 
    // 10,752 Bytes x64 VC19 (VS 2015) wide TCLib  linked
    //  9,728 bytes x64 VC19 (VS 2015) asci TCLib  linked
    #ifndef UNICODE
       #define UNICODE
    #endif
    #ifndef _UNICODE
       #define _UNICODE
    #endif   
    // Main.cpp               //Program displays a main Form/Window/Dialog With Three Buttons on it to simulate
    #include <windows.h>      //a program started from a main Form with three modules/options/modalities within
    #include <tchar.h>        //which it operates.  When you click the top button a new Form/Window/Dialog is
    #include <stdio.h>        //created with CreateWindow() of the "Form1" Window Class, and in the WM_CREATE
    #include "Main.h"         //handler of this new window it disables the main form with the three buttons and
    #include "Form1.h"        //therefore is an example of a modal dialog.  When you dismiss this modal dialog
    #include "Form2.h"        //and click on Button/Option #2 on the Main Form, a CreateWindow() call creates a
    #include "Form3.h"        //window of "Form2" Window Class, and in the WM_CREATE handler it sets the visi-
                              // bility of the main window to FALSE.
    const EVENTHANDLER         EventHandler[]=        //Since we foreward declared above
    {                                                 //the various event handling functions
     {WM_CREATE,               fnWndProc_OnCreate},   //of the various forms, windows, dialogs
     {WM_COMMAND,              fnWndProc_OnCommand},  //above, we can fill out the fields of
     {WM_CLOSE,                fnWndProc_OnClose}     //our EVENTHANDLER structures for the
    };                                                //various objects.
    
    
    LRESULT fnWndProc_OnCreate(WndEventArgs& Wea)      //...for this window it hides or makes invisible the main
    {                                                  //window.  After dismssing this window you'll find that you
     DWORD dwStyle=WS_CHILD|WS_VISIBLE;                //can click on the Option #3 button as many times as you like
     TCHAR szClassName[16];                            //because the window-form-dialog it creates neither disables
     WNDCLASSEX wc;                                    //nor makes invisible the main window.  Further note that these
                                                       //Option #3 windows can be interacted with regardless of what-
     Wea.hIns=((LPCREATESTRUCT)Wea.lParam)->hInstance; //ever is going on with the other windows.
     CreateWindow(_T("button"),_T("Option #1"),dwStyle,65,15,120,25,Wea.hWnd,(HMENU)IDC_BUTTON_FORM1,Wea.hIns,0);
     CreateWindow(_T("button"),_T("Option #2"),dwStyle,65,55,120,25,Wea.hWnd,(HMENU)IDC_BUTTON_FORM2,Wea.hIns,0);
     CreateWindow(_T("button"),_T("Option #3"),dwStyle,65,95,120,25,Wea.hWnd,(HMENU)IDC_BUTTON_FORM3,Wea.hIns,0);
    
     //Register Window Classes For Form1, Form2 and Form3
     wc.cbSize=sizeof(WNDCLASSEX),                            wc.style=CS_HREDRAW | CS_VREDRAW;
     wc.cbClsExtra=0,                                         wc.cbWndExtra=0;
     wc.hInstance=Wea.hIns,                                   wc.hIcon=LoadIcon(NULL, IDI_APPLICATION);
     wc.hIconSm=0,                                            wc.hCursor=LoadCursor(NULL, IDC_ARROW);
     wc.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH),    wc.lpszMenuName=NULL;
     _tcscpy(szClassName,_T("Form1")),                        wc.lpszClassName=szClassName;
     wc.lpfnWndProc=fnForm1_WndProc;
     RegisterClassEx(&wc);
    
     _tcscpy(szClassName,_T("Form2"));
     wc.lpfnWndProc=fnForm2_WndProc;
     wc.lpszClassName=szClassName;                //Note that a WM_CREATE call is akin to a constructor call in typical
     RegisterClassEx(&wc);                        //C++ class architecture.  When you receive this call/message Windows
                                                  //has finished doing what it needs to support the Window object, and
     _tcscpy(szClassName,_T("Form3"));            //is 'passing the ball' to you.  In my apps with multiple windows I
     wc.lpszClassName=szClassName;                //typically use the WM_CREATE handler to register any window classes
     wc.lpfnWndProc=fnForm3_WndProc;              //I need in the app, so that I can make CreateWindow() calls when I
     RegisterClassEx(&wc);                        //need to instantiate a window of some type.
    
     return 0;
    }
    
    
    long btnForm1_Click(WndEventArgs& Wea)                     //This is an 'Event Handler' for a click of the top button on the
    {                                                          //main Form.  It uses CreateWindowEx() to instantiate a new Window
     DWORD dwStyle,dwExStyle;                                  //of class "Form1"  Note that the last parameter of the call is
     HWND hDlg;                                                //Wea.hWnd.  That is the HWND of the main program Window.  The
     MSG Msg;                                                  //last parameter is the Creation Parameters parameter.  It can be
                                                               //retrieved (as can all the others) through the CREATIONSTRUCT a
     dwStyle   = WS_CAPTION | WS_POPUPWINDOW | WS_VISIBLE;     //pointer to which is received in the lParam of the WM_CREATE
     dwExStyle = WS_EX_DLGMODALFRAME | WS_EX_CONTROLPARENT;    //message for the newly instantiating Window.
     hDlg=CreateWindowEx(dwExStyle,_T("Form1"),_T("Form1"),dwStyle,50,25,310,185,Wea.hWnd,(HMENU)0,GetModuleHandle(0),Wea.hWnd);
     ShowWindow(hDlg,SW_SHOWNORMAL);
     UpdateWindow(hDlg);
     while(GetMessage(&Msg,NULL,0,0))    // The IsDialogMessage function determines whether a message is intended for the specified
     {                                   // dialog box and, if it is, processes the message.  If the message has been processed, the
        if(!IsDialogMessage(hDlg,&Msg))  // return value is nonzero.  If the message has not been processed, the return value is zero.
        {                                // Because the IsDialogMessage function performs all necessary translating and dispatching of
           TranslateMessage(&Msg);       // messages, a message processed by IsDialogMessage must not be passed to the TranslateMessage
           DispatchMessage(&Msg);        // or DispatchMessage function.If the return value is zero, its not a message for hDlg.  So
        }                                // process it.
     }
    
     return 0;
    }
    
    
    long btnForm2_Click(WndEventArgs& Wea)
    {
     HWND hWnd;
    
     hWnd=CreateWindowEx
     (
       0,
       _T("Form2"),
       _T("Form2"),
       WS_OVERLAPPEDWINDOW,
       200,250,310,185,
       0,
       (HMENU)0,
       GetModuleHandle(0),
       Wea.hWnd
     );
     ShowWindow(hWnd,SW_SHOWNORMAL);
     UpdateWindow(hWnd);
    
     return 0;
    }
    
    
    long btnForm3_Click(WndEventArgs& Wea)
    {
     HWND hWnd;
    
     hWnd=CreateWindowEx
     (
       0,
       _T("Form3"),
       _T("Form3"),
       WS_OVERLAPPEDWINDOW,
       CW_USEDEFAULT,CW_USEDEFAULT,300,260,
       0,
       (HMENU)0,
       GetModuleHandle(0),
       Wea.hWnd
     );
     ShowWindow(hWnd,SW_SHOWNORMAL);
     UpdateWindow(hWnd);
    
     return 0;
    }
    
    
    LRESULT fnWndProc_OnCommand(WndEventArgs& Wea)
    {
     switch(LOWORD(Wea.wParam))
     {
        case IDC_BUTTON_FORM1:
          return btnForm1_Click(Wea);
        case IDC_BUTTON_FORM2:
          return btnForm2_Click(Wea);
        case IDC_BUTTON_FORM3:
          return btnForm3_Click(Wea);
     }
    
     return 0;
    }
    
    
    LRESULT fnWndProc_OnClose(WndEventArgs& Wea)     //Search And Destroy Mission For Any Form3
    {                                                //Windows Hanging Around.
     HWND hForm;
    
     if(MessageBox(Wea.hWnd,_T("Do You Wish To Exit?"),_T("Exit App?"),MB_YESNO)==IDYES)
     {
        do                                           //If FindWindow() returns something other
        {                                            //than zero, it found a window matching
          hForm=FindWindow(_T("Form3"),_T("Form3")); //the description of what you are looking
          if(hForm)                                  //for.  In that case, send a WM_CLOSE
             SendMessage(hForm,WM_CLOSE,0,0);        //message.  If NULL is returned then just
          else                                       //break out of the loop and terminate the
             break;                                  //app.  I don't believe this code is really
        }while(TRUE);                                //necessary, but I left it in.
        DestroyWindow(Wea.hWnd);
        PostQuitMessage(0);
     }
    
     return 0;
    }
    
    
    LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam,LPARAM lParam)
    {
     WndEventArgs Wea;
    
     for(unsigned int i=0; i<dim(EventHandler); i++)
     {
         if(EventHandler[i].iMsg==msg)
         {
            Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
            return (*EventHandler[i].fnPtr)(Wea);
         }
     }
    
     return (DefWindowProc(hwnd, msg, wParam, lParam));
    }
    
    
    int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
    {
     TCHAR szClassName[]=_T("Multiple Forms");
     WNDCLASSEX wc={};
     MSG messages;
     HWND hWnd;
    
     wc.lpszClassName=szClassName;                wc.lpfnWndProc=fnWndProc;
     wc.cbSize=sizeof (WNDCLASSEX);               wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);
     wc.hInstance=hInstance,                      wc.hCursor=LoadCursor(NULL,IDC_ARROW);
     wc.hbrBackground=(HBRUSH)COLOR_BTNSHADOW;
     RegisterClassEx(&wc);
     hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,250,500,260,180,HWND_DESKTOP,0,hInstance,0);
     ShowWindow(hWnd,iShow);
     while(GetMessage(&messages,NULL,0,0))
     {
        TranslateMessage(&messages);
        DispatchMessage(&messages);
     }
    
     return messages.wParam;
    }
    Code:
    //Form1.cpp
    #ifndef UNICODE
       #define UNICODE
    #endif
    #ifndef _UNICODE
       #define _UNICODE
    #endif   
    #include  <Windows.h>
    #include  <tchar.h>
    #include  "Form1.h"
    #include  "Main.h"
    
    
    LRESULT fnForm1_OnCreate(WndEventArgs& Wea)
    {
     CREATESTRUCT* pCreateStruct;
     HWND hMain;
    
     pCreateStruct=(CREATESTRUCT*)Wea.lParam;
     hMain=(HWND)pCreateStruct->lpCreateParams;
     SetWindowLongPtr(Wea.hWnd,GWLP_USERDATA,(LONG_PTR)hMain);
     EnableWindow(hMain,FALSE);
    
     return 0;
    }
    
    
    LRESULT fnForm1_OnPaint(WndEventArgs& Wea)
    {
     PAINTSTRUCT ps;
     HDC hDC;
    
     hDC=BeginPaint(Wea.hWnd,&ps);
     TextOut(hDC,0,0,_T("This Is Form1.  It Disables The Main"),36);
     TextOut(hDC,0,16,_T("Window, And That Makes It Modal.  Note"),38);
     TextOut(hDC,0,32,_T("That We Passed The Handle Of The Main"),37);
     TextOut(hDC,0,48,_T("Window In The Last Parameter Of The"),35);
     TextOut(hDC,0,64,_T("CreateWindow() Call, And Retrieved It In"),40);
     TextOut(hDC,0,80,_T("fnForm1_OnCreate().  We Then Stored It So"),41);
     TextOut(hDC,0,96,_T("We Could EnableWindow(TRUE) The Main"),36);
     TextOut(hDC,0,112,_T("Window When This Modal Form Is"),30);
     TextOut(hDC,0,128,_T("Dismissed."),10);
     EndPaint(Wea.hWnd,&ps);
    
     return 0;
    }
    
    
    LRESULT fnForm1_OnDestroy(WndEventArgs& Wea)
    {
     HWND hMain;
    
     hMain=(HWND)GetWindowLongPtr(Wea.hWnd,GWLP_USERDATA);
     EnableWindow(hMain,TRUE);
     PostQuitMessage(0);
     return 0;
    }
    
    
    LRESULT CALLBACK fnForm1_WndProc(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
    {
     WndEventArgs Wea;
    
     for(unsigned int i=0; i<dim(Form1EventHandler); i++)
     {
         if(Form1EventHandler[i].iMsg==msg)
         {
            Wea.hWnd=hWnd, Wea.lParam=lParam, Wea.wParam=wParam;
            return (*Form1EventHandler[i].fnPtr)(Wea);
         }
     }
    
     return (DefWindowProc(hWnd, msg, wParam, lParam));
    }
    Code:
    //Form1.h         //Needs to be included in Main.cpp because the WM_CREATE handler there
    #ifndef FORM1_H   //references fnForm1_WndProc as the Window Procedure for the Form1 Class.
    #define FORM1_H   //This would be needed to register the class.
    LRESULT CALLBACK fnForm1_WndProc(HWND, unsigned int, WPARAM, LPARAM);
    #endif
    Code:
    //Form2.cpp
    #ifndef UNICODE
       #define UNICODE
    #endif
    #ifndef _UNICODE
       #define _UNICODE
    #endif   
    #include  <Windows.h>
    #include  <tchar.h>
    #include  "Main.h"
    #include  "Form2.h"
    
    
    LRESULT fnForm2_OnCreate(WndEventArgs& Wea)
    {
     CREATESTRUCT* pCreateStruct;
     HWND hMain;
    
     pCreateStruct=(CREATESTRUCT*)Wea.lParam;
     hMain=(HWND)pCreateStruct->lpCreateParams;
     SetWindowLongPtr(Wea.hWnd,GWLP_USERDATA,(LONG_PTR)hMain);
     ShowWindow(hMain,SW_HIDE);
    
     return 0;
    }
    
    
    LRESULT fnForm2_OnPaint(WndEventArgs& Wea)
    {
     PAINTSTRUCT ps;
     HDC hDC;
    
     hDC=BeginPaint(Wea.hWnd,&ps);
     TextOut(hDC,0,0,_T("This Is Form2.  It SW_HIDEs The Main"),36);
     TextOut(hDC,0,16,_T("Window, And SW_SHOWs It Upon Closing."),37);
     TextOut(hDC,0,32,_T("This Technique Can Be Used Similiarly"),37);
     TextOut(hDC,0,48,_T("To A Modal Dialog If It Isn't Necessary To"),42);
     TextOut(hDC,0,64,_T("View Simultaneously A Form Underneath The"),41);
     TextOut(hDC,0,80,_T("Dialog With Which You Can't Interact"),36);
     TextOut(hDC,0,96,_T("Anyway"),6);
     EndPaint(Wea.hWnd,&ps);
    
     return 0;
    }
    
    
    LRESULT fnForm2_OnDestroy(WndEventArgs& Wea)
    {
     HWND hMain;
    
     hMain=(HWND)GetWindowLongPtr(Wea.hWnd,GWLP_USERDATA);
     EnableWindow(hMain,TRUE);
     ShowWindow(hMain,TRUE);
    
     return 0;
    }
    
    
    LRESULT CALLBACK fnForm2_WndProc(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
    {
     WndEventArgs Wea;
    
     for(unsigned int i=0; i<dim(Form2EventHandler); i++)
     {
         if(Form2EventHandler[i].iMsg==msg)
         {
            Wea.hWnd=hWnd, Wea.lParam=lParam, Wea.wParam=wParam;
            return (*Form2EventHandler[i].fnPtr)(Wea);
         }
     }
    
     return (DefWindowProc(hWnd, msg, wParam, lParam));
    }
    Code:
    //Form2.h        //Needs to be included in Main.cpp because the WM_CREATE handler there
    #ifndef FORM2_H  //references fnForm2_WndProc as the Window Procedure for the Form1 Class.
    #define FORM2_H  //This would be needed to register the class.
    LRESULT CALLBACK fnForm2_WndProc(HWND, unsigned int, WPARAM, LPARAM);
    #endif
    Code:
    //Form3.cpp
    #ifndef UNICODE
       #define UNICODE
    #endif
    #ifndef _UNICODE
       #define _UNICODE
    #endif   
    #include  <Windows.h>
    #include  <tchar.h>
    #include  "Main.h"
    #include  "Form3.h"
    
    
    LRESULT fnForm3_OnPaint(WndEventArgs& Wea)
    {
     PAINTSTRUCT ps;
     HDC hDC;
    
     hDC=BeginPaint(Wea.hWnd,&ps);
     TextOut(hDC,0,0,_T("This Is Form3.  Not Only Does It Neither"),40);
     TextOut(hDC,0,16,_T("Hide Nor Disable The Main Window, But"),37);
     TextOut(hDC,0,32,_T("You'll Find That You Can Create As Many"),39);
     TextOut(hDC,0,48,_T("Of These As You Like By Continually"),35);
     TextOut(hDC,0,64,_T("Clicking The Bottom Button On The Main"),38);
     TextOut(hDC,0,80,_T("Form.  However, You'll Have To Drag One"),39);
     TextOut(hDC,0,96,_T("From On Top Of The Other Because They"),37);
     TextOut(hDC,0,112,_T("All Appear In The Same Location (I"),34);
     TextOut(hDC,0,128,_T("Changed That).  You May Further Note"),36);
     TextOut(hDC,0,144,_T("That Since These Windows Are Neither"),36);
     TextOut(hDC,0,160,_T("Disabled Nor Hidden At Any Time, You"),36);
     TextOut(hDC,0,176,_T("May Interact With Them Irregardless Of"),38);
     TextOut(hDC,0,192,_T("The State Of Form1 Or Form2. Pretty"),35);
     TextOut(hDC,0,208,_T("Neat, Don't You Think?"),22);
     EndPaint(Wea.hWnd,&ps);
    
     return 0;
    }
    
    
    LRESULT fnForm3_OnClose(WndEventArgs& Wea)
    {
     HWND hMain;
    
     MessageBox
     (
      Wea.hWnd,
      _T("Good Way To Release Any Resources, Memory, etc., You May Have Allocated"),
      _T("Window Close Report!"),
      MB_OK
     );
     hMain=(HWND)GetWindowLongPtr(Wea.hWnd,GWLP_USERDATA);
     EnableWindow(hMain,TRUE);
     DestroyWindow(Wea.hWnd);
     ShowWindow(hMain,TRUE);
    
     return 0;
    }
    
    
    LRESULT CALLBACK fnForm3_WndProc(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
    {
     WndEventArgs Wea;
    
     for(unsigned int i=0; i<dim(Form3EventHandler); i++)
     {
         if(Form3EventHandler[i].iMsg==msg)
         {
            Wea.hWnd=hWnd, Wea.lParam=lParam, Wea.wParam=wParam;
            return (*Form3EventHandler[i].fnPtr)(Wea);
         }
     }
    
     return (DefWindowProc(hWnd, msg, wParam, lParam));
    }
    Code:
    //Form3.h        //Needs to be included in Main.cpp because the WM_CREATE handler there
    #ifndef FORM3_H  //references fnForm3_WndProc as the Window Procedure for the Form1 Class.
    #define FORM3_H  //This would be needed to register the class.
    LRESULT CALLBACK fnForm3_WndProc(HWND, unsigned int, WPARAM, LPARAM);
    #endif
    A worthwhile addition to the above program would be modifications to enable resolution independence at altered user DPI (dots per inch) settings.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Program design and outlay in large projects
    By wiqxlzxsxj in forum C Programming
    Replies: 3
    Last Post: 01-19-2016, 08:07 PM
  2. How to handle very large number in C
    By suryak in forum C Programming
    Replies: 25
    Last Post: 12-17-2013, 06:35 PM
  3. Proper coding style on large projects
    By Verneg in forum C Programming
    Replies: 0
    Last Post: 03-27-2010, 06:13 PM
  4. Design of Large C Projects/Program
    By amrishpurohit in forum Tech Board
    Replies: 5
    Last Post: 09-03-2008, 01:29 AM
  5. Replies: 2
    Last Post: 09-16-2005, 11:44 PM

Tags for this Thread