Real glad you found that useful Abyssion. After I posted a link to that it occurred to me that a couple years back I made a slight but I think beneficial modification to that procedure that's not included in that link. Instead of defining the WndEventArgs parameter as a pointer to one of those as I have in that tutorial, i.e. lpWndEventArgs, I changed it to a reference, which is more of a C++ idiom I think. So here is that GetDlgItem() program with the text box and button I posted a couple posts ago redone in the style I now use with the reference parameter...
Code:
//Main.h
#ifndef Main_h
#define Main_h
#define dim(x) (sizeof(x) / sizeof(x[0]))
#define IDC_EDIT 2000
#define IDC_BUTTON 2005
struct WndEventArgs
{
HWND hWnd;
WPARAM wParam;
LPARAM lParam;
HINSTANCE hIns;
};
LRESULT fnWndProc_OnCreate (WndEventArgs& Wea);
LRESULT fnWndProc_OnCommand (WndEventArgs& Wea);
LRESULT fnWndProc_OnDestroy (WndEventArgs& Wea);
struct EVENTHANDLER
{
unsigned int iMsg;
LRESULT (*fnPtr)(WndEventArgs&);
};
const EVENTHANDLER EventHandler[]=
{
{WM_CREATE, fnWndProc_OnCreate},
{WM_COMMAND, fnWndProc_OnCommand},
{WM_DESTROY, fnWndProc_OnDestroy}
};
#endif
Code:
//Main.cpp
#include <windows.h>
#include <tchar.h>
#include "Form1.h"
LRESULT fnWndProc_OnCreate(WndEventArgs& Wea)
{
Wea.hIns=((LPCREATESTRUCT)Wea.lParam)->hInstance;
CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),WS_CHILD|WS_VISIBLE,25,35,255,25,Wea.hWnd,(HMENU)IDC_EDIT,Wea.hIns,0);
CreateWindowEx(0,_T("button"),_T("Press To Extract Text"),WS_CHILD|WS_VISIBLE,55,85,200,25,Wea.hWnd,(HMENU)IDC_BUTTON,Wea.hIns,0);
return 0;
}
LRESULT fnWndProc_OnCommand(WndEventArgs& Wea)
{
switch(LOWORD(Wea.wParam))
{
case IDC_BUTTON:
{
if(HIWORD(Wea.wParam)==BN_CLICKED)
{
TCHAR szBuffer[256];
GetWindowText(GetDlgItem(Wea.hWnd,IDC_EDIT),szBuffer,255);
MessageBox(Wea.hWnd,szBuffer,_T("Clicked Button!"),MB_OK);
}
}
break;
}
return 0;
}
LRESULT fnWndProc_OnDestroy(WndEventArgs& Wea)
{
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 hIns, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
TCHAR szClassName[]=_T("Form1");
WNDCLASSEX wc;
MSG messages;
HWND hWnd;
wc.lpszClassName=szClassName; wc.lpfnWndProc=fnWndProc;
wc.cbSize=sizeof (WNDCLASSEX); wc.style=0;
wc.hIcon=LoadIcon(NULL,IDI_APPLICATION); wc.hInstance=hIns;
wc.hIconSm=NULL; wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground=(HBRUSH)COLOR_BTNSHADOW; wc.cbWndExtra=0;
wc.lpszMenuName=NULL; wc.cbClsExtra=0;
RegisterClassEx(&wc);
hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,75,75,320,200,HWND_DESKTOP,0,hIns,0);
ShowWindow(hWnd,iShow);
while(GetMessage(&messages,NULL,0,0))
{
TranslateMessage(&messages);
DispatchMessage(&messages);
}
return messages.wParam;
}
If you compare it close you'll see what I mean. Not a major change but worth noting I believe. Also note above I used CreateWindow() calls for the child window controls without even assigning the returned HWND to a local HWND variable. Just throw it away. That's fine and doesn't interfere with the functionality of the program in any way.
About being sympathetic to me about my passion for minimizing the sizes of my programs, you shouldn't get me going on that! Its one of my pet topics! Just to give you an idea of what's possible with C++, I can build the above program to a 3,584 byte stand alone executable in 64 bit with MSVC using my own version of the C Standard Library which I've developed and named TCLib.lib. The command line compilation string would be this...
cl Main.cpp /O1 /Os /GS- /link TCLib.lib kernel32.lib user32.lib
In contrast, with standard linkages to the C runtime (LIBCMT.LIB) which is the default with Visual C++ the executable is 39,936 with VC15 from Visual Studio 2008 in 64 bit...
cl Main.cpp /O1 /Os /MT kernel32.lib user32.lib
The main thing I've worked on this year is the development of replacements for the C and C++ Standard Libraries so that I can avoid the bloat of modern software development.
Now, with regard to your question of how to persist objects not created at global/static scope across function calls, i.e., have them accessable throughout program run duration, any such needed object(s) I instantiate on the heap with new (or HeapAlloc()) during construction of my main program window in its WM_CREATE handler. Then, as with any data logically associated with the main program/window object, I store the pointer to that object as member data within the instantiated instance. The C ism of achieving this sort of OOP is not as elegant as with C++. What you have is the instantiated Class instance represented by the WNDCLASSEX object. One of the members of that object is...
WNDCLASSEX::cbWndExtra
...and here one can specify howsoever many extra bytes one needs to store whatever. The SetWindowLongPtr() and GetWindowLongPtr() functions are the getters/setters (accessors in OOP speak). A ridiculously simple example should suffice. The purpose of this program is to calculate and display in a GUI window the dimensions of a box (I should be able to retire on the proceeds geneated by this 'must have' killer app!). (And I see by your later reply to CodePlug that you already understand this stuff). So CBox.h and CBox.cpp are a Class modeling a box...
Code:
#ifndef CBox_h
#define CBox_h
class CBox
{
public:
CBox(double,double,double); //Constructor
~CBox(); //Destructor
double GetLength(); //m_Length accessor
double GetWidth(); //m_Width accessor
double GetHeight(); //m_Height accessor
double Volume(); //Returns Volume() of Box
private:
double m_Length;
double m_Width;
double m_Height;
};
#endif
Code:
//CBox.cpp
#include "CBox.h"
CBox::CBox(double dblLength, double dblWidth, double dblHeight)
{
this->m_Length=dblLength;
this->m_Width=dblWidth;
this->m_Height=dblHeight;
}
CBox::~CBox()
{
//destructor
}
double CBox::GetLength()
{
return this->m_Length;
}
double CBox::GetWidth()
{
return this->m_Width;
}
double CBox::GetHeight()
{
return this->m_Height;
}
double CBox::Volume()
{
return m_Length*m_Width*m_Height;
}
Our GUI main window represented by Main.cpp and Main.h instantiate one of these CBoxes in the WM_CREATE handler - fnWndProc_OnCreate(), which is the Constructor for the CBox_Window_Class RegisterClassEx()'ed down in WinMain(). And note it is indeed a C based Constructor as with C based OOP you'll inevitably have a constructor function such As CreateWindowEx() which returns a pointer to the newly created object. Further evidence that it is a Constructor can be seen by the fact that the code in the WM_CREATE handler is executing 'within' the CreateWindowEx() call in WinMain(), i.e., CreateWindowEx() down in WinMain() won't return and assign a HWND to hWnd in WinMain() until the WM_CREATE handler code returns with a 0. If a -1 is returned in fnWndProc_OnCreate() the CreateWindowEx() call in WinMain() will fail and hWnd will remain a NULL pointer.
But in fnWndProc_OnCreate() we declare a local stack based CBox* pointer pBox and create an instance of a CBox object with new that has sides 2.0, 3.0, and 4.0. We then assign this pointer virtual address to our main window instance's WNDCLASSEX::cbWndExtra bytes at offset zero. Then the local CBox* object goes out of scope and the pointer is destroyed but the CBox object lives on within the program's heap, simply because delete was never called on it....
Code:
// Main.cpp //C++ Object Oriented Program using native
#include <windows.h> //Windows C Api to display the dimensions
#include <stdio.h> //and volume of a class CBox object. Note
#include "Main.h" //that this program contains no global
#include "CBox.h" //variables.
LRESULT fnWndProc_OnCreate(WndEventArgs& Wea) //When the window object is created, create
{ //a CBox object and store/set a pointer to
CBox* pBox=NULL; //in the Window Class struct. If the
//memory allocation (new) fails, return -1
pBox=new CBox(2.0,3.0,4.0); //and program initialization will halt and
if(pBox) //WinMain() will exit. A WM_CREATE message
SetWindowLongPtr(Wea.hWnd,0,(LONG_PTR)pBox); //is received one time on Window creation.
else //The CreateWindow() call in WinMain() won't
return -1; //return until this procedure returns.
return 0;
}
LRESULT fnWndProc_OnPaint(WndEventArgs& Wea) //Windows will send a WM_PAINT message to
{ //a window when any part of the window
CBox* pBox=NULL; //becomes 'invalid' and needs to be
HFONT hFont,hTmp; //repainted. This will occur at program
PAINTSTRUCT ps; //start up and any time the window is
HDC hDC; //uncovered by another window. A pointer
//to the window's CBox object is stored
char szBuffer[128]; //in the window's Class Extra Bytes
hDC=BeginPaint(Wea.hWnd,&ps); //( .cbWndExtra ), and is here accessed
SetBkMode(hDC,TRANSPARENT); //using the Api function GetWindowLong().
hFont=CreateFont //To draw to a window in a WM_PAINT...
(
28,0,0,0,FW_HEAVY,0,0,0,ANSI_CHARSET,OUT_DEFAULT_PRECIS, //handler one may use
CLIP_DEFAULT_PRECIS,PROOF_QUALITY,DEFAULT_PITCH,"Courier New" //TextOut(), but that
); //function requires a
hTmp=(HFONT)SelectObject(hDC,hFont); //handle to a device
pBox=(CBox*)GetWindowLongPtr(Wea.hWnd,0); //context obtainable
sprintf(szBuffer,"Box.GetLength() = %6.2f",pBox->GetLength()); //with BeginPaint().
TextOut(hDC,25,30,szBuffer,strlen(szBuffer)); //The C Runtime func
sprintf(szBuffer,"Box.GetWidth() = %6.2f",pBox->GetWidth()); //sprintf can be used
TextOut(hDC,25,60,szBuffer,strlen(szBuffer)); //to output a text string
sprintf(szBuffer,"Box.GetHeight() = %6.2f",pBox->GetHeight()); //to an output buffer,
TextOut(hDC,25,90,szBuffer,strlen(szBuffer)); //and then TextOut()
sprintf(szBuffer,"Box.Volume() = %6.2f",pBox->Volume()); //will draw the text.
TextOut(hDC,25,120,szBuffer,strlen(szBuffer)); //A FONT is a GDI object
SelectObject(hDC,hTmp); //and needs to be
DeleteObject(hFont); //released to prevent
EndPaint(Wea.hWnd,&ps); //memory leaks. Finally
//EndPaint() needs to be
return 0; //called to terminate use
} //of the device context.
LRESULT fnWndProc_OnDestroy(WndEventArgs& Wea) //When you click the 'x' button in the
{ //window's title bar Windows will send
CBox* pBox=NULL; //WM_CLOSE and WM_DESTROY messages (among
//others) to the window's Window Procedure.
pBox=(CBox*)GetWindowLongPtr(Wea.hWnd,0); //This program needs to delete the CBox
if(pBox) //object a pointer to which is stored in
delete pBox; //the program's .cbWndExtra bytes. Then
PostQuitMessage(0); //PostQuitMessage() needs to be called so
//the message pump in WinMain() will exit
return 0; //and the program will end.
}
LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam,LPARAM lParam)
{
WndEventArgs Wea; //This procedure loops through the EVENTHANDER array
//of structs to try to make a match with the msg parameter
for(unsigned int i=0; i<dim(EventHandler); i++) //of the WndProc. If a match is made the event handling
{ //procedure is called through a function pointer -
if(EventHandler[i].iMsg==msg) //(EventHandler[i].fnPtr). If no match is found the
{ //msg is passed onto DefWindowProc().
Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
return (*EventHandler[i].fnPtr)(Wea);
}
}
return (DefWindowProc(hwnd, msg, wParam, lParam));
}
int WINAPI WinMain(HINSTANCE hIns, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
char szClassName[]="CBox_Window_Class";
WNDCLASSEX wc;
HWND hWnd=NULL;
MSG messages;
wc.lpszClassName=szClassName; wc.lpfnWndProc=fnWndProc;
wc.cbSize=sizeof (WNDCLASSEX); wc.style=0;
wc.hIcon=LoadIcon(NULL,IDI_APPLICATION); wc.hInstance=hIns;
wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION); wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground=(HBRUSH)COLOR_BTNSHADOW; wc.cbWndExtra=sizeof(void*);
wc.lpszMenuName=NULL; wc.cbClsExtra=0;
RegisterClassEx(&wc);
hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,100,100,400,300,HWND_DESKTOP,0,hIns,0);
ShowWindow(hWnd,iShow);
while(GetMessage(&messages,NULL,0,0))
{
TranslateMessage(&messages);
DispatchMessage(&messages);
}
return messages.wParam;
}
Code:
//Main.h
#ifndef Main_h
#define Main_h
#define dim(x) (sizeof(x) / sizeof(x[0]))
struct WndEventArgs
{
HWND hWnd;
WPARAM wParam;
LPARAM lParam;
HINSTANCE hIns;
};
LRESULT fnWndProc_OnCreate (WndEventArgs& Wea);
LRESULT fnWndProc_OnPaint (WndEventArgs& Wea);
LRESULT fnWndProc_OnDestroy (WndEventArgs& Wea);
struct EVENTHANDLER
{
unsigned int iMsg;
LRESULT (*fnPtr)(WndEventArgs&);
};
const EVENTHANDLER EventHandler[]=
{
{WM_CREATE, fnWndProc_OnCreate},
{WM_PAINT, fnWndProc_OnPaint},
{WM_DESTROY, fnWndProc_OnDestroy}
};
#endif
But to continue the story, when fnWndProc_OnCreate() exits the main program window is still not visible, although a lot of construction activity has taken place. When the Window Procedure finally retrieves a WM_PAINT message program execution will proceed to fnWndProc_OnPaint(), where another local CBox* object is created. But a CBox* isn't a CBox. The real instantiated CBox object created in WM_CREATE handler code is in the heap, but a pointer to it (its address) is stored at offset zero in the WNDCLASSEX::cbWndExtra bytes. We retrieve it like so...
Code:
pBox=(CBox*)GetWindowLongPtr(Wea.hWnd,0);
...and have a valid CBox* again in local pBox (local to WM_PAINT code). We can then TextOut() any of the information about our CBox such as the length, width, height and volume. When WM_PAINT handler code goes out of scope so does pBox again, but the actual CBox object in the heap remains valid.
When you [x] out in fnWndProc_OnDestroy the pointer to the CBox is reset and we can call delete on it to reclaim the memory and prevent memory leaks. Of course it would have been reclaimed anyway when the program terminated, but we always want to do right. So I believe that answers your question about object lifetime and scope, OOP issues, and eliminating globals/statics.
By the way, the above program can be compiled and linked against LIBCMT.LIB (the C Runtime) with this command line...
Code:
cl Main.cpp CBox.cpp /O1 /Os /MT kernel32.lib user32.lib gdi32.lib
...and in VC15 from Visual Studio 2008 I'm seeing 74,240 bytes for x64. For MinGW with the 4.8 compiler I'm seeing a whooping 188,928 bytes. That's a crying shame. The MinGW 32 bit compilers used to produce small executables because they link against msvcrt.dll (VC6 Runtime) which is a Windows system component, but for quite some time now they are linking against that winpthreads lib and their executable sizes have exploded. But the situation with Microsoft compilers is steadily worsening too as VC19 (Visual Studio 2015 - the latest) builds of the above have crept up to about 90,000 bytes. So the situation keeps getting worse. That's one of the reasons I've sunk most of this year into developing my replacements for the C and C++ Standard Libraries. The above program built with my 'Tiny C Lib' (TCLib.lib) comes in at a mere 6,144 bytes in x64. Here's the command line string for that...
Code:
cl Main.cpp CBox.cpp /O1 /Os /GS- /link TCLib.lib kernel32.lib user32.lib gdi32.lib
Abyssion said...
It seems that we share some similar values with respect to code organisation and minimal resource usage. I also am a huge fan of the way the Windows API was constructed and, in fact, it made me wonder whether to switch to C instead of C++. (Having said that though, I'd be lost without the std::string and std:vector vector classes, so maybe not.)
I agree with you there too but the situation is somewhat different with me. I kind of got started with C++ before the present String Class in the C++ Standard Library was adopted. In those early years everybody was building their own String Classes and that's what happened with me. I just kept the one I created for myself and kept working with it, rewriting it, learning from it, and improving it over the years. And the one I had added a lot less to program size than the one incorporated into the C++ Standard Library, and it had more functionality, so I stuck with it.
In terms of container objects such as vectors, I just simply never used them. I always preferred the models from BASIC family languages of dynamic multidimensional arrays. For example, the PowerBASIC language allows up to 8 dimensions all of which are dynamic, i.e., the arrays can be dimensioned (memory allocated) at runtime and also resized. The way I incorporated this into my C++ work was through a templated multi-dimension Array Class where I've overloaded the '(' and ')' operators so that I can use BASIC like syntax instead of C/C++ type syntax, e.g., var[][]. So like for a four dimensional array it might look like this...
...instead of this C ism...
So I've somewhat mis-stated the situation with the C++ Standard Library and my alternate library development work. I didn't develop my own version of it like I did with the C Standard Library; I simply wrote it off as a total loss and used my own String Class which I've always had and developed my templated multidimensional array class for containers. Things like linked lists I already had from my C coding days, and one thing about using one's own library code that one has written oneself is that one doesn't have to study tutorials on how to use it! Let me provide a last simple example of what I'm talking about in terms of how easy it is to free oneself of the bloat introduced by the C++ Standard Library. Let's take the case of parsing a simple comma delimited string such as this...
Code:
std::string strLine = "Zero, One, Two, Three, Four, Five, Six";
I believe the cannonical way of doing it in C++ would be something like this with the output following...
Code:
// cl StdLibParse1.cpp /O1 /Os /MT /EHsc
// 200,192 Bytes
#include <iostream>
#include <sstream>
int main()
{
std::string input = "Zero, One, Two, Three, Four, Five, Six";
std::istringstream ss(input);
std::string token;
while(std::getline(ss, token, ','))
{
std::cout << token << '\n';
}
return 0;
}
#if 0
Output:
=======
Zero
One
Two
Three
Four
Five
Six
#endif
So we've got 200,000 bytes to parse a string. Using my own library code I have this (My String Class not shown - but represented by #include Strings.h)...
Code:
// cl Demo5.cpp Strings.cpp /O1 /Os /GS- /Zc:sizedDealloc- /link TCLib.lib kernel32.lib
// 4,608 Bytes With INTEGRAL_CONVERSIONS, FLOATING_POINT_CONVERSIONS, And FORMATTING Remmed Out
// 5,120 Bytes With Full String Class
#define UNICODE
#define _UNICODE
#include <windows.h>
#include "stdlib.h"
#include "stdio.h"
#include "tchar.h"
#include "Strings.h"
extern "C" int _fltused=1;
int main()
{
int iParseCount=0;
String* pStrs=NULL;
String s1;
s1=_T("Zero, One, Two, Three, Four, Five");
s1.Print(_T("s1 = "),true);
iParseCount=s1.ParseCount(_T(','));
_tprintf(_T("iParseCount = %d\n\n"),iParseCount);
pStrs=new String[iParseCount];
s1.Parse(pStrs, _T(','), iParseCount);
for(int i=0; i<iParseCount; i++)
{
pStrs[i].LTrim();
pStrs[i].Print(true);
}
delete [] pStrs;
getchar();
return 0;
}
// Output:
// ==========================
// s1 = Zero, One, Two, Three, Four, Five
// iParseCount = 6
// Zero
// One
// Two
// Three
// Four
// Five
Its close to the same program but I see I have added a few things in the above version not in the one using std::string, but in any case its 40 times smaller with a saving of about 195,000 bytes. Both programs are x64 compiled with VC19 from Visual Studio 2015. Actually, the magic of saving all those bytes isn't hard to locate. Here is the entirety of my String::Parse() method excerpted from my String Class...
Code:
void String::Parse(String* pStr, TCHAR delimiter, size_t iParseCount)
{
TCHAR* pBuffer=new TCHAR[this->iLen+1];
if(pBuffer)
{
TCHAR* p=pBuffer;
TCHAR* c=this->lpBuffer;
while(*c)
{
if(*c==delimiter)
*p=0;
else
*p=*c;
p++, c++;
}
*p=0, p=pBuffer;
for(size_t i=0; i<iParseCount; i++)
{
pStr[i]=p;
p=p+pStr[i].iLen+1;
}
delete [] pBuffer;
}
}
Not much magic there. The top while loop copies to another buffer all the characters of the one pointed to by this substituting NULLs when it encounters a delimiter. The end result of that is a buffer containing an array of null terminated strings. The second loop simply collects them, delete is called on the 'work' buffer, and the method returns. I think any decent C coder could come up with that. So I've got a 4 or 5 k 64 bit stand alone executable. Is there any RAII or structured exception handling? Nope. None. There are no free lunches in this world. If you've got to have that sort of thing then you are going to have to pay for it in code bloat. Or use C#. Or use VB.NET. Whatever. So its a difficult personal choice that has to be made.
Getting back to CodePlugs point about tricky. I finally got what you were saying. Yep! I agree with you. A good many years ago I spent like a month or two working on writing my own C++ Class Framework. I've got a ClassFrameWorks directory under my C:\Code\CodeBlks directory with about 45 subfolders under it to prove it! And I remember getting stuck bad just on what you were talking about for several hours until I finally got something working that I was satisfied with. I forget the details but somehow or other I managed to set up the connection between the this pointer and HWNDs without using hooks or anything like that. So when I made that comment about 'simple as dirt' I meant obtaining a HWND from Windows using GetDlgItem() - not constructing a Class Framework and getting all the internal wiring worked out. Real sorry if I came off as insulting.