Thread: Control handle declaration conventions in large WinAPI projects

  1. #16
    Registered User
    Join Date
    Dec 2010
    Location
    Trinidad, CO (log cabin in middle of nowhere)
    Posts
    148
    Addressing your questions in reverse order, since you seem to be a pretty knowledgible coder Abyssion and wanting to improve your knowledge of Win32 SDK, I'd really recommend looking into how to use that last parameter of the CreateWindowEx call. It is typed as a LPVOID from MSDN docs, i.e., a void pointer....

    Code:
    LPVOID    lpParam
    What it is used for is critically important, as it serves as a conduit for transferring data from the point of a CreateWindow() or CreateWindowEx() call into the newly created window. The WM_CREATE handler (which I've previously stated is essentially a C based Constructor call) is critically implicated here, because in WM_CREATE the LPARAM is a pointer to a CREATESTRUCT object like so...

    Code:
    typedef struct tagCREATESTRUCT {
      LPVOID    lpCreateParams;
      HINSTANCE hInstance;
      HMENU     hMenu;
      HWND      hwndParent;
      int       cy;
      int       cx;
      int       y;
      int       x;
      LONG      style;
      LPCTSTR   lpszName;
      LPCTSTR   lpszClass;
      DWORD     dwExStyle;
    } CREATESTRUCT, *LPCREATESTRUCT;
    Look at those members carefully. You should recognize them. They are all the parameters of the CreateWindowEx() call that created the window, but in reverse order - no doubt internal Windows OS code simply retrieved them from the stack and that's the order they would ha ve been in. Whatever you stick in that last parameter of the CreateWindowEx() call then can be retrieved in WM_CREATE as its the 1st member of the CREATESTRUCT object.

    This is highly significant. As you are aware, according to good programming practice and certainly according to the principals of OOP, data should only be accessable from those parts of code that need that particular piece of data. So that lpCreateParams (I guess the older documentation specified it that way, which is more descriptive than the present listing of lpParam) provides a way for moving a piece of data (or multiple piecies of data) from one part or module of an application to another.

    Take that little demo above with my CBox class. You and CodePlug were having a discussion I believe about instantiating objects in WinMain(). With reference to that, in my example I could have declared and instantiated a local stack based CBox object in WinMain instead of creating one on the heap with new like I did in the WM_CREATE code. Of course, if one were to do that, the CBox object could have been made available to code within the Window Procedure simply by sticking a pointer to it, i.e.,,

    Code:
    WinMain(....)
    {
     ...
     CBox box(2.0, 3.0, 4.0);
      ...
     SetWindowLongPtr(hWnd, 0, (LONG_PTR)&box);
     ...
    }
    ...in the WNDCLASSEX::cbWndExtra bytes.

    But the other way of doing it would be to simply place the address of the local CBox object &box in that last parameter of the CreateWindowEx() call. Personally, like I said, I hate to put anymore code in WinMain() than I need to, but that's just personal preference. I know very advanced coders who do that when they feel like it. Its just that I don't.

    Where that lpCreateParams really comes into its own is in the creation of Windows Custom Controls. Take grid controls for example. If you were to create a grid control you would provide the user of the control with documentation as to how to set up the grid. For example, the grid could be instantiated through a CreateWindowEx() call where the szClassName parameter migh be "grid". But how would you specify such needed information as the number of columns in the grid, the number of rows, the header row captions, etc.?

    What you could do would be provide a declaration of a struct in the header file to contain this custom information, and pass the struct into the grid through that last parameter of the CreateWindowEx() call. Then code internal to the grid in its WM_CREATE code would construct the grid as the user specified. Give me a bit and I'll hunt up some of the best links I can find to help you see this...

    continued...
    Last edited by freddie; 06-20-2016 at 01:17 PM. Reason: line break

  2. #17
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    >> 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.
    This line of thinking is great for a truly embedded developer, but it is a bit ridiculous for a Desktop/Server developer. 90K is absolutely nothing - not to mention "rolling your own" just makes your code less maintainable and more prone to bugs. The MS CRT and C++ libs are used and tested by millions. The C++ Standard libs have a concrete set of interfaces and behaviors that millions know and understand. This is above and beyond important when you get into software development where you are not the only one reading and maintaining a code base.

    The size of your exe/dll isn't even all that important. Instead, you should use something like process explorer and look at the "Private Bytes". When you link to the DLL based CRT, it is highly likely that other processes have already loaded those DLL's into memory, so all the physical pages containing the code of those DLL's are shared.

    gg

  3. #18
    Registered User
    Join Date
    Dec 2010
    Location
    Trinidad, CO (log cabin in middle of nowhere)
    Posts
    148
    This line of thinking is great for a truly embedded developer, but it is a bit ridiculous for a Desktop/Server developer. 90K is absolutely nothing - not to mention "rolling your own" just makes your code less maintainable and more prone to bugs. The MS CRT and C++ libs are used and tested by millions. The C++ Standard libs have a concrete set of interfaces and behaviors that millions know and understand. This is above and beyond important when you get into software development where you are not the only one reading and maintaining a code base.

    The size of your exe/dll isn't even all that important. Instead, you should use something like process explorer and look at the "Private Bytes". When you link to the DLL based CRT, it is highly likely that other processes have already loaded those DLL's into memory, so all the physical pages containing the code of those DLL's are shared.
    I agree with all that gg. That project was/is simply something that interested me (still does), and I learned a good bit about compilers and linkers while working on it.

  4. #19
    Registered User
    Join Date
    Dec 2010
    Location
    Trinidad, CO (log cabin in middle of nowhere)
    Posts
    148

    Createstruct

    I guess I've examples of CREATESTRUCT usages scattered all over Jose Roca's Forum on my board, both in C++ and PowerBASIC. But here's a little example I just made in C++. What we'll do is create a rudimentary 'Custom Control' - perhaps with a grid control in mind, and in the blank control window which we'll create with a CreateWindowEx() call specifying our custom class which I've named "Grid", we'll TextOut() all the parameters of the CreateWindowEx() call that created the 'grid' custom control. Note the grid or custom control, if you will allow me the term, is a child window control of the main or parent window.

    In thinking about this keep in mind all I've said about CreateWindow() calls and WM_CREATE handlers being essentially C based constructor calls. With a C++ class one can pass data into any of many possible overloaded constructors. This information is used within the C++ Constructor to create the instance. What I'm showing here is the way or mechanism data is passed into one of these C based constructor calls using a CREATESTRUCT object.

    Let's take the concrete example of creating a grid control. One would hope to be able to instantiate such a control in the same manner as one would instantiate an edit control, button, listview Common Control, or anything else for that matter, and that would be through a CreateWindowEx() function call. But if you've ever used grids or viewed two dimensional tabular data, you know that such user interface elements have a specific number of rows, columns, and other properties such as fonts, etc. How would such information be passed into the creation code, i.e., the constructor, of a grid, or in general, any user created custom user interface element?

    Here is how it works. One would create an object such as something like this for a grid control...

    Code:
    struct GridData
    {
     int iRows;
     int iCols;
    };
    Also, one would have to register a Custom Window Class for the Custom Control or Grid. That could be registered in the WM_CREATE handler for the main window class. Here is the entirety of that for the example app I've developed...

    Code:
    long fnWndProc_OnCreate(WndEventArgs& Wea)
    {
     TCHAR szClassName[]=_T("Grid");
     HWND hGrid=NULL;
     WNDCLASSEX wc;
     GridData gd;
    
     #ifdef Debug
     fprintf(fp,"  Entering fnWndProc_OnCreate()\n");
     fprintf(fp,"    Wea.hWnd = %Iu\n",(size_t)Wea.hWnd);
     #endif
     Wea.hIns=((LPCREATESTRUCT)Wea.lParam)->hInstance;
     memset(&wc,0,sizeof(wc));
     wc.lpszClassName=szClassName;                           wc.lpfnWndProc=fnGridProc;
     wc.cbSize=sizeof (WNDCLASSEX);                          wc.style=0;
     wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);                wc.hInstance=Wea.hIns;
     wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION);             wc.hCursor=LoadCursor(NULL,IDC_ARROW);
     wc.hbrBackground=(HBRUSH)GetStockObject(LTGRAY_BRUSH);  wc.cbWndExtra=sizeof(void*);
     wc.lpszMenuName=NULL;                                   wc.cbClsExtra=0;
     RegisterClassEx(&wc);
     gd.iRows = 15;
     gd.iCols =  6;
     #ifdef Debug
     fprintf(fp,"    &gd = %p\n",&gd);
     #endif
     hGrid=CreateWindowEx(WS_EX_CLIENTEDGE,szClassName,_T("Text For A Grid Or Whatever"),WS_CHILD|WS_VISIBLE,15,15,575,375,Wea.hWnd,(HMENU)IDC_GRID,Wea.hIns,&gd);
     #ifdef Debug
     fprintf(fp,"    hGrid = %Iu\n",(size_t)hGrid);
     fprintf(fp,"  Leaving fnWndProc_OnCreate()\n\n");
     #endif
    
     return 0;
    }
    As you can see above, the custom class is named "Grid". And there is a local stack based instance of a GridData object created, and with that instance I set the rows to 15 and the columns to 6. Then I put the address of that object into the last parameter of the CreateWindowEx() call that creates an instance of the grid whose parent is the main program window. Note the style is WS_CHILD | WS_VISIBLE - same as I would do for any child window control.

    Now, the program has two Window Procedures by necessity. There is a Window Procedure for the main program window and another for our custom control class named "grid". No way of getting out of this. To register a Window Class one of the most important fields of the WNDCLASSEX object is the lpfnWndProc. That would be the Window Procedure for the Custom Control or grid.

    When that CreateWindowEx() call on the grid class occurs, in the WM_CREATE handler which is fnGridProc_OnCreate() will be received a pointer to a CREATESTRUCT object, and that object will contain every parameter of the CreateWindowEx() call that instantiated the grid - including the address (a pointer to) of that GridData object from fnWndProc_OnCreate(). Here is that code...

    Code:
    long fnGridProc_OnCreate(WndEventArgs& Wea)
    {
     MyCreateStruct* pMyCreateStruct=NULL;
     CREATESTRUCT* pCreateStruct=NULL;
     GridData* pGridData=NULL;
     HANDLE hHeap=NULL;
    
     #ifdef Debug
     fprintf(fp,"    Entering fnGridProc_OnCreate()\n");
     fprintf(fp,"      Wea.hWnd = %Iu\n",(size_t)Wea.hWnd);
     #endif
     pCreateStruct=(CREATESTRUCT*)Wea.lParam;
     Wea.hIns=pCreateStruct->hInstance;
     hHeap=GetProcessHeap();
     pMyCreateStruct=(MyCreateStruct*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,sizeof(MyCreateStruct));
     if(pMyCreateStruct)
     {
        pGridData=(GridData*)pCreateStruct->lpCreateParams;
        pMyCreateStruct->GrdDta.iRows = pGridData->iRows;
        pMyCreateStruct->GrdDta.iCols = pGridData->iCols;
        pMyCreateStruct->hInstance    = pCreateStruct->hInstance;
        pMyCreateStruct->hMenu        = pCreateStruct->hMenu;
        pMyCreateStruct->hwndParent   = pCreateStruct->hwndParent;
        pMyCreateStruct->cy           = pCreateStruct->cy;
        pMyCreateStruct->cx           = pCreateStruct->cx;
        pMyCreateStruct->y            = pCreateStruct->y;
        pMyCreateStruct->x            = pCreateStruct->x;
        pMyCreateStruct->style        = pCreateStruct->style;
        pMyCreateStruct->dwExStyle    = pCreateStruct->dwExStyle;
        _tcscpy(pMyCreateStruct->szCaption,pCreateStruct->lpszName);
        _tcscpy(pMyCreateStruct->szClassName,pCreateStruct->lpszClass);
        SetWindowLongPtr(Wea.hWnd,0,(LONG_PTR)pMyCreateStruct);
        #ifdef Debug
        fprintf(fp,"      pMyCreateStruct->GrdDta.iRows = %d\n",pMyCreateStruct->GrdDta.iRows);
        fprintf(fp,"      pMyCreateStruct->GrdDta.iCols = %d\n",pMyCreateStruct->GrdDta.iCols);
        fprintf(fp,"      pCreateStruct           = %Iu\n",(size_t)pCreateStruct);
        fprintf(fp,"      pMyCreateStruct         = %Iu\n",(size_t)pMyCreateStruct);
        fprintf(fp,"      pCreateStruct->lpszName = %s\n",pCreateStruct->lpszName);
        #endif
     }
     #ifdef Debug
     fprintf(fp,"    Leaving fnGridProc_OnCreate()\n");
     #endif
    
     return 0;
    }
    Things get a little 'dicey' here as what I want to do is output the values of all those parameters in the WM_PAINT code for the Custom Control or "Grid". The problem with that is that I only have a pointer to a CREATESTRUCT object during WM_CREATE code. The custom control or grid isn't visible at the point of that call, and long before the WM_PAINT call comes in that pointer to the CREATESTRUCT data is gone. Here is what a CREATESTRUCT looks like...

    Code:
    struct CREATESTRUCT 
    {  
        LPVOID    lpCreateParams; 
        HINSTANCE hInstance; 
        HMENU     hMenu; 
        HWND      hwndParent; 
        int       cy; 
        int       cx; 
        int       y; 
        int       x; 
        LONG      style; 
        LPCTSTR   lpszName; 
        LPCTSTR   lpszClass; 
        DWORD     dwExStyle; 
    };
    So all we can do if we want to 'persist' that data for later output in WM_PAINT with TextOut() is copy it to another location in memory. And we'll have to allocate memory for that and use a pointer to it. But there's even more bad news and complicating factors. We can't save the data in the exact form of the CREATESTRUCT object because three of those members are actually pointers to stack memory that was valid at the point of the CreateWindowEx() call on the grid class, but will be used for something else at the point of a WM_PAINT call. Those three problematic members are lpCreateParams, lpszName, and lpszClass. So we've got to create a new object with actual string buffers to copy the lpszName and lpszClass members to. Something like so...

    Code:
    struct MyCreateStruct
    {
     GridData  GrdDta;
     HINSTANCE hInstance;
     HMENU     hMenu;
     HWND      hwndParent;
     int       cy;
     int       cx;
     int       y;
     int       x;
     LONG      style;
     TCHAR     szCaption[64];
     TCHAR     szClassName[16];
     DWORD     dwExStyle;
    };
    Note above I've placed a GridData object within that struct where the lpCreateParams was in a CREATESTRUCT, and I've provided a 64 character and a 16 character buffer for the lpszName and lpszClass members of CREATESTRUCT, and in our WM_CREATE code for the grid we'll have to copy those strings to this alternate memory. You can see those strcpy() operations above in fnGridProc_OnCreate(). And of course a memory allocation for a MyCreateStruct object had to be made, and I used HeapAlloc() for that, and saved the pointer at offset zero in the WNDCLASSEX::cbWndExtra bytes.

    Here's the whole program, first lpCreateParams.h...

    Code:
    // lpCreateParams.h
    #ifndef lpCreateParams_h
    #define lpCreateParams_h
    
    #define IDC_GRID            2000
    #define dim(x) (sizeof(x) / sizeof(x[0]))
    
    struct WndEventArgs
    {
     HWND                         hWnd;
     WPARAM                       wParam;
     LPARAM                       lParam;
     HINSTANCE                    hIns;
    };
    
    struct EVENTHANDLER
    {
     unsigned int                 iMsg;
     long                         (*fnPtr)(WndEventArgs&);
    };
    
    long fnWndProc_OnCreate       (WndEventArgs& Wea);
    long fnWndProc_OnDestroy      (WndEventArgs& Wea);
    
    const EVENTHANDLER EventHandler[]=
    {
     {WM_CREATE,                  fnWndProc_OnCreate},
     {WM_DESTROY,                 fnWndProc_OnDestroy}
    };
    
    long fnGridProc_OnCreate      (WndEventArgs& Wea);
    long fnGridProc_OnPaint       (WndEventArgs& Wea);
    long fnGridProc_OnDestroy     (WndEventArgs& Wea);
    
    const EVENTHANDLER GridEventHandler[]=
    {
     {WM_CREATE,                  fnGridProc_OnCreate},
     {WM_PAINT,                   fnGridProc_OnPaint},
     {WM_DESTROY,                 fnGridProc_OnDestroy}
    };
    
    struct GridData
    {
     int iRows;
     int iCols;
    };
    
    
    struct MyCreateStruct
    {
     GridData  GrdDta;
     HINSTANCE hInstance;
     HMENU     hMenu;
     HWND      hwndParent;
     int       cy;
     int       cx;
     int       y;
     int       x;
     LONG      style;
     TCHAR     szCaption[64];
     TCHAR     szClassName[16];
     DWORD     dwExStyle;
    };
    
    #endif
    And Main.cpp...

    Code:
    //Main.cpp
    #include <windows.h>
    #include <stdio.h>
    #include <tchar.h>
    #include "lpCreateParams.h"
    #define Debug
    #ifdef Debug
    FILE* fp=NULL;
    #endif
    
    
    long fnGridProc_OnCreate(WndEventArgs& Wea)
    {
     MyCreateStruct* pMyCreateStruct=NULL;
     CREATESTRUCT* pCreateStruct=NULL;
     GridData* pGridData=NULL;
     HANDLE hHeap=NULL;
    
     #ifdef Debug
     fprintf(fp,"    Entering fnGridProc_OnCreate()\n");
     fprintf(fp,"      Wea.hWnd = %Iu\n",(size_t)Wea.hWnd);
     #endif
     pCreateStruct=(CREATESTRUCT*)Wea.lParam;
     Wea.hIns=pCreateStruct->hInstance;
     hHeap=GetProcessHeap();
     pMyCreateStruct=(MyCreateStruct*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,sizeof(MyCreateStruct));
     if(pMyCreateStruct)
     {
        pGridData=(GridData*)pCreateStruct->lpCreateParams;
        pMyCreateStruct->GrdDta.iRows = pGridData->iRows;
        pMyCreateStruct->GrdDta.iCols = pGridData->iCols;
        pMyCreateStruct->hInstance    = pCreateStruct->hInstance;
        pMyCreateStruct->hMenu        = pCreateStruct->hMenu;
        pMyCreateStruct->hwndParent   = pCreateStruct->hwndParent;
        pMyCreateStruct->cy           = pCreateStruct->cy;
        pMyCreateStruct->cx           = pCreateStruct->cx;
        pMyCreateStruct->y            = pCreateStruct->y;
        pMyCreateStruct->x            = pCreateStruct->x;
        pMyCreateStruct->style        = pCreateStruct->style;
        pMyCreateStruct->dwExStyle    = pCreateStruct->dwExStyle;
        _tcscpy(pMyCreateStruct->szCaption,pCreateStruct->lpszName);
        _tcscpy(pMyCreateStruct->szClassName,pCreateStruct->lpszClass);
        SetWindowLongPtr(Wea.hWnd,0,(LONG_PTR)pMyCreateStruct);
        #ifdef Debug
        fprintf(fp,"      pMyCreateStruct->GrdDta.iRows = %d\n",pMyCreateStruct->GrdDta.iRows);
        fprintf(fp,"      pMyCreateStruct->GrdDta.iCols = %d\n",pMyCreateStruct->GrdDta.iCols);
        fprintf(fp,"      pCreateStruct           = %Iu\n",(size_t)pCreateStruct);
        fprintf(fp,"      pMyCreateStruct         = %Iu\n",(size_t)pMyCreateStruct);
        fprintf(fp,"      pCreateStruct->lpszName = %s\n",pCreateStruct->lpszName);
        #endif
     }
     #ifdef Debug
     fprintf(fp,"    Leaving fnGridProc_OnCreate()\n");
     #endif
    
     return 0;
    }
    
    
    long fnGridProc_OnPaint(WndEventArgs& Wea)
    {
     MyCreateStruct* pMyCreateStruct=NULL;
     TCHAR szBuffer[80], szTmp[24];
     HFONT hFont=NULL;
     HFONT hTmp=NULL;
     PAINTSTRUCT ps;
     int iBkGround;
     HDC hDC=NULL;
    
     #ifdef Debug
     fprintf(fp,"  Entering fnGridProc_OnPaint()\n");
     fprintf(fp,"    Wea.hWnd = %Iu\n",(size_t)Wea.hWnd);
     #endif
     hDC=BeginPaint(Wea.hWnd,&ps);
     iBkGround=SetBkMode(hDC,TRANSPARENT);
     pMyCreateStruct=(MyCreateStruct*)GetWindowLongPtr(Wea.hWnd,0);
     if(pMyCreateStruct)
     {
        hFont=CreateFont(14,0,0,0,FW_HEAVY,0,0,0,ANSI_CHARSET,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,PROOF_QUALITY,DEFAULT_PITCH,"Lucida Console");
        if(hFont)
        {
           hTmp=(HFONT)SelectObject(hDC,hFont);
    
           _tcscpy(szBuffer,_T("pMyCreateStruct->GrdDta.iRows = "));       // GridData::iRows
           _stprintf(szTmp,_T("%d"),pMyCreateStruct->GrdDta.iRows);
           _tcscat(szBuffer,szTmp);
           TextOut(hDC,0,0,szBuffer,_tcslen(szBuffer));
    
           _tcscpy(szBuffer,_T("pMyCreateStruct->GrdDta.iCols = "));       // GridData::iCols
           _stprintf(szTmp,_T("%d"),pMyCreateStruct->GrdDta.iCols);
           _tcscat(szBuffer,szTmp);
           TextOut(hDC,0,20,szBuffer,_tcslen(szBuffer));
    
           _tcscpy(szBuffer,_T("pMyCreateStruct->hInstance    = "));       // pCreateStruct->hInstance
           _stprintf(szTmp,_T("%Iu"),(size_t)pMyCreateStruct->hInstance);
           _tcscat(szBuffer,szTmp);
           TextOut(hDC,0,40,szBuffer,_tcslen(szBuffer));
    
           _tcscpy(szBuffer,_T("pMyCreateStruct->hMenu        = "));       // pCreateStruct->hMenu
           _stprintf(szTmp,_T("%Iu"),(size_t)pMyCreateStruct->hMenu);
           _tcscat(szBuffer,szTmp);
           TextOut(hDC,0,60,szBuffer,_tcslen(szBuffer));
    
           _tcscpy(szBuffer,_T("pMyCreateStruct->hParent      = "));       // pCreateStruct->hwndParent
           _stprintf(szTmp,_T("%Iu"),(size_t)pMyCreateStruct->hwndParent);
           _tcscat(szBuffer,szTmp);
           TextOut(hDC,0,80,szBuffer,_tcslen(szBuffer));
    
           _tcscpy(szBuffer,_T("pMyCreateStruct->cy           = "));       // pCreateStruct->cy
           _stprintf(szTmp,_T("%d"),pMyCreateStruct->cy);
           _tcscat(szBuffer,szTmp);
           TextOut(hDC,0,100,szBuffer,_tcslen(szBuffer));
    
           _tcscpy(szBuffer,_T("pMyCreateStruct->cx           = "));       // pCreateStruct->cx
           _stprintf(szTmp,_T("%d"),pMyCreateStruct->cx);
           _tcscat(szBuffer,szTmp);
           TextOut(hDC,0,120,szBuffer,_tcslen(szBuffer));
    
           _tcscpy(szBuffer,_T("pMyCreateStruct->y            = "));       // pCreateStruct->y
           _stprintf(szTmp,_T("%d"),pMyCreateStruct->y);
           _tcscat(szBuffer,szTmp);
           TextOut(hDC,0,140,szBuffer,_tcslen(szBuffer));
    
           _tcscpy(szBuffer,_T("pMyCreateStruct->x            = "));       // pCreateStruct->x
           _stprintf(szTmp,_T("%d"),pMyCreateStruct->x);
           _tcscat(szBuffer,szTmp);
           TextOut(hDC,0,160,szBuffer,_tcslen(szBuffer));
    
           _tcscpy(szBuffer,_T("pMyCreateStruct->style        = "));       // pCreateStruct->style
           _stprintf(szTmp,_T("%d"),(int)pMyCreateStruct->style);
           _tcscat(szBuffer,szTmp);
           TextOut(hDC,0,180,szBuffer,_tcslen(szBuffer));
    
           _tcscpy(szBuffer,_T("pMyCreateStruct->szCaption    = "));
           _tcscat(szBuffer,pMyCreateStruct->szCaption);
           TextOut(hDC,0,200,szBuffer,_tcslen(szBuffer));
    
           _tcscpy(szBuffer,_T("pMyCreateStruct->szClassName  = "));
           _tcscat(szBuffer,pMyCreateStruct->szClassName);
           TextOut(hDC,0,220,szBuffer,_tcslen(szBuffer));
    
           SelectObject(hDC,hTmp);
           DeleteObject(hFont);
        }
     }
     SetBkMode(hDC,iBkGround);
     EndPaint(Wea.hWnd,&ps);
     #ifdef Debug
     fprintf(fp,"  Leaving fnGridProc_OnPaint()\n\n");
     #endif
    
     return 0;
    }
    
    long fnGridProc_OnDestroy(WndEventArgs& Wea)
    {
     MyCreateStruct* pMyCreateStruct=NULL;
    
     #ifdef Debug
     fprintf(fp,"  Entering fnGridProc_OnDestroy()\n");
     fprintf(fp,"    Wea.hWnd = %Iu\n",(size_t)Wea.hWnd);
     #endif
     pMyCreateStruct=(MyCreateStruct*)GetWindowLongPtr(Wea.hWnd,0);
     if(pMyCreateStruct)
     {
        #ifdef Debug
        BOOL blnFree=HeapFree(GetProcessHeap(),0,pMyCreateStruct);
        fprintf(fp,"    blnFree = %d\n",blnFree);
        #else
        HeapFree(GetProcessHeap(),0,pMyCreateStruct);
        #endif
     }
     #ifdef Debug
     fprintf(fp,"  Leaving fnGridProc_OnDestroy()\n");
     #endif
    
     return 0;
    }
    
    
    LRESULT CALLBACK fnGridProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
    {
     WndEventArgs Wea;
    
     for(unsigned int i=0; i<dim(GridEventHandler); i++)
     {
         if(GridEventHandler[i].iMsg==msg)
         {
            Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
            return (*GridEventHandler[i].fnPtr)(Wea);
         }
     }
    
     return (DefWindowProc(hwnd, msg, wParam, lParam));
    }
    
    long fnWndProc_OnCreate(WndEventArgs& Wea)
    {
     TCHAR szClassName[]=_T("Grid");
     HWND hGrid=NULL;
     WNDCLASSEX wc;
     GridData gd;
    
     #ifdef Debug
     fprintf(fp,"  Entering fnWndProc_OnCreate()\n");
     fprintf(fp,"    Wea.hWnd = %Iu\n",(size_t)Wea.hWnd);
     #endif
     Wea.hIns=((LPCREATESTRUCT)Wea.lParam)->hInstance;
     memset(&wc,0,sizeof(wc));
     wc.lpszClassName=szClassName;                           wc.lpfnWndProc=fnGridProc;
     wc.cbSize=sizeof (WNDCLASSEX);                          wc.style=0;
     wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);                wc.hInstance=Wea.hIns;
     wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION);             wc.hCursor=LoadCursor(NULL,IDC_ARROW);
     wc.hbrBackground=(HBRUSH)GetStockObject(LTGRAY_BRUSH);  wc.cbWndExtra=sizeof(void*);
     wc.lpszMenuName=NULL;                                   wc.cbClsExtra=0;
     RegisterClassEx(&wc);
     gd.iRows = 15;
     gd.iCols =  6;
     #ifdef Debug
     fprintf(fp,"    &gd = %p\n",&gd);
     #endif
     hGrid=CreateWindowEx(WS_EX_CLIENTEDGE,szClassName,_T("Text For A Grid Or Whatever"),WS_CHILD|WS_VISIBLE,15,15,575,375,Wea.hWnd,(HMENU)IDC_GRID,Wea.hIns,&gd);
     #ifdef Debug
     fprintf(fp,"    hGrid = %Iu\n",(size_t)hGrid);
     fprintf(fp,"  Leaving fnWndProc_OnCreate()\n\n");
     #endif
    
     return 0;
    }
    
    
    long fnWndProc_OnDestroy(WndEventArgs& Wea)
    {
     #ifdef Debug
     fprintf(fp,"  Entering fnWndProc_OnDestroy()\n");
     fprintf(fp,"    Wea.hWnd = %Iu\n",(size_t)Wea.hWnd);
     #endif
     PostQuitMessage(0);
     #ifdef Debug
     fprintf(fp,"  Leaving fnWndProc_OnDestroy()\n\n");
     #endif
    
     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("lpCreateParams Demo");
     WNDCLASSEX wc;
     MSG messages;
     HWND hWnd;
    
     #ifdef Debug
     fp=fopen("Output.txt","w");
     fprintf(fp,"Entering WinMain()\n");
     fprintf(fp,"  hInstance = %Iu\n",(size_t)hInstance);
     #endif
     wc.lpszClassName=szClassName;                wc.lpfnWndProc=fnWndProc;
     wc.cbSize=sizeof (WNDCLASSEX);               wc.style=0;
     wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);     wc.hInstance=hInstance;
     wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION);  wc.hCursor=LoadCursor(NULL,IDC_ARROW);
     wc.hbrBackground=(HBRUSH)COLOR_BTNSHADOW;    wc.cbWndExtra=0;
     wc.lpszMenuName=NULL;                        wc.cbClsExtra=0;
     RegisterClassEx(&wc);
     #ifdef Debug
     fprintf(fp,"  Right Before CreateWindowEx() Call In WinMain()\n");
     #endif
     hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,75,75,625,450,HWND_DESKTOP,0,hInstance,0);
     #ifdef Debug
     fprintf(fp,"  hWnd    = %Iu\n",(size_t)hWnd);
     fprintf(fp,"  Right After CreateWindowEx() Call In WinMain()\n");
     #endif
     ShowWindow(hWnd,iShow);
     while(GetMessage(&messages,NULL,0,0))
     {
        TranslateMessage(&messages);
        DispatchMessage(&messages);
     }
     #ifdef Debug
     fprintf(fp,"Leaving WinMain()");
     fclose(fp);
     #endif
    
     return messages.wParam;
    }
    If Debug is defined an output.txt log file is created, and here is what I got for a program run...

    Code:
    Entering WinMain()
      hInstance = 4194304
      Right Before CreateWindowEx() Call In WinMain()
      Entering fnWndProc_OnCreate()
        Wea.hWnd = 4261094
        &gd = 000000000022F438
        Entering fnGridProc_OnCreate()
          Wea.hWnd = 7996644
          pMyCreateStruct->GrdDta.iRows = 15
          pMyCreateStruct->GrdDta.iCols = 6
          pCreateStruct           = 2289104
          pMyCreateStruct         = 6378576
          pCreateStruct->lpszName = Text For A Grid Or Whatever
        Leaving fnGridProc_OnCreate()
        hGrid = 7996644
      Leaving fnWndProc_OnCreate()
    
      hWnd    = 4261094
      Right After CreateWindowEx() Call In WinMain()
      Entering fnGridProc_OnPaint()
        Wea.hWnd = 7996644
      Leaving fnGridProc_OnPaint()
    
      Entering fnWndProc_OnDestroy()
        Wea.hWnd = 4261094
      Leaving fnWndProc_OnDestroy()
    
      Entering fnGridProc_OnDestroy()
        Wea.hWnd = 7996644
        blnFree = 1
      Leaving fnGridProc_OnDestroy()
    Leaving WinMain()
    When the program runs you should see a main program window representing a "lpCreateParams Demo" class, and most of the client area of that window is taken up by the child window created which is our Custom Control or "Grid" class. And within the client area of the Custom Control or "Grid" class should be visible all the parameters of the CreateWindowEx() call which created the Custom Control, as well as the values of the GridData object passed into the creation code of the grid through the lpCreateParams parameter of the CreateWindowEx() call. Whew! That was a lot! Can you see how important and powerful this is??? It represents the fundamental basis or architecture upon which custom controls and Active X Controls are built.

    I've developed and tested this with GCC TDM-MinGW 4.8. Hopefully it'll work with MS VC. I'll test it later. Any problems let me know.

  5. #20
    Registered User
    Join Date
    Dec 2010
    Location
    Trinidad, CO (log cabin in middle of nowhere)
    Posts
    148
    is there any harm in trying to free "uninitialised" GDI object handles with DestroyIcon, DeleteObject, ReleaseDC, DeleteDC etc? I'd assume that the functions themselves would verify that the handles passed to them point to valid GDI objects before trying to free resources associated with them, but of course, knowing how easily GDI leaks can occur, I'd rather not make such assumptions and dance with undefined behaviour. Does anyone have any info on the inner workings of these functions? Their documentation on MSDN does not address what happens when invalid (NULL) handles are passed.
    Regarding that, I'd say that I'd personally avoid releasing any kind of acquired resource be it GDI objects, memory, or whatever more than once. I can't really say without testing it what the results would be of calling Windows GDI DeleteObject() more than once on the same HANDLE. I simply don't do those kinds of things. With memory acquired through a memory allocation function the result is at best an instant crash or at worst corruption of data somewhere in your process memory (the crash comes later, at a moment of its own choosing, when its able to do you the maximum personal damage, such as demonstrating a program you thought you had working to your boss or a potential employer).

    So what I do is to always maintain any acquired handles to anything in a known state. Its really not rocket science. You declare a HANDLE to something and set it to NULL...

    Code:
    HFONT hFont=NULL;
    Then call your allocation function to acquire the resource....

    Code:
    hFont=CreateFont(......);
    if(hFont)
    {
        // use resource
    }  DeleteObject(hFont);
    Of course that's easy and obvious. But what about situations where a resource variable might be passed around to functions any of which might or might not release it or acquire different HFONTs using the same HANDLE? What I would do in that case is adopt the regimen of always setting the resource variable to NULL after releasing it. I'm not aware that resource deallocation functions typically do that for you. But if you set it to NULL yourself after a release call then you are keeping it in a known state. Then, if at some later point in the code you would attempt to reuse that HANDLE, simply test it to see whether it is NULL or not. I'm aware that various schemes exist to help with all this such as Smart Pointers and RAII, etc. If that appeals to you I'd check it out. But I really would avoid releasing anything twice, as that puts you in the no man's land of undefined behavior I think. But if its set to null, always test that in your release calls, and you won't call releases twice.
    Last edited by freddie; 06-21-2016 at 12:46 PM. Reason: add sentence

  6. #21
    'Allo, 'Allo, Allo
    Join Date
    Apr 2008
    Posts
    639
    What freddie said. Passing NULL where it shouldn't make sense to do so doesn't always mean 'do nothing'. For example, the function that TerminateProcess calls to actually stop a process takes a process handle. If you pass it NULL then it doesn't do nothing, instead it immediately terminates all threads in your program except the one that called it*. So if the docs dont say what happens, it's generally best to avoid it.

    You probably won't ever see it, but conceptually GDI & User handles like HWNDs can be reused (much like process ids). User handles (I forget about GDI ones) are desktop wide too, so if you blindly send them into functions after you've destroyed them you could end up changing other apps stuff.

    * At least on 7/8 it does. The nature of the undocumented beast is that it can and most likely will change.

  7. #22
    Registered User
    Join Date
    Jul 2015
    Posts
    64
    Many thanks for the compliment Freddie! I wouldn't say "knowledgeable", just willing to learn

    The example that you provided on how to use CREATESTRUCT objects is exceptionally helpful; thank you so much! I think that, having handled WM_CREATE incorrectly in the past, I got myself into the bad habit of taking the message for granted; I can't believe I have never thought to look up the message's w/l Params before. Cheers for drawing my attention to them and the CREATESTRUCT structure; it is already helping me massively (in terms of software design) to have the option of passing data to the windows procedure on window creation!

    If you don't mind me asking further questions, what are the architectural and functional differences between pointing to objects via WNDCLASSEX.cbWndExtra and pointing to objects via the last parameter of CreateWindowEx()? If I was deciding which method to use to pass data to the windows procedure, what factors should I consider? Of course, cbWndExtra would allow for a greater scope of access to the pointed-to object within the windows procedure, whilst using CREATESTRUCT members would only allow for access within WM_CREATE. As you pointed out, this can be overcome by creating a more-or-less verbatim copy of the object on the heap and pointing to it from cbWndExtra, but could it not be argued that we might as well do this in WinMain and cut out the "CREATESTRUCT middle-man"? Or do you, personally, avoid this because you prefer to minimise WinMain code? Is there anything else a developer needs to consider, other than object scoping, when choosing which method to use?

    Upon your suggestion to look into lpCreateParams, Freddie, I stumbled across this thread, which I believe is also your work: https://forum.powerbasic.com/forum/u...ing-geek-speak

    Is your surname Harris? That thread is on my to-read list, but it definitely looks relevant. I don't have access to my IDE at the moment, but I'll test-compile your lpCreateParams example code as soon as I can. Again, a huge thank you for all the effort; I think I'm finally starting to understand how Win32 was designed to be worked with and hopefully others will benefit too!

    Cheers also for the advice on freeing GDI object resources. I shall try to get into the habit of NULLifying handles and checking their status before releasing them.

    Adeyblue, I actually had not considered the point that you raise about TerminateProcess; thank you for the heads up! Perhaps it was a little naive of me to think that passing NULL in an undocumented fashion would just magically turn out as I expected!

    Cheers for all the help/suggestions guys!

  8. #23
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    Something that I still use today as an initial reference is the 2008 MSDN Library - which you can download here: Download MSDN Library for Visual Studio 2008 SP1 from Official Microsoft Download Center

    This was the last time all of MSDN was available as a big 'ole download. So you can quickly look up messages and their associated w/l params without waiting on the interweb. For newer Win 8/10 stuff (like touch-digitizer messages for tablets; pinch-to-zoom etc...) you still have to hit the online MSDN. Sometimes it's good to check online anyways if you are targeting Win10 - for example, GetVersion currently lies to you one Win10 if you are not manifested for Win10.

    gg

  9. #24
    Registered User
    Join Date
    Jul 2015
    Posts
    64
    That might be an idea actually, cheers for the suggestion Codeplug!
    Do we know how nicely the MSDN offline library plays with Visual Studio 2015, by any chance? I gather that there is still some setup required even if you download it as a stand-alone reference (none of this integration with VS malarkey)?

    Out of curiosity, is "gg" your initials?

  10. #25
    Lurking whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,612
    It should work fine. The reason is because if you mount the ISO and run it, you will see that it installs something called the Microsoft Document Explorer 2008 - which is what MSVC 2008 used to look at help files.

  11. #26
    Registered User
    Join Date
    Jul 2015
    Posts
    64
    Ahh wonderful! Is Microsoft Document Explorer a lightweight application?
    I hate programs that hog resources and add things to my user profile directory unnecessarily, that's all.
    In fact, I'll probably just create a restore point and install it anyway. Cheers for the heads up Whiteflags!

  12. #27
    Registered User
    Join Date
    Dec 2010
    Location
    Trinidad, CO (log cabin in middle of nowhere)
    Posts
    148
    If you don't mind me asking further questions, what are the architectural and functional differences between pointing to objects via WNDCLASSEX.cbWndExtra and pointing to objects via the last parameter of CreateWindowEx()?
    In the program I provided there are two completely different window objects that get created. First there is the main Window Class represented by "lpCreateParams Demo". Then in the creation code for that we register the "Grid" Class and create an instance of it whose parent is the main window of "lpCreateParams Demo". What got passed into the WM_CREATE of the "Grid" Class were all the arguments of the CreateWindowEx() call that created the grid. Like you said, it could have been done differently. After the CreateWindowEx() call that created the object of the "Grid" Class I could have used that HWND of the grid to save those items as part of the WNDLASSEX::cbWndExtra bytes. So you are right about that. I guess it does boil down to personal preference now that I think about it. I suppose that somehow to me it just seems more elegant or something to use the CREATESTRUCT*.

    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, i.e., forms, windows, dialogs, whatever you want to call them, 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.

    Yep, I'm the guy who wrote that tutorial in the PowerBASIC Forums. I thought I had posted something like that there a couple years ago. In fact, I spent a minute or two looking for it but didn't find it. You did better than me! If I had found it I likely would have just provided a link! From your perusal of some of my posts there and in the Jose Roca Forum you might have gathered that PowerBASIC is something of an interesting language. I'm a programming junkie and have played with piles of programming languages over the years, but for like over two decades the C Family Languages and the BASIC Family languages have been my favorites. Up until recently I'd say that in any given year I spend about half my time coding in PowerBASIC and the other half in C++. What kept me heavily in the C++ world was that I do the Windows CE handheld data recorder programming for our data collectors where I work. For that I always used Microsoft's eMbedded C/C++. For our desktop work involving a lot of heavy duty number crunching, reporting, and database work I always used PowerBASIC.

    But I've shifted most of my work over to C++ now because of the tragedy which occurred at PowerBASIC in 2011. The creator of that language, Bob Zale, passed away unexpectedly. He was a world class assembler coder and compiler writer. His passion in life was creating world class compilers, and he did it all in assembler, which is unusual nowadays believe. His motto was "smaller, faster". He only ever produced compilers for Windows, and the variable types in that language are straight of of MASM and the Windows SDK, i.e., ints, DWORDs, etc. But the mistake he made, in my opinion, and other's opinion also, is that he was too late on the 64 bit bandwagon. He spent too much time on a simplified GUI creation framework of his which he named DDT (Dynamic Dialog Tools) and is part of his language, than on porting his compiler code to creating 64 bit binaries. The understanding everyone has in the PowerBAIC Community is that he had started work on a 64 bit compiler, but no one is quite sure how much of it he got done before his untimely death. There is someone else in the company working on a 64 bit compiler, but whether it will ever be finished, or as good as Bob Zale's work, no one knows. Folks who can write rock solid compilers and operating systems are hard to come by. They usually aren't walking the streets looking for work. So I've taken the loss of PowerBASIC hard. I lost an important tool in my toolbox that may not ever be able to be replaced. You've gotta realize that PowerBASIC could match C tick for tick in terms of performance, it had all the low level features of C such as pointers, inline assembler, easy access to the Windows Api, etc., it created binaries way smaller than anything a C++ coder could come anywhere close to using the C++ Standard Library, and with all that it had many of the high level features of .NET such as easy built in access to COM/OLE components such as Microsoft Office, Word, Excel, or anything using that architecture.

    So I didn't answer your question yet from several posts back about how my TLib.lib accomplishes the reductions in program sizes that it does (I was planning on getting to it at some point), but what I've written above provides some of the context for my work on it, that is, to try to come up with an application development framework that compares favorably to what I lost in PowerBASIC. By the way, that app above that demos the lpCreateParams builds to 6,656 bytes in x64 with my TCLib.lib.

    But I don't consider my work on TCLib.lib ready for prime time yet. Its still under development. Like CodePlugs intimated, library code has to be rock solid. When you have bugs in a project that needs fixed, they had better be in that project's source code and not in the compiler or library code or you are in trouble. But almost all the work I've done so far on my TCLib.lib is posted on my board in the Jose Roca Forum under the 'Discussion' topic. I have to warn you there is a lot of information and posts there that would take you a real long time to get through. Basically all the work involves the replacement of core stdio, stdlib, and string C Runtime functions with replacements. The original source of work on all this goes all the way back to Matt Pietrek who was something of a luminary in the field of software development years ago. He wrote a popular column for "Microsoft System Journal" for many years named "Under The Hood", where he covered low level issues involved with compilers, linkers, PE File format, etc. You'll find extensive discussions of his code with links where I've stated. Lot of other links there too you might find interesting.

    Right now that project is going through a difficult time. I'm finding I'm having to do something I truely hated to do, and that is code my own full replacements for the printf family of functions. That's gonna hurt. Its going to cost me several k size wise. What I had been doing was using Matt Pietreks really slick approach, and that was inside my printf, fprintf, sprintf, etc., functions, simply pass the varargs parameters onto wvsprintf, which is in user32, i.e., a Windows component, i.e., its not a C or C++ runtime function. The problem with that is it doesn't work for floating point numbers. To the best of my knowledge, there is no capacity within Windows itself to convert floating point numbers to string representations (I've looked and asked around, CodeProject - For those who code, etc.). So what I had to do was code my own floating point conversions which work excellently. But I've got a situation where my printfs don't work exactly like the C Runtime versions. I thought I could 'put up with it', but then changed my mind. 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. I gotta go for now!

  13. #28
    Registered User
    Join Date
    Dec 2010
    Location
    Trinidad, CO (log cabin in middle of nowhere)
    Posts
    148
    In the program I provided there are two completely different window objects that get created. First there is the main Window Class represented by "lpCreateParams Demo". Then in the creation code for that we register the "Grid" Class and create an instance of it whose parent is the main window of "lpCreateParams Demo". What got passed into the WM_CREATE of the "Grid" Class were all the arguments of the CreateWindowEx() call that created the grid. Like you said, it could have been done differently. After the CreateWindowEx() call that created the object of the "Grid" Class I could have used that HWND of the grid to save those items as part of the WNDLASSEX::cbWndExtra bytes. So you are right about that. I guess it does boil down to personal preference now that I think about it. I suppose that somehow to me it just seems more elegant or something to use the CREATESTRUCT*.
    But in thinking about it further I'd like to add that I usually think of the WNDCLASSEX::cbWndExtra bytes as a storage mechanism where I'm attaching member data relating to an instance of an 0object to that instance, just like your member variables in a real C++ Class. On the other hand, I usually think of the CREATEPARAMS* pointed to by the LPARAM in the WM_CREATE message as a data transfer mechanism where I'm wanting to pass something into the object's constructor. Another way of thinking about it, in the case of an app with multiple top level or OVERLAPPEDWINDOWs, and the HWND of one window was passed to another, is that you are giving access to one window's data from another, kind of like friend functions in C++. Does that make sense?

  14. #29
    'Allo, 'Allo, Allo
    Join Date
    Apr 2008
    Posts
    639
    Is Microsoft Document Explorer a lightweight application?
    You have no option. If you want to use that download that's the only game in town to browse it.

    Right now that project is going through a difficult time. I'm finding I'm having to do something I truely hated to do, and that is code my own full replacements for the printf family of functions. That's gonna hurt. Its going to cost me several k size wise.
    I started writing my own CRT at one point, but then I gave up when I thought 'why bother doing all this to wrap some functions in user32, when I could just link to the exact functions I'm rewriting in msvcrt.dll.' A majority of them also exist in ntdll.dll with some notable omissions if msvcrt has your ire for some reason. These two are loaded into pretty much every process by default, so as it doesn't seem you're doing this as a learning exercise, just save yourself the bother and the k's. Yes, its technically cheating, but if you're piggybacking off user32 already, you're already down that rabbit hole.

    To the best of my knowledge, there is no capacity within Windows itself to convert floating point numbers to string representations
    If you discount the sprintf-y functions in the dll's above, variants can hold floats and doubles, and there's a plethora of functions to manipulate and format them. For instance: VarFormatNumber and VarBstrFromR4

  15. #30
    Registered User
    Join Date
    Dec 2010
    Location
    Trinidad, CO (log cabin in middle of nowhere)
    Posts
    148
    If you discount the sprintf-y functions in the dll's above, variants can hold floats and doubles, and there's a plethora of functions to manipulate and format them. For instance: VarFormatNumber and VarBstrFromR4
    Wow! You just told me something I didn't know adeyblue! I wasn't aware of those functions. That would have saved me a lot of work because I ended up coding my own routines to convert doubles. I more or less wrote four byte floats off. When I originally discovered this issue with floats and eliminating the C Runtime I first found 32 bit assembler code written by Raymond Filiatraut in the MASM32 Forum at www.masm32com. It was in Raymonds FPU.LIB. While it only worked for 32 bit, I did get it working there by using some inline assembler. But that wouldn't solve my 64 bit issue; Raymond didn't want to rework his lib for x64, and I didn't feel like tackling it at the time. So I wrote my own FltToCh and FltToWch functions. They turned out excellently, so that's what I'm using now. They do thousands seperating too, rounding to specified decimal places, etc. The only downside to it is that it takes a fair amount of code to do all that from scratch. Couple hundred lines. So it likely adds a k or so to executable size. I will look into those two functions you provided links for. I may swap out my FltToCh in favor of those to do the conversion. That might save me a k or two. And that might counteract to some extent all the code I'm having to write to implement printf, sprintf, fprintf, etc. A lot of miserable string parsing, and all because of floats. Matt Pietrek's implementations work fine for integral quantities and Z strings. Thanks heaps for that information.

    Actually, I have nothing whatsoever against the C Runtime. I love it heaps. It pains me to no end to have to eliminate that. Its the C++ Standard Library that's my mortal enemy.

    'why bother doing all this to wrap some functions in user32, when I could just link to the exact functions I'm rewriting in msvcrt.dll.'
    I did that some too. But like you said, I considered it cheating. Do you know if msvcrt.dll is truely loaded into every process? I might do some exploration of that myself. When I did do it I did a LoadLibrary() /GetProcAddress() but LoadLibrary() is reference counted, so it may be loaded anyway. I'm not sure about that point. But if for certain msvcrt.dll is loaded into every process, then it does seem reasonable to use its C Runtime implementations.

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