Jose's Read Only Forum 2023

IT-Consultant: Frederick J. Harris => Discussion => Topic started by: Frederick J. Harris on April 02, 2016, 12:03:13 AM

Title: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 02, 2016, 12:03:13 AM
     Still working on TCLib.lib issues, and trying to move things out of the 'proof of concept' stage to a point where this all could be used in real world application development.  All or most of my test programs so far have been fairly trivial things with under 40 lines of code.  For the past several days though I've been working on getting an app to work that I consider to be fairly complex, and that would be the one I posted here last summer...

http://www.jose.it-berater.org/smfforum/index.php?topic=5058.0

...where you can find a PowerBASIC and C++ version.   In that app I have a main thread with a GUI (Graphical User Interface), and a worker thread which performs heavy duty string processing involving well over a gigabyte of data.  As about 100 separate string processing jobs are completed in the worker thread, the program writes progress reports to a continuously scrolling output window to inform the user of the progress and details of each separate job - kind of like Microsoft's major download/installation programs.  The programming is such that a cancel button allows the user to terminate the jobs prematurely, as is the case with major downloads and program installations.

      The first problem I ran into in getting this program to run with TCLib was that I have no implementation of C Runtime rand() in there.  That random number generator function was used to come up with string sizes to process for the 100 separate jobs.   I wanted string sizes to process between 1 million and 25 million characters.  I believe the C Runtime rand() function returns numbers between 1 and 32767, so I had to develop a 'blow up' or scaling function to modify those numbers to fall within a range of my choosing, just like the PowerBASIC Rnd(a, b) function...


y = RND(a, b)


Here's my implementation of that, and the rand() function I found on the internet...


int rand(void)
{             
next = next * 1103515245 + 12345;
return (unsigned int)(next>>16) & RAND_MAX;
}                                                                                                                 


int Rnd(int iMin, int iMax)           
{
double dblRange,dblMaxFactor,dblRandomNumber;

dblRange=iMax-iMin;                         
dblMaxFactor=dblRange/RAND_MAX;             
dblRandomNumber=(double)rand();     

return iMin+dblMaxFactor*dblRandomNumber;
}                                     


I had the Rnd() function previously, as I needed that in the C++ version using the C Runtime I posted previously.  But the rand() I just came up with for this work now.

The string processing to be done on these multi megabyte strings is this....


//   1) Allocate a multi-million byte array of dash characters;
//   2) Change every 7th dash to a 'P';
//   3) Replace every 'P' with a "PU', thereby growing the buffer;
//   4) Replace every remaining dash with an '8';
//   5) Insert a carriage return / line feed every 90 characters,


This is an abbreviated version of the code which is executing within a thread procedure which makes the 100 or so (iLineCount) calls to the DoProcessing() function ...


for(int i=1; i<=iLineCount; i++)
{
    ...
    iNumber=Rnd(1000000, 25000000);
    iTotalBytes=iTotalBytes+iNumber;
    DoProcessing(iNumber,iTickCount);
    ....
}


As you can see, DoProcessing() is within a for loop, iNumber gets the return from the call to Rnd() for a number between 1 million and 25 million, and iTotalBytes keeps a running tally of the total number of characters processed so far for final reporting purposes at the end.  I guess it should be iTotalChars and not iTotalBytes, as the actual code is using hard coded wchar_ts. 

     Having gotten through all that, when I attempted to use TCLib to compile the program it compiled and ran, but when I clicked the button to start the processing nothing happened.  Usually the scrolling lines of output display about every half second, but nothing happened.  I figured it locked up.  Well, that didn't surprise me.  Been fighting for three months with this now, and no part of it came easy. 

     So I figured I'd have to start from scratch and see what was going wrong.  And that means debugging.  But my primary way of debugging is opening an output log file where I can see what values my variables are taking on, and check the returns from function calls.  But I never really implemented i/o file support in TCLib yet.  There is a Mike_V version of Matt Pietrek's LibCTiny over at www.CodeProject.com.  I thought his work would be more helpful to me than it was, and that's where I actually started out in my quest to develop wide character support.  And in that his work did help me.  But he had pretty much reinterpreted the stdio.h FILE structure machinery to get file i/o working.  Or at least I think he did.  I never really tested it because in my interpretation he completely blew the printf/sprintf code, which in my mind is a cornerstone of this whole endeavor.  If you don't have that you haven't anything at all and might as well just quit.

     What I had discovered in my investigations was that a FILE object from stdio.h as used in fprintf and a HANDLE object as returned by kernel32.lib's CreateFile function were one and the same entity.  Now before you go jumping all over me about this let me state I haven't traced the full lineage of that through the circuitous and obscure pathways of the various Windows includes, but am just stating that if you use Windows Api CreateFile() to return a HANDLE to an opened text file, and you pass that HANDLE to fprintf cast to a FILE*, it'll work!!!  But I didn't really do that.  I simply implemented fprintf using kernel32 functions; its still a variadic function and can be used in the same way as the 'real' fprintf, but you've gotta use CreateFile() to open the file - not fopen(), and you've gotta use CloseHandle() to close the file - not fclose().  Here is my fprintf which needs to be added to TCLib.mak....


//=============================================================
//   Developed As An Addition To Matt Pietrek's LibCTiny.lib
//                By Fred Harris, March 2016
//
// cl fprintf.cpp /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN   
//=============================================================
// cl sprintf.cpp /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdarg.h>
#include "stdio.h"
#define EOF (-1)
#pragma comment(linker, "/defaultlib:user32.lib")

int __cdecl fprintf(HANDLE hFile, const char* format, ...)
{
char szBuff[1024];
DWORD cbWritten;
va_list argptr;
int retValue;

va_start(argptr, format);
retValue = wvsprintfA(szBuff, format, argptr);
va_end(argptr);
WriteFile(hFile, szBuff, retValue, &cbWritten, 0);

return retValue;
}

int __cdecl fwprintf(HANDLE hFile, const wchar_t* format, ...)
{
wchar_t szBuff[1024];
DWORD cbWritten;
va_list argptr;
int retValue;

va_start(argptr, format);
retValue = wvsprintfW(szBuff, format, argptr);
va_end(argptr);
WriteFile(hFile, szBuff, retValue*2, &cbWritten, 0);

return retValue;
}


     So, now with debugging support lets try to see what ails my string processing code.  Here is MemTest1.cpp, where I've isolated the string processing algorithm from the GUI app, and have it in a simple console app.  Upon examination you'll see I have a globally declared HANDLE object to provide my text file output debugging support, and I use it in main() to open a file with CreateFile().  And I have quite a few GetTickCount() calls spread around to see how many ticks its taking to do various things.  In main() you'll see I have a variable iChars set to 100000, and we pass that to DoProcessing() to see what happens.  And it does compile, link, run, and terminate normally.  Here is MemTest1.cpp...


// cl MemTest1.cpp /O1 /Os /GS- /link TCLib.lib kernel32.lib
#define   UNICODE
#define   _UNICODE
#include  <windows.h>
#include  "stdio.h"
#include  "tchar.h"
#define   x64
HANDLE    hFile=NULL;
extern "C" int _fltused=1;


void DoProcessing(int& iNumber, int& iStart)
{
int i=0,iCtr=0,j;
wchar_t* s1=NULL;
wchar_t* s2=NULL;
DWORD iTicks;

_ftprintf(hFile,_T("Entering DoProcessing()\r\n"));
_ftprintf(hFile,_T("  iNumber    = %d\r\n"),iNumber);
iTicks=GetTickCount()-iStart;
int iLineLength=90;
int iNumPs=iNumber/7+1;
int iPuExtLength=iNumber+iNumPs;
int iNumFullLines=iPuExtLength/iLineLength;
int iMaxMem=iPuExtLength+iNumFullLines*2;
s1=(wchar_t*)GlobalAlloc(GPTR,iMaxMem*sizeof(wchar_t));  //Allocate two buffers big enough to hold the original NUMBER of chars
s2=(wchar_t*)GlobalAlloc(GPTR,iMaxMem*sizeof(wchar_t));  //plus substitution of PUs for Ps and CrLfs after each LINE_LENGTH chunk.
_ftprintf(hFile,_T("  iMaxMem    = %d\r\n"),iMaxMem);
iTicks=GetTickCount()-iStart;
_ftprintf(hFile,_T("  iTicks     = %d        After Memory Allocations\r\n"),iTicks);

for(i=0; i<iNumber; i++)                // 1) Create an xMB string of dashes
     s1[i]=L'-';
iTicks=GetTickCount()-iStart;
_ftprintf(hFile,_T("  iTicks     = %d        After Filling Memory With Dashes\r\n"),iTicks);
 
for(i=0; i<iNumber; i++, iCtr++)        // 2) Change every 7th dash to a "P"
{
     if(iCtr==7)
     {
        s1[i]=L'P';
        iCtr=0;
     }
}
iTicks=GetTickCount()-iStart;
_ftprintf(hFile,_T("  iTicks     = %d       After Putting Ps Every 7th Char\r\n"),iTicks);

iCtr=0;                                 // 3) Substitute 'PUs' for 'Ps'
for(i=0; i<iNumber; i++)
{
     if(wcsncmp(s1+i,L"P",1)==0)
     {
        wcscpy(s2+iCtr,L"PU");
        iCtr+=2;
     }
     else
     {
        s2[iCtr]=s1[i];
        iCtr++;
     }
}
iTicks=GetTickCount()-iStart;
_ftprintf(hFile,_T("  iTicks     = %d       After wcsncmp()\r\n"),iTicks);

for(i=0; i<iPuExtLength; i++)          // 4) Replace every '-' with an 8;
{
     if(s2[i]==L'-')
        s2[i]=56;   //56 is '8'
}
iTicks=GetTickCount()-iStart;
_ftprintf(hFile,_T("  iTicks     = %d\r\n"),iTicks);

i=0, j=0, iCtr=0;                      // 5)Put in a CrLf every 90 characters
while(i<iPuExtLength)
{
    s1[j]=s2[i];
    i++, j++, iCtr++;
    if(iCtr==iLineLength)
    {
       s1[j]=13, j++;
       s1[j]=10, j++;
       iCtr=0;
    }
}
iTicks=GetTickCount()-iStart;
_ftprintf(hFile,_T("  iTicks     = %d\r\n"),iTicks);
GlobalFree(s1), GlobalFree(s2);
iTicks=GetTickCount()-iStart;
_ftprintf(hFile,_T("  iTicks     = %d\r\n"),iTicks);
_ftprintf(hFile,_T("Leaving DoProcessing()\r\n\r\n"));
}


int _tmain()
{
int iTicks,iStart,iChars;
TCHAR szBuffer[16];
DWORD cbWritten;
   
hFile=CreateFile(_T("Output.txt"),GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
iStart=GetTickCount();
iTicks=GetTickCount()-iStart;
_ftprintf(hFile,_T("iTicks = %d\r\n\r\n"),iTicks);
iChars=100000;
DoProcessing(iChars,iStart);
iTicks=GetTickCount()-iStart;
_ftprintf(hFile,_T("\r\niTicks = %d\r\n"),iTicks);
_tprintf(_T("Processing Complete!\n"));
CloseHandle(hFile);

return 0;
}


Upon opening the Output.txt file we have this....


iTicks = 0

Entering DoProcessing()
  iNumber    = 100000
  iMaxMem    = 116824
  iTicks     = 0        After Memory Allocations
  iTicks     = 0        After Filling Memory With Dashes
  iTicks     = 0        After Putting Ps Every 7th Char
  iTicks     = 6568     After wcsncmp()
  iTicks     = 6568
  iTicks     = 6568
  iTicks     = 6568
Leaving DoProcessing()

iTicks = 6568


continued...
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 02, 2016, 12:07:58 AM
     Its taking about six and a half seconds to process about a hundred thousand characters.  That's terribly, terribly slow!  In my original app using either PowerBASIC or C++ and the C Runtime I was processing 100 strings each of which varied in size between 1 million and 25 million characters.  And it was only taking between 12 and 20 seconds for a hundred such strings each of which was 10 to 250 times bigger than the one above!  So what I'm saying is that somehow we have some serious, serious performance problems in TCLib.lib.  Perhaps I've finally found the Achilles Heel of my work on this.  Maybe TCLib can't be used anywhere performance is necessary?  Could it possibly be that the C Runtime when it initializes is creating a heap that is hundreds of times faster than my lack of any memory management in that regard?

     Well, in looking closely at the GetTickCount() results above its possible to see where all the time is being spent.  We start out at 0 ticks in main().  Even after the memory allocations at the top of DoProcessing() using GlobalAlloc() we're still at 0 ticks, so that isn't taking any measurable time at all.  Next GetTickCount() info is after running through the 100,000 wchar_t buffer and filling it with dashes ( '-' ).  And that apparently isn't taking any measurable time as we're still at 0. 

     Next step in the algorithm is to start at the beginning of the buffer again and write a L'P' wchar_t to the buffer every 7th wchar_t.  And we're still at 0!  That's not the bottleneck.  Makes one begin to wonder what's taking all the time!

     Then comes the routine to run through the buffer again, and replace every L'P' encountered with a L"PU".  The 2nd buffer is used for that.  The way that logic works is that s1 is tested each wchar_t at a time from beginning to end for a L'P' character.  wcsncmp() is used for that.  When it encounters one it writes the L"PU" to the s2 buffer, otherwise it just writes the dash found in s1.  Counters keep track of where reads/writes occur in each buffer so that when processing is finished s2 contains everything in s1 but with Ps replaced by PUs, so naturally its longer.  And that is taking 6568 ticks or essentially the time of the whole program run.  So I do believe we've found our bottleneck.  Its strncmp/wcsncmp!!!  So lets take a look at that from strncmp.cpp....


int __cdecl wcsncmp(const wchar_t* str1, const wchar_t* str2, size_t count)
{
size_t iLen1=wcslen(str1);
size_t iLen2=wcslen(str2);
if(count>iLen1)
    return -1;
if(count>iLen2)
    return 1;
wchar_t* pStr1=(wchar_t*)malloc(count*2+2);
wchar_t* pStr2=(wchar_t*)malloc(count*2+2);
wcsncpy(pStr1,str1,count);
wcsncpy(pStr2,str2,count);
pStr1[count]=0;
pStr2[count]=0;
int iReturn=wcscmp(pStr1,pStr2);
free(pStr1);
free(pStr2);

return iReturn;
}


I definitely overdid it there in my initial zest to provide a bullet proof version of strncmp.  Its real easy for me to see what the problem is.  In my string processing algorithm I'm moving along the s1 buffer one wchar_t at a time looking for a L'P' with wcsncmp.  And every time wcsncmp is being called wcslen() is being called on both string parameters!   I believe some absolutely drastic simplification is in order!  Something like this....


//===============================================================================================
//               Developed As An Addition To Matt Pietrek's LibCTiny.lib
//                            By Fred Harris, January 2016
//
//       cl strncmp.cpp /D "_CRT_SECURE_NO_WARNINGS" /c /W3 /DWIN32_LEAN_AND_MEAN
//===============================================================================================
#include <windows.h>
#include "malloc.h"
#include "string.h"

int __cdecl strncmp(const char* str1, const char* str2, size_t count)
{
for(size_t i=0; i<count; i++)
{
     if(str1[i]<str2[i])
        return -1;
     if(str1[i]>str2[i])
        return 1;
}

return 0;
}

int __cdecl wcsncmp(const wchar_t* str1, const wchar_t* str2, size_t count)
{
for(size_t i=0; i<count; i++)
{
     if(str1[i]<str2[i])
        return -1;
     if(str1[i]>str2[i])
        return 1;
}

return 0;
}


So if you bring up your command prompt window where you have TCLib you can remove the slow version of strncmp like this...


Lib TCLib.lib /remove:strncmp.obj  [ENTER]


And once you compile the new strncmp.cpp above you can insert that in TCLib like this...


Lib TCLib.lib strncmp.obj   [ENTER]


And while you are at it you might want to do the same with fprintf.cpp.  Also, string.h, stdio.h, and tchar.h need to be updated with the fprintf information.

     Having gotten through that and running MemTest1.cpp after recompiling we end up with this Output.txt file...


iTicks = 0

Entering DoProcessing()
  iNumber    = 100000
  iMaxMem    = 116824
  iTicks     = 0        After Memory Allocations
  iTicks     = 0        After Filling Memory With Dashes
  iTicks     = 0        After Putting Ps Every 7th Char
  iTicks     = 0        After wcsncmp()
  iTicks     = 0
  iTicks     = 0
  iTicks     = 0
Leaving DoProcessing()

iTicks = 0


     How's that for improvement?  On a measly 200,000 byte buffer its running so fast now that its underneath the resolution of GetTickCount(), which I believe is somewhere in the neighborhood of 16 to 32 ticks, and its coming in a 0 for the whole run instead of the previous 6568 milliseconds!  On one calculation I made with larger buffers I calculated its now 963 times faster!  So I do believe we'll be good to go on the main multithreaded GUI version, which is this....

continued...
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 02, 2016, 12:09:22 AM

// T12_TCLib64.cpp
// cl T12_TCLib64.cpp /O1 /Os /GS- /FeT12_TCLib64.exe /link TCLib.lib kernel32.lib user32.lib gdi32.lib
#ifndef UNICODE
    #define UNICODE                   // Program shows how to produce real time scrolling output of data
#endif                                // processing as processing occurs using threads.  The 'main' or
#ifndef _UNICODE                      // GUI thread contains a main program object with a 'Process Data'
    #define _UNICODE                  // and 'Cancel' button.  When the user clicks the 'Process Data'
#endif                                // button a 2nd top level window is opened with a CreateWindowEx()
#include <windows.h>                  // call and that is the 'output' window which displays program
#include "stdio.h"                    // outputs.  The output window of class "Process" is registered
#include "tchar.h"                    // in fnMain_OnCreate(), which is the constructor function, so to
#include "T12_TCLib.h"                // speak, for the "Main" Window Class, RegisterClassEx()'ed...


int rand(void)                        // down in WinMain().  The 'Cancel' button on the main start up...
{                                     // window is for terminating data processing early before it is
next = next * 1103515245 + 12345;    // finished.  The long data processing job I decided upon to demon-
return (unsigned int)(next>>16) & RAND_MAX;   // strate this technique is a follows ...
}                                     //   1) Allocate a multi-million byte array of dash characters;
                                      //   2) Change every 7th space to a 'P';
                                      //   3) Replace every 'P' with a "PU', thereby growing the buffer;
int Rnd(int iMin, int iMax)           //   4) Replace every remaining dash with an '8';
{                                     //   5) Insert a carriage return / line feed every 90 characters,
double dblRange,dblMaxFactor,dblRandomNumber;

dblRange=iMax-iMin;                         
dblMaxFactor=dblRange/RAND_MAX;             
dblRandomNumber=(double)rand();     

return iMin+dblMaxFactor*dblRandomNumber;
}                                     
                                     
                                     
void Format(wchar_t* pBuffer, size_t iNumber) // This algorithm is fully contained within the DoProcessing()
{                                             // function below.  The 1st parameter of DoProcessing() is
wchar_t szBuf1[24];                          // iNumber of type size_t.  The program calls this function
wchar_t szBuf2[24];                          // LINE_BUFFER_COUNT - 2 times.  At this time I have
size_t iDigit=0;                             // LINE_BUFFER_COUNT #defined as 102, so DoProcessing() would
size_t iLen=0;                               // get called 100 times - each with a different iNumber para-
size_t iCtr=1;                               // meter.  Where DoProcessing() gets called from is the worker
size_t j=0;                                  // thread function ProcessingThread().  The worker thread, as 
                                              // well as the output window to which the thread draws output,
memset(szBuf1,0,24*sizeof(wchar_t));         // is created in the button click procedure for the button
memset(szBuf2,0,24*sizeof(wchar_t));         // on the main or start up form/window/dialog described at
#ifdef x64                                   // first above.  That procedure would be fnMain_OnCommand(),
iLen=swprintf(szBuf1,L"%llu",iNumber);       // which handles the program's response to 'Process Data' or
#else                                        // 'cancel' button clicks.  As an aside, the Format() function
iLen=swprintf(szBuf1,L"%u",iNumber);         // just left inserts commas every three places in the iNumber
#endif                                       // parameter of DoProcessing(), e.g., this - 123,456,789,
_wcsrev(szBuf1);                             // instead of this - 123456789.  And the Rnd() function just
for(size_t i=0; i<iLen; i++)                 // above it returns a random integral value between the iMin
{                                            // and iMax parameters.  In the ProcessingThread() function,
     if(iCtr==3)                              // as just described, there is a for loop which runs from 1
     {                                        // to LINE_BUFFER_COUNT -2, i.e., 1 to 98 if LINE_BUFFER_COUNT
        iDigit++;                             // is set to 100, and Rnd() will be called that many times
        szBuf2[j]=szBuf1[i];                  // like so ...
        if(iDigit<iLen)                       //
        {                                     // iNumber = Rnd(10000, 40000);
           j++;                               //
           szBuf2[j]=L',';                    // ... generating 100 random numbers between 10 thousand and 40
        }                                     // thousand which numbers will be passed on to DoProcessing() as
        j++, iCtr=1;                          // follows within the for loop in ProcessingThread()...
     }                                        //
     else                                     // DoProcessing(iNumber,iTickCount);
     {                                        //
        iDigit++;                             // And each call will start my 1) through 5) sequence again
        szBuf2[j]=szBuf1[i];                  // with a new buffer size to do the dashes, P, and PU thing
        j++, iCtr++;                          // as described above.  The Print() function just below
     }                                        // will draw lines of text to the output window such as "It
}                                            // Took 0.218 Seconds To Complete Processing Job Number 32
_wcsrev(szBuf2);                             // Involving 16,913,083 Bytes!".  All in all, its a real mean
wcscpy(pBuffer,szBuf2);                      // data processing machine!
}


void ErrorMemFree(wchar_t** pPtrs, int iNum)  // This little thingie just left likely will never be called.
{                                             // Its purpose is to unravel memory allocations up to the
HANDLE hHeap=NULL;                           // point where a memory allocation failure occurred.

hHeap=GetProcessHeap();
if(hHeap)
{
    for(int i=0; i<iNum; i++)
    {
        if(pPtrs[i])
        {
           HeapFree(hHeap,0,pPtrs[i]);
           pPtrs[i]=NULL;
        }
    }
    HeapFree(hHeap,0,pPtrs);
    pPtrs=NULL;
}
}


void Print(HWND hWnd, ScrollData* pScrDta, size_t& iLine, wchar_t* pszStr)
{
size_t iLen,iRequiredBytes,iWidth;

if(iLine<LINE_BUFFER_COUNT)
{
    if(pszStr)
    {
       iLen=wcslen(pszStr);
       iWidth=iLen*pScrDta->cxChar;
       if(iWidth>(size_t)pScrDta->iMaxWidth)
       {
          pScrDta->iMaxWidth=(int)iWidth;
          SCROLLINFO si;
          si.cbSize = sizeof(si);
          si.fMask  = SIF_RANGE | SIF_PAGE;
          si.nMin   = 0;
          si.nMax   = pScrDta->iMaxWidth / pScrDta->cxChar;
          si.nPage  = pScrDta->cxClient / pScrDta->cxChar;
          SetScrollInfo(hWnd, SB_HORZ, &si, TRUE);
       }
       iRequiredBytes=iLen*sizeof(wchar_t)+sizeof(wchar_t);
       pScrDta->pPtrs[iLine]=(wchar_t*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,iRequiredBytes);
       wcscpy(pScrDta->pPtrs[iLine],pszStr);
       if(iLine>=pScrDta->cyClient/pScrDta->cyChar)
       {
          InvalidateRect(hWnd,NULL,FALSE);
          SendMessage(hWnd,WM_VSCROLL,MAKEWPARAM(SB_LINEDOWN,0),0);
       }
       else
          InvalidateRect(hWnd,NULL,FALSE);
       iLine++;
    }
    else
    {
       if(iLine>=pScrDta->cyClient/pScrDta->cyChar)
          SendMessage(hWnd,WM_VSCROLL,MAKEWPARAM(SB_LINEDOWN,0),0);
       iLine++;
    }
}
}


void DoProcessing(size_t& iNumber, size_t& iTickCount)
{
int i=0,iCtr=0,j;
wchar_t* s1=NULL;
wchar_t* s2=NULL;

DWORD tick=GetTickCount();
int iLineLength=90;
int iNumPs=(int)iNumber/7+1;
int iPuExtLength=(int)iNumber+iNumPs;
int iNumFullLines=iPuExtLength/iLineLength;
int iMaxMem=iPuExtLength+iNumFullLines*2;
s1=(wchar_t*)GlobalAlloc(GPTR,iMaxMem*sizeof(wchar_t));  //Allocate two buffers big enough to hold the original NUMBER of chars
s2=(wchar_t*)GlobalAlloc(GPTR,iMaxMem*sizeof(wchar_t));  //plus substitution of PUs for Ps and CrLfs after each LINE_LENGTH chunk.

for(i=0; i<iNumber; i++)                // 1) Create a  string of dashes
     s1[i]=L'-';

for(i=0; i<iNumber; i++, iCtr++)        // 2) Change every 7th dash to a "P"
{
     if(iCtr==7)
     {
        s1[i]=L'P';
        iCtr=0;
     }
}

iCtr=0;                                 // 3) Substitute 'PUs' for 'Ps'
for(i=0; i<iNumber; i++)
{
     if(wcsncmp(s1+i,L"P",1)==0)
     {
        wcscpy(s2+iCtr,L"PU");
        iCtr+=2;
     }
     else
     {
        s2[iCtr]=s1[i];
        iCtr++;
     }
}

for(i=0; i<iPuExtLength; i++)          // 4) Replace every '-' with an 8;
{
     if(s2[i]==L'-')
        s2[i]=56;   //56 is '8'
}

i=0, j=0, iCtr=0;                      // 5)Put in a CrLf every 90 characters
while(i<iPuExtLength)
{
    s1[j]=s2[i];
    i++, j++, iCtr++;
    if(iCtr==iLineLength)
    {
       s1[j]=13, j++;
       s1[j]=10, j++;
       iCtr=0;
    }
}
iTickCount=GetTickCount()-tick;
GlobalFree(s1), GlobalFree(s2);
}


DWORD ProcessingThread(LPVOID hProcess)
{
wchar_t szSeconds[8],szJob[128],szJobNum[8],szNumber[32];
size_t iLineCount,iNumber,iTickCount,iLine=0;
double dblSeconds,dblTotal=0.0;
ScrollData* pScrDta=NULL;
size_t iTotalBytes=0;

pScrDta=(ScrollData*)GetWindowLongPtr((HWND)hProcess,0);
if(!pScrDta)
    return FALSE;
iLineCount=pScrDta->iNumLines-2;
for(int i=1; i<=iLineCount; i++)
{
     if(GetWindowLongPtr((HWND)hProcess,1*sizeof(void*)))
     {
        iNumber=Rnd(1000000, 25000000);
        iTotalBytes=iTotalBytes+iNumber;
        DoProcessing(iNumber,iTickCount);
        dblSeconds=(double)iTickCount/(double)1000.0;
        dblTotal=dblTotal+dblSeconds;
        FltToTch(szSeconds, dblSeconds, 8, 3, _T('.'),true);
        wcscpy(szJob,(wchar_t*)L"It Took ");
        wcscat(szJob,szSeconds);
        wcscat(szJob,(wchar_t*)L" Seconds To Complete Processing Job # ");
        swprintf(szJobNum,L"%d",i);
        wcscat(szJob,szJobNum);
        wcscat(szJob,(wchar_t*)L" Involving ");
        Format(szNumber,(size_t)iNumber);
        wcscat(szJob,szNumber);
        wcscat(szJob,(wchar_t*)L" Bytes!");
        Print((HWND)hProcess,pScrDta,iLine,szJob);
     }
     else
        return FALSE;
}
SetWindowLongPtr((HWND)hProcess,1*sizeof(void*),(LONG_PTR)FALSE);
szJob[0]=NULL;
Print((HWND)hProcess,pScrDta,iLine,szJob);
Format(szNumber,iTotalBytes);
wcscpy(szJob,(wchar_t*)L"Processing Complete.  ");
wcscat(szJob,szNumber);
wcscat(szJob,(wchar_t*)L" Bytes Were Processed In ");
FltToTch(szSeconds, dblTotal, 8, 3, _T('.'),true);
wcscat(szJob,szSeconds);
wcscat(szJob,(wchar_t*)L" Seconds.");
Print((HWND)hProcess,pScrDta,iLine,szJob);

return TRUE;
}


long fnProcess_OnCreate(WndEventArgs& Wea)  // Index    Offset    What's Stored There
{                                           // =======================================================
CREATESTRUCT* pCreateStruct=NULL;          // 0        0  -  7   ScrollData* ( pScrDta)
ScrollData* pScrDta=NULL;                  // 1        8  - 15   blnContinue (processing in progress)
HANDLE hHeap=NULL;                         // 2       16  - 23   hMain (Main Wnd HWND)
HFONT hFont=NULL;
TEXTMETRIC tm;
HWND hMain;
HDC hdc;

pCreateStruct=(CREATESTRUCT*)Wea.lParam;
hMain=(HWND)pCreateStruct->lpCreateParams;
SetWindowLongPtr(Wea.hWnd,2*sizeof(void*),(LONG_PTR)hMain);
hHeap=GetProcessHeap();
pScrDta=(ScrollData*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,sizeof(ScrollData));
if(!pScrDta)
    return -1;
SetWindowLongPtr(Wea.hWnd,0,(LONG_PTR)pScrDta);
hdc = GetDC(Wea.hWnd);
hFont=CreateFont(-1*(10*GetDeviceCaps(hdc,LOGPIXELSY))/72,0,0,0,FW_SEMIBOLD,0,0,0,ANSI_CHARSET,0,0,DEFAULT_QUALITY,0,(wchar_t*)L"Courier New");
if(!hFont)
    return -1;
HFONT hTmp=(HFONT)SelectObject(hdc,hFont);
GetTextMetrics(hdc, &tm);
pScrDta->cxChar = tm.tmAveCharWidth;
pScrDta->cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * pScrDta->cxChar / 2;
pScrDta->cyChar = tm.tmHeight + tm.tmExternalLeading;
DeleteObject(SelectObject(hdc,hTmp));
ReleaseDC(Wea.hWnd, hdc);
pScrDta->iNumLines=LINE_BUFFER_COUNT;
pScrDta->pPtrs=(wchar_t**)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, sizeof(wchar_t*) * pScrDta->iNumLines);
if(!pScrDta->pPtrs)
    return -1;

return 0;
}


long fnProcess_OnSize(WndEventArgs& Wea)
{
ScrollData* pScrDta=NULL;
SCROLLINFO si;

pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
if(pScrDta)
{
    pScrDta->cxClient = LOWORD(Wea.lParam);
    pScrDta->cyClient = HIWORD(Wea.lParam);
    si.cbSize = sizeof(si) ;
    si.fMask  = SIF_RANGE | SIF_PAGE;
    si.nMin   = 0;
    si.nMax   = pScrDta->iNumLines - 1;
    si.nPage  = pScrDta->cyClient / pScrDta->cyChar;
    SetScrollInfo(Wea.hWnd, SB_VERT, &si, TRUE);
    si.cbSize = sizeof(si);
    si.fMask  = SIF_RANGE | SIF_PAGE;
    si.nMin   = 0;
    si.nMax   = pScrDta->iMaxWidth / pScrDta->cxChar;
    si.nPage  = pScrDta->cxClient / pScrDta->cxChar;
    SetScrollInfo(Wea.hWnd, SB_HORZ, &si, TRUE);
}

return 0;
}


long fnProcess_OnVScroll(WndEventArgs& Wea)
{
ScrollData* pScrDta=NULL;
SCROLLINFO si;

pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
if(pScrDta)
{
    si.cbSize = sizeof(si) ;// Get all the vertial scroll bar information
    si.fMask  = SIF_ALL ;
    GetScrollInfo(Wea.hWnd, SB_VERT, &si);
    int iVertPos = si.nPos; // Save the position for comparison later on
    switch (LOWORD(Wea.wParam))
    {
      case SB_TOP:
           si.nPos = si.nMin ;
           break ;
      case SB_BOTTOM:
           si.nPos = si.nMax ;
           break ;
      case SB_LINEUP:
           si.nPos -= 1 ;
           break ;
      case SB_LINEDOWN:
           si.nPos += 1 ;
           break ;
      case SB_PAGEUP:
           si.nPos -= si.nPage ;
           break ;
      case SB_PAGEDOWN:
           si.nPos += si.nPage ;
           break ;
      case SB_THUMBTRACK:
           si.nPos = si.nTrackPos ;
           break ;
      default:
           break ;
    }
    si.fMask = SIF_POS ;
    SetScrollInfo(Wea.hWnd, SB_VERT, &si, TRUE);
    GetScrollInfo(Wea.hWnd, SB_VERT, &si);
    if(si.nPos != iVertPos)
    {
       ScrollWindow(Wea.hWnd, 0, pScrDta->cyChar*(iVertPos-si.nPos), NULL, NULL);
       UpdateWindow(Wea.hWnd);
    }
}

return 0;
}


long fnProcess_OnHScroll(WndEventArgs& Wea)
{
ScrollData* pScrDta=NULL;
SCROLLINFO si;

pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
if(pScrDta)
{
    si.cbSize = sizeof (si);// Get all the horizontal scroll bar information
    si.fMask  = SIF_ALL;
    GetScrollInfo(Wea.hWnd, SB_HORZ, &si) ;// Save the position for comparison later on
    int iHorzPos = si.nPos;
    switch (LOWORD(Wea.wParam))
    {
      case SB_LINELEFT:
           si.nPos -= 1 ;
           break ;
      case SB_LINERIGHT:
           si.nPos += 1 ;
           break ;
      case SB_PAGELEFT:
           si.nPos -= si.nPage ;
           break ;
      case SB_PAGERIGHT:
           si.nPos += si.nPage ;
           break ;
      case SB_THUMBTRACK:              // case SB_THUMBPOSITION:
           si.nPos = si.nTrackPos ;
           break ;
      default :
           break ;
    }
    si.fMask = SIF_POS;
    SetScrollInfo(Wea.hWnd, SB_HORZ, &si, TRUE);
    GetScrollInfo(Wea.hWnd, SB_HORZ, &si);
    if(si.nPos != iHorzPos)
       ScrollWindow(Wea.hWnd, pScrDta->cxChar*(iHorzPos-si.nPos), 0, NULL, NULL);
}

return 0;
}


long fnProcess_OnPaint(WndEventArgs& Wea)
{
int x,y,iPaintBeg,iPaintEnd,iVertPos,iHorzPos;
ScrollData* pScrDta=NULL;
HFONT hFont=NULL;
PAINTSTRUCT ps;
SCROLLINFO si;
HDC hdc;

hdc = BeginPaint(Wea.hWnd, &ps);
pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
if(pScrDta)
{
    hFont=CreateFont(-1*(10*GetDeviceCaps(hdc,LOGPIXELSY))/72,0,0,0,FW_SEMIBOLD,0,0,0,ANSI_CHARSET,0,0,DEFAULT_QUALITY,0,(wchar_t*)L"Courier New");
    HFONT hTmp=(HFONT)SelectObject(hdc,hFont);
    si.cbSize = sizeof (si) ;// Get vertical scroll bar position
    si.fMask  = SIF_POS ;
    GetScrollInfo(Wea.hWnd, SB_VERT, &si), iVertPos = si.nPos;
    GetScrollInfo(Wea.hWnd, SB_HORZ, &si), iHorzPos = si.nPos;
    if(iVertPos+ps.rcPaint.top/pScrDta->cyChar>0)
       iPaintBeg=iVertPos + ps.rcPaint.top / pScrDta->cyChar;
    else
       iPaintBeg=0;
    if(iVertPos + ps.rcPaint.bottom / pScrDta->cyChar < pScrDta->iNumLines - 1)
       iPaintEnd=iVertPos + ps.rcPaint.bottom / pScrDta->cyChar;
    else
       iPaintEnd=pScrDta->iNumLines-1;
    for(int i = iPaintBeg; i<= iPaintEnd; i++)
    {
        if(pScrDta->pPtrs[i])
        {
           x = pScrDta->cxChar * (1 - iHorzPos);
           y = pScrDta->cyChar * (i - iVertPos);
           TextOut(hdc, x, y, pScrDta->pPtrs[i], (int)wcslen(pScrDta->pPtrs[i]));
        }
    }
    DeleteObject(SelectObject(hdc,hTmp));
}
EndPaint(Wea.hWnd, &ps);

return 0;
}


long fnProcess_OnMouseWheel(WndEventArgs& Wea)
{
int zdelta=GET_WHEEL_DELTA_WPARAM(Wea.wParam);
if(zdelta>0)
{
    for(int i=0; i<10; i++)
        SendMessage(Wea.hWnd,WM_VSCROLL,MAKEWPARAM(SB_LINEUP,0),0);
}
else
{
    for(int i=0; i<10; i++)
        SendMessage(Wea.hWnd,WM_VSCROLL,MAKEWPARAM(SB_LINEDOWN,0),0);
}

return 0;
}


long fnProcess_OnSetCursor(WndEventArgs& Wea)
{
if(GetWindowLongPtr(Wea.hWnd,1*sizeof(void*)))
    SetCursor(LoadCursor(NULL,IDC_WAIT));
else
    DefWindowProc(Wea.hWnd,WM_SETCURSOR,Wea.wParam,Wea.lParam);

return 0;
}


long fnProcess_OnDestroy(WndEventArgs& Wea)
{
HWND hMain=NULL,hBtn=NULL;
ScrollData* pScrDta=NULL;
HANDLE hHeap=NULL;

hMain=(HWND)GetWindowLongPtr(Wea.hWnd,2*sizeof(void*));
hBtn=GetDlgItem(hMain,IDC_BTN_PROCESS_DATA);
EnableWindow(hBtn,TRUE);
SetWindowLongPtr(hMain,0,(LONG_PTR)FALSE);
hHeap=GetProcessHeap();
pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
if(pScrDta->pPtrs)
{
    for(int i=0; i<pScrDta->iNumLines; i++)
    {
        if(pScrDta->pPtrs[i])
           HeapFree(hHeap,0,pScrDta->pPtrs[i]);
    }
    HeapFree(hHeap,0,pScrDta->pPtrs);
}

return 0;
}


LRESULT CALLBACK fnProcess(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
WndEventArgs Wea;

for(unsigned int i=0; i<dim(ProcessEventHandler); i++)
{
     if(ProcessEventHandler[i].iMsg==msg)
     {
        Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
        return (*ProcessEventHandler[i].fnPtr)(Wea);
     }
}

return (DefWindowProc(hwnd, msg, wParam, lParam));
}


long fnMain_OnCreate(WndEventArgs& Wea)
{
wchar_t szClassName[]=L"Process";
WNDCLASSEX wc;

Wea.hIns=((LPCREATESTRUCT)Wea.lParam)->hInstance;
CreateWindowEx(0,L"button",L"Process Data",WS_CHILD|WS_VISIBLE,75,20,125,30,Wea.hWnd,(HMENU)IDC_BTN_PROCESS_DATA,Wea.hIns,0);
CreateWindowEx(0,L"button",L"Cancel",WS_CHILD|WS_VISIBLE,75,70,125,30,Wea.hWnd,(HMENU)IDC_BTN_CANCEL,Wea.hIns,0);
memset(&wc,0,sizeof(wc));
wc.lpszClassName = szClassName;                  wc.lpfnWndProc   = fnProcess;
wc.cbSize        = sizeof (WNDCLASSEX);          wc.hInstance     = Wea.hIns;
wc.hCursor       = LoadCursor(NULL,IDC_ARROW),   wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.cbWndExtra    = 3*sizeof(void*),              wc.style         = CS_HREDRAW | CS_VREDRAW;
RegisterClassEx(&wc);

return 0;
}


long fnMain_OnCommand(WndEventArgs& Wea)
{
wchar_t szClassName[]=L"Process";
HWND hProcess=NULL;

switch(LOWORD(Wea.wParam))
{
   case IDC_BTN_PROCESS_DATA:
     {
        DWORD lpThreadId=0;
        hProcess=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,450,100,850,392,0,0,GetModuleHandle(NULL),Wea.hWnd);
        if(hProcess)
        {
           ShowWindow(hProcess,SW_SHOWNORMAL);
           SetWindowLongPtr(hProcess,1*sizeof(void*),(LONG_PTR)TRUE);
           SetWindowLongPtr(Wea.hWnd,0*sizeof(void*),(LONG_PTR)hProcess);
           EnableWindow((HWND)Wea.lParam,FALSE);
           HANDLE hThread=CreateThread(NULL,0,ProcessingThread,hProcess,0,&lpThreadId);

        }
        break;
     }
   case IDC_BTN_CANCEL:
     {
        hProcess=FindWindow(szClassName,szClassName);
        if(hProcess)
        {
           SetWindowLongPtr(hProcess,1*sizeof(void*),(LONG_PTR)FALSE);
           SetWindowLongPtr(Wea.hWnd,0*sizeof(void*),(LONG_PTR)FALSE);
        }
        break;
     }
}

return 0;
}


long fnMain_OnSetCursor(WndEventArgs& Wea)
{
if(GetWindowLongPtr(Wea.hWnd,0))
    SetCursor(LoadCursor(NULL,IDC_WAIT));
else
{
    DefWindowProc(Wea.hWnd, WM_SETCURSOR, Wea.wParam, Wea.lParam);
    SetCursor(LoadCursor(NULL,IDC_ARROW));
}

return 0;
}


long fnMain_OnDestroy(WndEventArgs& Wea)
{
PostQuitMessage(0);
return 0;
}


LRESULT CALLBACK fnMain(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)  // Main Window Window Procedure
{
WndEventArgs Wea;

for(unsigned int i=0; i<dim(MainEventHandler); i++)
{
     if(MainEventHandler[i].iMsg==msg)
     {
        Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
        return (*MainEventHandler[i].fnPtr)(Wea);
     }
}

return (DefWindowProc(hwnd, msg, wParam, lParam));
}


int WINAPI _tWinMain(HINSTANCE hIns, HINSTANCE hPrevIns, LPTSTR lpszArgument, int iShow)
{
wchar_t szClassName[]=L"Main";
WNDCLASSEX wc;
MSG messages;
HWND hWnd;

memset(&wc,0,sizeof(wc));
wc.lpszClassName=szClassName;                wc.lpfnWndProc=fnMain;
wc.cbSize=sizeof (WNDCLASSEX);               wc.hInstance=hIns;
wc.hCursor=LoadCursor(NULL,IDC_ARROW),       wc.hbrBackground=(HBRUSH)COLOR_BTNSHADOW;
wc.cbWndExtra=1*sizeof(void*);
RegisterClassEx(&wc);
hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,275,625,280,160,HWND_DESKTOP,0,hIns,0);
ShowWindow(hWnd,iShow);
while(GetMessage(&messages,NULL,0,0))
{
    TranslateMessage(&messages);
    DispatchMessage(&messages);
}

return messages.wParam;
}


And here is the header...


// T12_TCLib.h
#ifndef Main_h
#define Main_h

#define dim(x)                (sizeof(x) / sizeof(x[0])) // Used in for loops of Window Procedures
#define LINE_BUFFER_COUNT     102                        // How many lines to set up for scrolling
#define IDC_BTN_PROCESS_DATA  1500                       // Control Id For Button to process data
#define IDC_BTN_CANCEL        1505                       // Control Id for Cancel Button
#define RND_MAX               32767

struct WndEventArgs                                      // This struct amalgamates the parameters of the
{                                                        // Window Procedure together into one object.  .NET
HWND                         hWnd;                      // does this to, e.g., 'PaintEventArgs'.
WPARAM                       wParam;                   
LPARAM                       lParam;                   
HINSTANCE                    hIns;                     
};

long fnMain_OnCreate          (WndEventArgs& Wea);       // These function proto-types just left are event
long fnMain_OnCommand         (WndEventArgs& Wea);       // or message handling procedures for the two Window
long fnMain_OnSetCursor       (WndEventArgs& Wea);       // Classes RegisterClassEx()'ed in this App, i.e.,
long fnMain_OnDestroy         (WndEventArgs& Wea);       // "Main" and "Process".  Just below an EVENTHANDLER 

long fnProcess_OnCreate       (WndEventArgs& Wea);       // object is defined, which contains two members. 
long fnProcess_OnSize         (WndEventArgs& Wea);       // The first member, iMsg, is a #define from the
long fnProcess_OnVScroll      (WndEventArgs& Wea);       // various Window's header files, e.g., WM_CREATE
long fnProcess_OnHScroll      (WndEventArgs& Wea);       // is equal to 1; WM_COMMAND is equal to 273.  The
long fnProcess_OnMouseWheel   (WndEventArgs& Wea);       // other member of EVENTHANDLER, fnPtr, is the run-
long fnProcess_OnSetCursor    (WndEventArgs& Wea);       // time address of the event/message handling proc-
long fnProcess_OnPaint        (WndEventArgs& Wea);       // edure associated with the respective or associated
long fnProcess_OnDestroy      (WndEventArgs& Wea);       // message held in iMsg.  An array of these objects

struct EVENTHANDLER                                      // are created to hold each event/event procedure
{                                                        // address pair for each of the two Window Classes
unsigned int                 iMsg;                      // Registered.  For example, MainEventHandler[] has
long                         (*fnPtr)(WndEventArgs&);   // four elements 0 through 3. 
};                                                       // MainEventHandler[0].iMsg=1=WM_CREATE, and

const EVENTHANDLER            MainEventHandler[]=        // MainEventHandler[0].fnPtr will hold the runtime
{                                                        // address of fnMain_OnCreate().  A for loop is used
{WM_CREATE,                  fnMain_OnCreate},          // in fnMain - the Window Procedure for the "Main"
{WM_COMMAND,                 fnMain_OnCommand},         // Window Class, to iterate through each array element
{WM_SETCURSOR,               fnMain_OnSetCursor},       // trying to make a match between the MSG parameter
{WM_DESTROY,                 fnMain_OnDestroy}          // of the Window Procedure, and the iMsg member of
};                                                       // the EVENTHANDLER object.  If a match is made, the

const EVENTHANDLER            ProcessEventHandler[]=     // associated function is called through its address
{                                                        // held in the fnPtr member.  Pretty, d*** slick!
{WM_CREATE,                  fnProcess_OnCreate},       // I wish I'd have been smart enough to figure some-
{WM_SIZE,                    fnProcess_OnSize},         // thing like this out for myself, but I learned it
{WM_VSCROLL,                 fnProcess_OnVScroll},      // from Douglas Boling who writes books for Microsoft
{WM_HSCROLL,                 fnProcess_OnHScroll},      // Press.  And he gives credit for it to Ray Duncan.
{WM_MOUSEWHEEL,              fnProcess_OnMouseWheel},   // So what you are really seeing here is an alter-
{WM_SETCURSOR,               fnProcess_OnSetCursor},    // native to the switch construct usually used in
{WM_PAINT,                   fnProcess_OnPaint},        // Window Procedures to map incoming messages to the
{WM_DESTROY,                 fnProcess_OnDestroy}       // code which handles each respective message.  Its
};                                                       // a much more elegant solution to the problem.

struct ScrollData                                        // Further note how the dim macro is used in the for
{                                                        // loop to determine the number of elements in each
wchar_t**                    pPtrs;                     // array.
int                          iNumLines;
int                          cxChar;
int                          cxCaps;
int                          cyChar;
int                          cxClient;
int                          cyClient;
int                          iMaxWidth;
};

extern "C" int _fltused  =    1;
static unsigned int next =    1;
#endif


continued...
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 02, 2016, 12:10:49 AM
     I'll amalgamate all this into a zip, and the new TCLib.lib will have all the improvements and additions.  But before I close, I'd like to mention that with the addition of fprintf.obj to TCLib.lib, I can now present a debug version of ec02a.cpp where defining Debug when TCLib is being used will result in debug output.  Here is the output file created when Debug is defined...


Entering fnWndProc_OnCreate()
  Wea.hWnd         = 00000000003105F0
  sizeof(LRESULT)  = 8
  blnDpiAware      = 1
  pDpiData         = 000001DD230C0AD0
  dpiX             = 96
  dpiY             = 96
  pDpiData->rx     = 1.000
  pDpiData->ry     = 1.000
  iCxScreen        = 1920
  iCyScreen        = 1080
  x                = 791
  y                = 380
Leaving fnWndProc_OnCreate()

Entering fnWndProc_OnDestroy()
  Wea.hWnd         = 00000000003105F0
  pDpiData         = 000001DD230C0AD0
  blnFree          = 1
  hFont1           = 0000000000000000
  blnFree(hFont1)  = 1
  hFont2           = 0000000000000000
  blnFree(hFont2)  = 1
Leaving fnWndProc_OnDestroy()


Here's the new ec02a.cpp and Form1.h headers for this example of Jim's ...


// Uses/links with  LIBCMT:   cl ec02a.cpp /O1 /Os /MT kernel32.lib user32.lib gdi32.lib
// Uses/links with TCLib:     cl ec02a.cpp /O1 /Os /GS- /link TCLib.lib kernel32.lib user32.lib gdi32.lib
//  5,632 Bytes               TCLib, x86, VC19, UNICODE
//  6,144 Bytes               TCLib, x64, VC15, UNICODE
//  7,168 Bytes               TCLib, x64, VC19, UNICODE
// 19,968 Bytes               GCC 4.8, x64, UNICODE
// 88,064 Bytes               VC15, x64 Std. UNICODE, Build With C Runtime
#define TCLib
#ifndef UNICODE
   #define UNICODE            // App demonstrates Dpi Aware coding.  Note, you can't build with both
#endif                        // #define TCLib and #define Debug.  As of yet I have not provided
#ifndef _UNICODE              // FILE* i/o capabilities in TCLib.  If TCLib is defined, TCLib specific
   #define _UNICODE           // versions of stdio.h and tchar.h must be used.  Hence, those files
#endif                        // need to be placed in the app's directory, or pathed specifically.
#include <windows.h>
#ifdef TCLib                  // I'm explicitely calling SetProcessDPIAware() in SetMyProcessDpiAware()
   #include "stdio.h"         // because that function isn't available in all build environments
   #include "tchar.h"         // out there.
#else   
   #include <stdio.h>
   #include <tchar.h>
#endif   
#include "Form1.h"            // If Debug is defined an Output.txt Log File is created, and debug
#define Debug                 // information logged to there.
#ifdef Debug
   HANDLE fp=NULL;
#endif


void SetMyProcessDpiAware()   // This function isn't available on the older GCC versions I have.
{                             // Calling this function tells Windows the app is High DPI Aware,
BOOL (__stdcall* pFn)(void); // and so Windows won't virtualize fonts and sizes of things and
                              // wreck them.
HINSTANCE hInstance=LoadLibrary(_T("user32.dll"));
if(hInstance)
{
    pFn=(BOOL (__stdcall*)(void))GetProcAddress(hInstance,"SetProcessDPIAware");
    if(pFn)
       pFn();
    FreeLibrary(hInstance);
}
}


LRESULT fnWndProc_OnCreate(WndEventArgs& Wea)
{
BOOL blnDpiAware=FALSE;      // This is the creation/instantiation function, i.e., Constructor
DpiData* pDpiData=NULL;      // for the "Form1" Class registered in WinMain, i.e., the main
TCHAR szBuffer[6];           // and only app window or start up form/dialog/window.  In WinMain
HANDLE hHeap=NULL;           // I allocate three 'spots' for app specific data in the
HFONT hFont1=NULL;           // WNDCLASS::cbWndExtra bytes.  That would be 3 * sizeof(void*) or
HFONT hFont2=NULL;           // 24 bytes in x64 and 12 bytes in x86.  I store the DpiData* in
int dpiX,dpiY;               // offset #1, hFont1 in offset #2, and hFont2 in offset #3.
HDC hDC=NULL;               
HWND hCtl;

#ifdef Debug
fp=CreateFile(_T("Output.txt"),GENERIC_WRITE,0,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
_ftprintf(fp,_T("Entering fnWndProc_OnCreate()\r\n"));
_ftprintf(fp,_T("  Wea.hWnd         = %p\r\n"),Wea.hWnd);
_ftprintf(fp,_T("  sizeof(LRESULT)  = %Iu\r\n"),sizeof(LRESULT));
#endif
Wea.hIns    = ((LPCREATESTRUCT)Wea.lParam)->hInstance;
hHeap       = GetProcessHeap();
blnDpiAware = IsProcessDPIAware();
#ifdef Debug
_ftprintf(fp,_T("  blnDpiAware      = %d\r\n"),blnDpiAware);
#endif

// Deal With DPI                                 // This code will normalize, fix, whatever you want
hDC          = GetDC(NULL);                      // to call it, the bad things that happen on modern
dpiX         = GetDeviceCaps(hDC, LOGPIXELSX);   // displays when the user alters DPI settings through
dpiY         = GetDeviceCaps(hDC, LOGPIXELSY);   // Control Panel.  If code like this isn't implemented,
pDpiData     = (DpiData*)HeapAlloc               // and the user alters DPI settings from those used
(                                                // by the coder (you) when the app was developed, text
  hHeap,                                          // and controls will almost certainly be 'clipped', and
  HEAP_ZERO_MEMORY,                               // fonts will be 'virtualized' such that they'll become
  sizeof(DpiData)                                 // fuzzy.  Note I have a DpiData struct defined in
);                                               // Form1.h.  The purpose of that is to persist DPI
if(!pDpiData)                                    // factors across function calls.  Not needed here, but
    return -1;                                    // useful in more complex apps.
SetWindowLongPtr(Wea.hWnd,0*sizeof(void*),(LONG_PTR)pDpiData);
pDpiData->rx = dpiX/96.0;
pDpiData->ry = dpiY/96.0;
ReleaseDC(Wea.hWnd,hDC);
#ifdef Debug
_ftprintf(fp,_T("  pDpiData         = %p\r\n"),pDpiData);
_ftprintf(fp,_T("  dpiX             = %d\r\n"),dpiX);
_ftprintf(fp,_T("  dpiY             = %d\r\n"),dpiY);
FltToTch(szBuffer, pDpiData->rx, 6, 3, _T('.'),true);
_ftprintf(fp,_T("  pDpiData->rx     = %s\r\n"),szBuffer);
FltToTch(szBuffer, pDpiData->ry, 6, 3, _T('.'),true);
_ftprintf(fp,_T("  pDpiData->ry     = %s\r\n"),szBuffer);
#endif

// Position/Center Main App Window
int iCxScreen=GetSystemMetrics(SM_CXSCREEN);
int iCyScreen=GetSystemMetrics(SM_CYSCREEN);
int x = (iCxScreen/2 - 338/2)/pDpiData->rx;
int y = (iCyScreen/2 - 320/2)/pDpiData->ry;
MoveWindow(Wea.hWnd,SizX(x),SizY(y),SizX(338),SizY(320),FALSE);
#ifdef Debug
_ftprintf(fp,_T("  iCxScreen        = %d\r\n"),iCxScreen);
_ftprintf(fp,_T("  iCyScreen        = %d\r\n"),iCyScreen);
_ftprintf(fp,_T("  x                = %d\r\n"),x);
_ftprintf(fp,_T("  y                = %d\r\n"),y);
#endif
 
// Instantiate Child Window Objects
double dblFont1 = 10.0;
double dblFont2 = 10.0;
DWORD  dwStyles = WS_CHILD|WS_VISIBLE|WS_BORDER|ES_AUTOVSCROLL|ES_WANTRETURN|ES_AUTOHSCROLL|ES_MULTILINE;

hCtl=CreateWindow(_T("edit"),NULL,dwStyles,SizX(15),SizY(15),SizX(295),SizY(112),Wea.hWnd,(HMENU)IDC_EDIT1,Wea.hIns,NULL);
hFont1=CreateFont(-MulDiv(dblFont1,dpiY,72),0,0,0,FW_NORMAL,0,0,0,ANSI_CHARSET,OUT_TT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,FF_DONTCARE,_T("Arial"));
SetWindowLongPtr(Wea.hWnd,1*sizeof(void*),(LONG_PTR)hFont1);
SendMessage(hCtl, (UINT)WM_SETFONT, (WPARAM)hFont1, (LPARAM)TRUE);
SetWindowText(hCtl, _T(" Text in this control set to RED ON WHITE"));

hCtl=CreateWindow(_T("edit"),NULL,dwStyles,SizX(15),SizY(160),SizX(295),SizY(112),Wea.hWnd,(HMENU)IDC_EDIT2,Wea.hIns,NULL);
hFont2=CreateFont(-MulDiv(dblFont2,dpiY,72),0,0,0,FW_NORMAL,0,0,0,ANSI_CHARSET,OUT_TT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,FF_DONTCARE,_T("Garamond"));
SetWindowLongPtr(Wea.hWnd,2*sizeof(void*),(LONG_PTR)hFont2);
SendMessage(hCtl, (UINT)WM_SETFONT, (WPARAM)hFont2, (LPARAM)TRUE);
SetWindowText(hCtl, _T(" Text in this control set to Green ON BLACK"));
#ifdef Debug
_ftprintf(fp,_T("Leaving fnWndProc_OnCreate()\r\n\r\n"));
#endif

return 0;
}


LRESULT fnWndProc_OnCtlColorEdit(WndEventArgs& Wea)
{
int iCtlId=GetDlgCtrlID((HWND)Wea.lParam);

if(iCtlId==IDC_EDIT1)
{
    SetTextColor((HDC)Wea.wParam, RGB(255, 0, 0));
   SetBkMode((HDC)Wea.wParam,TRANSPARENT);
   return (LRESULT)GetStockObject(WHITE_BRUSH);
}
if(iCtlId==IDC_EDIT2)
{
    SetTextColor((HDC)Wea.wParam, RGB(0, 255, 0));
   SetBkMode((HDC)Wea.wParam,TRANSPARENT);
   return (LRESULT)GetStockObject(BLACK_BRUSH);
}  

return DefWindowProc(Wea.hWnd, WM_CTLCOLOREDIT, Wea.wParam, Wea.lParam);
}


LRESULT fnWndProc_OnDestroy(WndEventArgs& Wea)
{
DpiData* pDpiData=NULL;
BOOL blnFree=FALSE;
HFONT hFont1=NULL;
HFONT hFont2=NULL;
HFONT hFont=NULL;

#ifdef Debug
_ftprintf(fp,_T("Entering fnWndProc_OnDestroy()\r\n"));
_ftprintf(fp,_T("  Wea.hWnd         = %p\r\n"),Wea.hWnd);
#endif
pDpiData=(DpiData*)GetWindowLongPtr(Wea.hWnd,0*sizeof(void*));
blnFree=HeapFree(GetProcessHeap(),0,pDpiData);
#ifdef Debug
_ftprintf(fp,_T("  pDpiData         = %p\r\n"),pDpiData);
_ftprintf(fp,_T("  blnFree          = %d\r\n"),blnFree);
#endif
hFont=(HFONT)GetWindowLongPtr(Wea.hWnd,1*sizeof(void*));
blnFree=DeleteObject(hFont);
#ifdef Debug
_ftprintf(fp,_T("  hFont1           = %p\r\n"),hFont1);
_ftprintf(fp,_T("  blnFree(hFont1)  = %d\r\n"),blnFree);
#endif
hFont=(HFONT)GetWindowLongPtr(Wea.hWnd,2*sizeof(void*));
blnFree=DeleteObject(hFont);
#ifdef Debug
_ftprintf(fp,_T("  hFont2           = %p\r\n"),hFont2);
_ftprintf(fp,_T("  blnFree(hFont2)  = %d\r\n"),blnFree);
#endif
PostQuitMessage(0);
#ifdef Debug
_ftprintf(fp,_T("Leaving fnWndProc_OnDestroy()\r\n"));
CloseHandle(fp);
#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 _tWinMain(HINSTANCE hIns, HINSTANCE hPrevIns, LPTSTR lpszArgument, int iShow)
{
TCHAR szClassName[]=_T("Form1");
MSG messages;
WNDCLASS wc;
HWND hWnd;

memset(&wc,0,sizeof(WNDCLASS));
SetMyProcessDpiAware();
wc.lpszClassName = szClassName,                     wc.lpfnWndProc   = fnWndProc;
wc.hIcon         = LoadIcon(NULL,IDI_APPLICATION),  wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
wc.hInstance     = hIns,                            wc.hCursor       = LoadCursor(NULL,IDC_ARROW),         
wc.cbWndExtra    = 3*sizeof(void*);
RegisterClass(&wc);
hWnd=CreateWindow(szClassName,_T("Edit Ctrl Colors and Fonts"),WS_OVERLAPPEDWINDOW,0,0,0,0,HWND_DESKTOP,0,hIns,0);
ShowWindow(hWnd,iShow);
while(GetMessage(&messages,NULL,0,0))
{
    TranslateMessage(&messages);
    DispatchMessage(&messages);
}

return messages.wParam;
}


Form1.h

//Form1.h
#ifndef Form_h
#define Form_h

#define IDC_EDIT1                 2000
#define IDC_EDIT2                 2005
#define dim(x)                    (sizeof(x) / sizeof(x[0]))
#define SizX(x)                   (int)(x * pDpiData->rx)       // For DPI Scaling Calculations And Purposes
#define SizY(y)                   (int)(y * pDpiData->ry)       // For DPI Scaling Calculations And Purposes
#ifdef TCLib
   extern "C" int                 _fltused=1;
#endif
   
struct WndEventArgs
{
HWND                             hWnd;
WPARAM                           wParam;
LPARAM                           lParam;
HINSTANCE                        hIns;
};

LRESULT fnWndProc_OnCreate        (WndEventArgs& Wea);
LRESULT fnWndProc_OnCtlColorEdit  (WndEventArgs& Wea);
LRESULT fnWndProc_OnDestroy       (WndEventArgs& Wea);

struct EVENTHANDLER
{
unsigned int                     iMsg;
LRESULT                          (*fnPtr)(WndEventArgs&);
};

const EVENTHANDLER EventHandler[]=
{
{WM_CREATE,                      fnWndProc_OnCreate},
{WM_CTLCOLOREDIT,                fnWndProc_OnCtlColorEdit},
{WM_DESTROY,                     fnWndProc_OnDestroy}
};

struct DpiData                   
{                                 
double                           rx;
double                           ry;
};

#endif

     In closing this post I'd like to say that the above program, which requires floating point support to allow high DPI Aware functionality, and my previous Multi-threaded string processing example, seems to move TCLib out of the proof of concept or testing stage, and into a realm where its use in mainstream coding projects could become a reality.   

zip next post
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 02, 2016, 12:12:08 AM
Here's the zip
Title: Re: Moving TCLib Into Mainstream
Post by: James C. Fuller on April 02, 2016, 12:41:41 AM
Fred,
  Good stuff. I'll look at it in the AM.

James
Title: Re: Moving TCLib Into Mainstream
Post by: Patrice Terrier on April 02, 2016, 10:16:21 AM
Fred

Once you are done with it, i have some good candidates to check your TCLib with VS Community 2015.

...
Title: Re: Moving TCLib Into Mainstream
Post by: James C. Fuller on April 02, 2016, 07:04:50 PM
Fred,
  You have UNICODE defined in your Strings.cpp file. I did not think your class was for unicode use only?
Why did you name stricmp _stricmp. All others strcpy, strcat, strcmp are defined without the underscore?

I needed this one :

//=====================================================================================
//               Developed As An Addition To Matt Pietrek's LibCTiny.lib
//                            By James C. Fuller March 2016
//
//                      cl strchr.cpp /c /W3 /DWIN32_LEAN_AND_MEAN
//=====================================================================================
#include <windows.h>
#include "string.h"

char * __cdecl _strchr (const char *s, int c)
{
    do {
        if (*s == c)
        {
            return (char*)s;
        }
    } while (*s++);
    return (0);
}

wchar_t * __cdecl _wcschr (const wchar_t *s, int c)
{
    do {
        if (*s == c)
        {
            return (wchar_t*)s;
        }
    } while (*s++);
    return (0);
}


I am set up at present to only use and compile 64bit unicode source with TCLib
All your demos except Demo18 I believe are in that format?
Can you do a Demo18 for unicode 64bit?

I needed to tweak a few items but first impressions are good.

James
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 03, 2016, 04:32:18 AM
Everything should work in either asci or wide, x64 or x86, VS 2008 or VS2015, although to be honest I'm starting to slack off a bit in my testing of x86.

Yes, my String Class is set up to work with either ascii or unicode.  To use unicode the main calling *.cpp files should have #define UNICODE in them, and it should also be defined in Strings.cpp.  If its undefined in both (or multiple) places then the ascii takes over.  The reason I frequently use this construct...


#ifndef UNICODE
#define UNICODE
#endif
#ifndef _UNICODE
#define _UNICODE
#endif


...is because the Visual Studio IDE predefines the wide character set, so if I didn't use the #ifndef, warnings would be generated on multiple definitions of the equate.  Of course, CodeBlocks doesn't predefine wide character, and I like my sourse to be able to be compiled in both without warnings, so that's where that comes from.  Neither GCC nor VC++ predefine wide character; its just an issue with the Visual Studio IDE.

I've been fighting the whole wide character thing with C/C++ since the beginning in the late 90s.  By fighting with it I mean trying to come up with the easiest way of dealing with it.  I think the end result of that for me is to leave Strings.cpp and Strings.h coded with the tchar macros, which of course are horribly ugly, but in my other source files just do the #defines of UNICODE and _UNICODE, and use the non-tchar forms, i.e., wcslen, wcscat, wcscpy, etc.  And of course preface string literals with L.

I've had philosophical discussions of this issue at www.cplusplus.com with other coders, and that's the way I like to handle it.  Let me pose for a moment some of the issues.  And let me bring up the Forger's Win32 Tutorial (a popular C++ destination for folks learning Win32).  All the code there is quite good, but when folks try to compile it with Visual Studio, they immediately get errors due to Visual Studio pre-defining the wide character set.  For example, CreateWindowEx is used in that tutorial to create windows, and there is no specification in the code as to the character set being used.  Therefore, it will compile fine with CodeBlocks using GCC, but not with Visual Studio due to the latter's propensity to specify the wide character set. 

Of course, to solve the problem one could strictly use the tchar macros for absolutely everything as Petzold's last Win32 book does (version 5 or 6; I forget which).  But they are just so horrible!  The personal solution I have come up with is to use the #ifndef setup I first described.  That allows my code to be correct no matter what Windows compiler is used. 
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 03, 2016, 04:43:46 AM
In terms of _stricmp, this is from my main C reference written by Peter Aitken and Bradley Jones...

Quote
String comparison functions that are non-case-sensitive operate in the same format as the case sensitive function strcmp.  Most compilers have their own non-case-sensitive comparison functions.  Zortech uses the library function strcmpl().  Microsoft uses a function called _stricmp().  Borland has two functions strcmpi() and stricmp().  You need to check your library reference manual to determine which function is appropriate for your compiler...

Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 03, 2016, 04:46:47 AM
And this link...

https://msdn.microsoft.com/en-us/library/ms235365.aspx

...states that stricmp is deprecated, although to be honest I don't typically let that stop me from using something because I'm deprecated myself! ;D
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 03, 2016, 04:52:06 AM
Quote
You have UNICODE defined in your Strings.cpp file. I did not think your class was for unicode use only?

Just dawned on me what you are getting at.  Yes, that ought to be in the Strings.h file instead I guess, with the other #defines.  Somehow or other I got in the habit of putting it in the Strings.cpp file instead.   I suspect everything should work as expected if you transfer it to there.
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 03, 2016, 04:55:09 AM
We'll make your strchr an official addition.  Thanks!  There's probably others that could be added too.

About that _stricmp, stricmp thing.  Had me confused for a bit too!  I can reconsider the naming if you want, but from the research I've done, it looks like _stricmp is the right one for Microsoft coding - even with GCC I'd think.  Not?
Title: Re: Moving TCLib Into Mainstream
Post by: James C. Fuller on April 03, 2016, 11:17:02 AM
Quote from: Frederick J. Harris on April 03, 2016, 04:52:06 AM
Quote
You have UNICODE defined in your Strings.cpp file. I did not think your class was for unicode use only?

Just dawned on me what you are getting at.  Yes, that ought to be in the Strings.h file instead I guess, with the other #defines.  Somehow or other I got in the habit of putting it in the Strings.cpp file instead.   I suspect everything should work as expected if you transfer it to there.

I believe  it should be defined (or not ) in your app that includes String.cpp

James
Title: Re: Moving TCLib Into Mainstream
Post by: James C. Fuller on April 03, 2016, 01:22:34 PM
Fred,
  re: stricmp, I agree . Some bc9 library routines used stricmp but I overlooked a #define stricmp _stricmp I had in my translations. 

After hacking a bit on the Strings.cpp file I am now able to create non-unicode apps.
I removed the UNICODE defines and added guards for the others
James


#ifndef _WINDOWS_
#include  <windows.h>
#endif

#ifndef string_h
#include  "string.h"
#endif

#ifndef stdio_h
#include  "stdio.h"
#endif

#ifndef Strings_h
#include  "Strings.h"
#endif

#ifndef tchar_h
#include  "tchar.h"
#endif


Title: Re: Moving TCLib Into Mainstream
Post by: James C. Fuller on April 03, 2016, 04:15:48 PM
Fred,
  I've done a bit of research on File IO but am not up to speed yet on how to implement without FILE or STL, especially a "Line Input" routine.

I already had a bc9 fprint which now works fine.
I do have two functions to read and save a complete ansi text file when app is in Unicode mode.

I did not see any file io comments on the handmade hero link.

James

Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 03, 2016, 05:30:45 PM
Glad you mentioned that about the situation with FILE.  I consider that a downfall of the code I posted regarding fprintf, but the actual situation was that I wanted to implement something quick and dirty which nontheless worked well enough for the debugging purpose I needed at that moment.  Give me a minute and I'll come up with a link to some work a Mike_V did on this.  What he did may work.  I simply never tested it.  Be back in a minute...
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 03, 2016, 05:37:19 PM
Here's the link...

http://www.codeproject.com/Articles/15156/Tiny-C-Runtime-Library

If you can't get the code for some reason I have it all.  I actually did quite a bit of work early on with Mike_V's code.  That's actually how I got clued in on how to set up wide character support.  Note he doesn't use command line compiling or batch files like we do.  He's using the VS 2005 or VS 2008 IDE.  And his code won't compile as x64 unless you remove one of his floating point math *.cpp files with inline assembler in it (inline asm mostly doesn't work in x64).  Once you remove that his code compiles easily from the command line of in Visual Studio.  I spent some time looking at his file implementations but didnt completely figure it all out.  Its somewhat complex.
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 03, 2016, 05:40:17 PM
And thanks for pointing that out about my UNICODE defines in Strings.cpp.  That's something I had been doing wrong for so long I never even took notice of it.  Sometimes I don't pay as close attention to proper form as I should.
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 03, 2016, 05:43:05 PM
And about that _stricmp thing again - not to keep beating a dead horse, but I just looked up the actual inclusions of string compare functions in the C Standard and these are the only ones there...

memcmp     Compare two blocks of memory (function )

strcmp      Compare two strings (function )

strcoll       Compare two strings using locale (function )

strncmp      Compare characters of two strings (function )

strxfrm      Transform string using locale (function )

I've never even used strcoll or strxfrm.

Title: Re: Moving TCLib Into Mainstream
Post by: James C. Fuller on April 03, 2016, 06:25:30 PM
Fred,
  I haven't looked at Mike_V's code yet but I did run into a link (lost it) where the author was using
memory mapped files for file io rather than the normal interprocess communication.?

Re _stricmp. It is not a c standard but who cares. The target for me is VS 64bit unicode :)

James

Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 03, 2016, 06:38:54 PM
[QUOTE]
The target for me is VS 64bit unicode


That's mostly true for me too James. 

Wouldn't know anything about a memory mapped file solution to the issue.  But take a look at what Mike_V did with the FILE object.  I think it might be workable (that is, if it works! ;D ).

From my standpoint what I did with the HANDLE instead of FILE objects is temporarily if not long term workable, and fixing it is quite a ways down on my todo list.  But I can see for you in working with your BCX translation code it is far from optimal having two functions with the same name one of which uses FILE* and the other HANDLE.  Maybe you would want to work on fixing it and make a joint project out of this?

For myself, I was next wanting to explore if all this works with ODBC.  I looked over the headers and at least looking at those I didn't see anything in them to suggest the C Runtime was needed.  I'll see.

Then I wanted to explore whether my ActiveX Grid Control could be compiled against TCLib instead of loading C Runtime.  I was never able to get my C++ versions anywhere near as small as My PowerBASIC version.  If I can get it working with TCLib I'm sure I can remedy that.
Title: Re: Moving TCLib Into Mainstream
Post by: James C. Fuller on April 03, 2016, 06:48:42 PM
Fred,
  I was thinking more of a file class wrapper as we are using c++. I'll see if I can find that MMF article. It may have been in code project too.

what is
extern "C" int _fltused=1
for?

Sorry if it's in your explanation text. I figured it would be faster to ask than reread. :)

James
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 03, 2016, 06:55:08 PM
Yes, the extern "C" int _fltused is absolutely of critical importance.  Without that its not even possible to declare a double and assign a floating point number to it.  I'd call that pretty critical. 

Actually, to save you the bother of reading and building Mike_V's work I can test whether his FILE implementation works or not.  I spent days and days working with his code (but never tested that in particular), before abandoning it cuz of what I perceive he screwed up in his sprintf code.  So it would be easy for me to test in a few minutes.

But I may not be able to do it 'till tonight or tomorrow.  Have to find out what I'm doing this afternoon. 
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 03, 2016, 08:49:11 PM
Turns out my tentative plans to do a little horseback riding this afternoon were cancelled.  The weather here has taken a turn for the worst, to say the least!  I've a crab-apple tree outside my window of my house in full bloom, but right now we've 20 - 40 mph gusting winds and its like 29 degrees F with snow fluries blowing around.

So I just sunk like an hour into seeing if I could get Mike_V's FILE related stdio code working.  No can do.  I keep getting all kinds of bizarre errors, which one gets inured to in doing this 'off the beaten path' type work.  It would really take me several days of work to get to the bottom of it I think, and I'm not inclined right now.  For example though, here are his file.cpp and libct.h files...


// file.cpp

// file routine overrides

// 08/20/05 (mv)
// cl file.cpp /O1 /Os /GL /D "WIN32" /D "NDEBUG" /D "_LIB" /D "_CRT_SECURE_NO_DEPRECATE" /D "_CRT_NONSTDC_NO_DEPRECATE" /FD /MT /GS- /GR- /W4 /c /Zi /TP  /GR-
#include <windows.h>
#include <stdio.h>
#include "libct.h"

/* FILE, as defined in stdio.h
struct _iobuf {
        char *_ptr;
        int   _cnt;
        char *_base; Used to store HANDLE
        int   _flag;
        int   _file;
        int   _charbuf;
        int   _bufsiz;
        char *_tmpfname;
        };
typedef struct _iobuf FILE;
*/

//_flag values (not the ones used by the normal CRT
#define _FILE_TEXT 0x0001
#define _FILE_EOF 0x0002
#define _FILE_ERROR 0x0004

struct _FILE : public FILE
{
void set_handle(HANDLE h) {_base = (char*)h;};
HANDLE get_handle() const {return (HANDLE)_base;};
};

// used directly by the stdin, stdout, and stderr macros
_FILE __iob[3];
FILE *__iob_func() {return (FILE*)__iob;}

void _init_file()
{
// STDIN
__iob[0].set_handle(GetStdHandle(STD_INPUT_HANDLE));
__iob[0]._flag = _FILE_TEXT;

// STDOUT
__iob[1].set_handle(GetStdHandle(STD_OUTPUT_HANDLE));
__iob[1]._flag = _FILE_TEXT;

// STDERR
__iob[2].set_handle(GetStdHandle(STD_ERROR_HANDLE));
__iob[2]._flag = _FILE_TEXT;
}


BEGIN_EXTERN_C

/*int _fileno(FILE *fp)
{
return (int)fp; // FIXME:  This doesn't work under Win64
}

HANDLE _get_osfhandle(int i)
{
return (HANDLE)i; // FIXME:  This doesn't work under Win64
}*/

FILE *fopen(const char *path, const char *attrs)
{
DWORD access, disp;
if (strchr(attrs, 'w'))
{
access = GENERIC_WRITE;
disp = CREATE_ALWAYS;
}
else
{
access = GENERIC_READ;
disp = OPEN_EXISTING;
}

HANDLE hFile = CreateFileA(path, access, 0, 0, disp, 0, 0);
if (hFile == INVALID_HANDLE_VALUE)
return 0;

_FILE *file = new _FILE;
memset(file, 0, sizeof(_FILE));
file->set_handle(hFile);

if (strchr(attrs, 't'))
file->_flag |= _FILE_TEXT;

return file;
}

FILE *_wfopen(const wchar_t *path, const wchar_t *attrs)
{
DWORD access, disp;
if (wcschr(attrs, L'w'))
{
access = GENERIC_WRITE;
disp = CREATE_ALWAYS;
}
else
{
access = GENERIC_READ;
disp = OPEN_EXISTING;
}

HANDLE hFile = CreateFileW(path, access, 0, 0, disp, 0, 0);
if (hFile == INVALID_HANDLE_VALUE)
return 0;

_FILE *file = new _FILE;
memset(file, 0, sizeof(_FILE));
file->set_handle(hFile);

if (wcschr(attrs, L't'))
file->_flag |= _FILE_TEXT;

return file;
}


int fprintf(FILE *fp, const char *s, ...)
{
va_list args;
va_start(args, s);

char bfr[1024];
int len = wvsprintfA(bfr, s, args);
va_end(args);

fwrite(bfr, len+1, sizeof(char), fp);
return len;
}

int fwprintf(FILE *fp, const wchar_t *s, ...)
{
va_list args;
va_start(args, s);

wchar_t bfr[1024];
int len = wvsprintfW(bfr, s, args);

va_end(args);

char ansibfr[1024];
WideCharToMultiByte(CP_ACP, 0, bfr, -1, ansibfr, sizeof(ansibfr), 0, 0);

fwrite(ansibfr, len+1, sizeof(char), fp);
return len;
}


int fclose(FILE *fp)
{
CloseHandle(((_FILE*)fp)->get_handle());
delete fp;
return 0;
}

int feof(FILE *fp)
{
return (fp->_flag & _FILE_EOF) ? 1 : 0;
}

int fseek(FILE *str, long offset, int origin)
{
DWORD meth = FILE_BEGIN;
if (origin == SEEK_CUR)
meth = FILE_CURRENT;
else if (origin == SEEK_END)
meth = FILE_END;
SetFilePointer(((_FILE*)str)->get_handle(), offset, 0, meth);
((_FILE*)str)->_flag &= ~_FILE_EOF;
return 0;
}

long ftell(FILE *fp)
{
return SetFilePointer(((_FILE*)fp)->get_handle(), 0, 0, FILE_CURRENT);
}

size_t fread(void *buffer, size_t size, size_t count, FILE *str)
{
if (size*count == 0)
return 0;
if (feof(str))
return 0;

HANDLE hFile = ((_FILE*)str)->get_handle();
int textMode = ((_FILE*)str)->_flag & _FILE_TEXT;

char *src;
if (textMode)
src = (char*)malloc(size*count);
else
src = (char*)buffer;

DWORD br;
if (!ReadFile(hFile, src, (DWORD)(size*count), &br, 0))
((_FILE*)str)->_flag |= _FILE_ERROR;
else if (!br) // nonzero return value and no bytes read = EOF
((_FILE*)str)->_flag |= _FILE_EOF;

if (!br)
return 0;

// Text-mode translation is always ANSI
if (textMode) // text mode: must translate CR -> LF
{
char *dst = (char*)buffer;
for (DWORD i = 0; i < br; i++)
{
if (src[i] != '\r')
{
*dst++ = src[i];
continue;
}

// If next char is LF -> convert CR to LF
if (i+1 < br)
{
if (src[i+1] == '\n')
{
*dst++ = '\n';
i++;
}
else
*dst++ = src[i];
}
else if (br > 1)
{
// This is the hard part: must peek ahead one byte
DWORD br2 = 0;
char peekChar = 0;
ReadFile(hFile, &peekChar, 1, &br2, 0);
if (!br2)
*dst++ = src[i];
else if (peekChar == '\n')
*dst++ = '\n';
else
{
fseek(str, -1, SEEK_CUR);
*dst++ = src[i];
}
}
else
*dst++ = src[i];
}

free(src);
}

return br/size;
}

size_t fwrite(const void *buffer, size_t size, size_t count, FILE *str)
{
DWORD bw = 0, bw2 = 0;

if (size*count == 0)
return 0;

HANDLE hFile = ((_FILE*)str)->get_handle();
int textMode = ((_FILE*)str)->_flag & _FILE_TEXT;

// Text-mode translation is always ANSI!
if (textMode) // text mode -> translate LF -> CRLF
{
const char *src = (const char*)buffer;
size_t startpos = 0, i = 0;
for (i = 0; i < size*count; i++)
{
if (src[i] != '\n')
continue;
if (i > 0 && src[i-1] == '\r') // don't translate CRLF
continue;

if (i > startpos)
{
WriteFile(hFile, &src[startpos], i-startpos, &bw2, 0);
bw += bw2;
}

const char *crlf = "\r\n";
WriteFile(hFile, crlf, 2, &bw2, 0);
bw++; // one '\n' written

startpos = i+1;
}

if (i > startpos)
{
WriteFile(hFile, &src[startpos], i-startpos, &bw2, 0);
bw += bw2;
}
}
else
WriteFile(hFile, buffer, (DWORD)(size*count), &bw, 0);
return bw/size;
}

char *fgets(char *str, int n, FILE *s)
{
if (feof(s))
return 0;

int i;
for (i = 0; i < n-1; i++)
{
if (!fread(&str[i], 1, sizeof(char), s))
break;
if (str[i] == '\r')
{
i--;
continue;
}
if (str[i] == '\n')
{
i++;
break;
}
}

str[i] = 0;
return str;
}

wchar_t *fgetws(wchar_t *str, int n, FILE *s)
{
// Text-mode fgetws converts MBCS->Unicode
if (((_FILE*)str)->_flag & _FILE_TEXT)
{
char *bfr = (char*)malloc(n);
fgets(bfr, n, s);
MultiByteToWideChar(CP_ACP, 0, bfr, -1, str, n);
free(bfr);
return str;
}

// Binary fgetws reads as Unicode

if (feof(s))
return 0;

int i;
for (i = 0; i < n-1; i++)
{
if (!fread(&str[i], 1, sizeof(wchar_t), s))
break;
if (str[i] == L'\r')
{
i--;
continue; // does i++
}
if (str[i] == L'\n')
{
i++;
break;
}
}

str[i] = 0;
return str;
}


int fgetc(FILE *s)
{
if (s == 0 || feof(s))
return EOF;

char c;
fread(&c, 1, sizeof(char), s);

return (int)c;
}

wint_t fgetwc(FILE *s)
{
if (s == 0 || feof(s))
return (wint_t)EOF;

// text-mode fgetwc reads and converts MBCS
if (((_FILE*)s)->_flag & _FILE_TEXT)
{
char ch = (char)fgetc(s);
wint_t wch;
MultiByteToWideChar(CP_ACP, 0, &ch, 1, (LPWSTR)&wch, 1);
return wch;
}

// binary fgetwc reads unicode

wint_t c;
fread(&c, 1, sizeof(wint_t), s);

return c;
}

END_EXTERN_C



//libct.h

// libct.h

#pragma once

#include <tchar.h>

#ifdef __cplusplus
#define EXTERN_C extern "C"
#define BEGIN_EXTERN_C extern "C" {
#define END_EXTERN_C }
#else
#define EXTERN_C
#define BEGIN_EXTERN_C
#define END_EXTERN_C
#endif



BEGIN_EXTERN_C

extern TCHAR *_argv[];
int _init_args();
void _term_args();

typedef void (__cdecl *_PVFV)();
extern _PVFV __xc_a[], __xc_z[];    /* C++ initializers */

void _initterm(_PVFV *pfbegin, _PVFV *pfend);
void _init_atexit();
void _doexit();

END_EXTERN_C

void _init_file();

 
Title: Re: Moving TCLib Into Mainstream
Post by: James C. Fuller on April 03, 2016, 09:01:10 PM
Fred,
  The MMF on code project was MFC.
I did find this one on the PbForum
http://forum.powerbasic.com/forum/user-to-user-discussions/source-code/25065-alternate-line-input
Advantage is the ability to parse HUGE files one line at a time.
Converting to c++ might be a pain.

Re weather: I live 50 miles north of Albany,NY and the sun is a shining but only 32F with a high wind gust of 22.1 mph at 1:12pm
James

Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 03, 2016, 11:34:01 PM
Quote
The MMF on code project was MFC.

What does 'MMF' mean?


Never mind!  "MMF = Memory Mapped File" :-[

Another quick thought.  My fprintf code can be reinterpreted, I believe, to use a FILE* as the first parameter, just like its supposed to be.  Then inside the fprintf it needs to be cast back to a HANDLE.  Also, a cast would be necessary at the point of call to cast the HANDLE returned by CreateFile to a FILE*. 
Title: Re: Moving TCLib Into Mainstream
Post by: James C. Fuller on April 04, 2016, 12:50:51 AM
Fred,
  I'm fine with fprintf as it is.

James
Title: Re: Moving TCLib Into Mainstream
Post by: James C. Fuller on April 04, 2016, 06:53:46 PM
Fred,
  I was able to convert the Pb code here:
http://forum.powerbasic.com/forum/user-to-user-discussions/source-code/25065-alternate-line-input

but I don't like it as it skips blank lines.

Ballgame Rained/Snowed out so I may get more done today.

James
Title: Re: Moving TCLib Into Mainstream
Post by: James C. Fuller on April 04, 2016, 08:13:08 PM
The more I think about it is it so horrible for it to skip blank lines.
I know when I am parsing files using a line input method I need to check for blank lines before I operate on the line.
With this method you know you won't be getting any blank lines.

Fred, 
What do you think?

James

Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 04, 2016, 08:36:34 PM
I'd tend to think you would want it to behave like the PowerBASIC Line Input # statement.  If you were reading a text file with Line Input # and outputting the results to the console, blank lines would be "" I assume, but printing them would add a CrLf I further assume.  So wouldn't you want that behavior as an end result? 
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 04, 2016, 08:40:21 PM
Just as an aside, that situation with CRT gets() has given me a fair amount of grief over the years in that strings end up with the \n at the end instead of its being removed. 
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 04, 2016, 08:48:24 PM
Also, TCLib seems to work OK with ODBC.  In 64 bit one can't connect to 32 bit Office/Access, but I tested with SQL Server and made the connection no sweat.  I'll test in a bit reading/writing data, but don't see that that will make any difference.

Also, can't seem to get the fprintf/fopen situation out of my head even though I said what I had was good enough.  Just now I tested an fopen / _wfopen, and I changed the fprintf's 1st parameter from HANDLE, which its not supposed to be, to a FILE*, which it is supposed to be, and it works perfectly except for one tiny weenie glitch.  WriteFile wants a "\r\n" for a Crlf whereas fprintf is satisfied with just \n.  Not sure what I want to do about it, if anything.  And for that matter, I could leave what I have alone, if it puts you out a lot in your use of what I provided.
Title: Re: Moving TCLib Into Mainstream
Post by: James C. Fuller on April 04, 2016, 10:13:31 PM
Fred,
Ok I got it.
Could be a lot cleaner.
Remember it was written with bc9Basic so there is a bit of not needed fluff that I didn't remove.
It probably should/could be wrapped in a class?

James

 
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 05, 2016, 05:27:24 AM
Nice Work Jim.  Just looked at it and don't understand it yet but I'll work on it tomorrow.  Actually, I never worked with memory mapped files.  I know you have been mentioning this line input issue in C++ for some time, so I know you've given it a lot of thought.  As I believe I've mentioned already, for text files I've always used CRT fgets, but of course that won't work with TCLib until fgets gets implemented through Win32!  Is there some reason you used memory mapped files to tackle the thing?  Since I don't know anything about them but do know and use the console and file Win32 functions a fair amount, I'm thinking if I were to tackle the issue as you have I'd have likely started there possibly with ReadFile() or something.  Just asking is all. 

I've always bounced around a lot between the CRT functions and Win32 functions for files.  Text files I've always found easiest to deal with using CRT.  Binary and Random Access files go either way but I've a leaning to just use Win32 for those. 

Was just looking at the docs for ReadFile(), and I didn't see anything there regarding any capacity to just read a 'line' up to a CR, LF or CrLf.  So I'm assuming one would have to read the whole file into memory and parse out the lines.  Never tried it.  Seems to me possible though.  With my String::Replace() I could replace each CrLf with a single delimiter such as space, tab, or comma or whatever.  Then use String::Parse() to break it into lines.  Just theorizing here, brainstorming, so to speak.  Don't know if it would work, but I've a sinking feeling it'll prey on my mind 'till I try it.  Tomorrow's another day!   
Title: Re: Moving TCLib Into Mainstream
Post by: James C. Fuller on April 05, 2016, 11:45:43 AM
Fred,
  Normally I'm an C++ STL guy using std
I used to be a minimalist in my younger days but have no real reason anymore.
You, and TCLib somehow awakened the tiny in me :)

As you mentioned ReadFile has no provisions for line input . I do have code that will read and write a buffer that I will post shortly.
I had vaguely remembered something about using MMF for line input and found Don's code along with some variations on Code project.
The best thing with MMF is you can process HUGE 4gb files. This is just asci text files but the text is converted into Unicode fstring's.
I am in the process of writing a class wrapper for it. Damn (good) classes are hard to write. I'm a coder not a designer and have a hard time sometimes with the big picture.

Also this is complete turn around for me ; finding and using Win32 functions instead of  the CRT to do a job. I'm usually doing exactly the opposite.

Having fun though!

James
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 05, 2016, 02:30:12 PM
Just occurred to me this morning when I woke up how one would write a Line Input / fgets() with ReadFile().  You would set up a 1024 or 2048 buffer or something, then set up ReadFile() to read one byte at a time, and you would of course write it to the buffer, but you would test for CrLf or end of line or whatever.  Several years back I used ReadFile() that way I think for reading GPS data from a COM port for our data collectors.
Title: Re: Moving TCLib Into Mainstream
Post by: James C. Fuller on April 05, 2016, 05:17:28 PM
Fred,
  First draft of an TCLib MMF input class.
Similar to you I have my own way of attacking c++, especially classes.
I found classes with VB5 and really liked the set, get properties.
When I started learning C++ I really missed them. Yes you can build getters and setters,
but it's not the same. I ran across a c++ template implementation several years ago that worked pretty good
but had quite a bit of overhead. I then discovered and or built (can't remember which) a set of macro's.
I have a similar set for PowerBASIC.
Not quite the same as the good old get/set of VB and PB but close.
The c++ forum regulars  whined loudly but .....

I rarely place methods in the class structure itself.
It looks fine in bc9Basic but... their placement in the c++ source is not usually right after the declarations.
I always preface private variables with m_ which makes it easy to do the props

These are my prop macros

#define PropGet(p,t) const t& p() const {return m_##p;}
#define PropSet(p,t) void p(const t& param_##p){m_##p=param_##p;}
#define PropGetSet(p,t) PropGet(p,t)PropSet(p,t)

When you access you do so without the m_

In the cAliInput class I have
    PropGet(iErr, int);
    PropGet(sErr, fstring);

You can see in main() how their accessed
        fsErr = cai->sErr();
        fsErr.Print( true);

We only have ProGet's here but to set you would use
  fsErr = "ERROR #72"
  cai->sErr(fsErr)

This is Bc9Basic translated c++ so it does have a bit of fluff.
My tcl implementation is not complete in the translator but not too bad.

James



Title: Re: Moving TCLib Into Mainstream [Vectors]
Post by: James C. Fuller on April 07, 2016, 12:50:22 PM
Fred,
  Maybe a candidate for addition to the TCLib?
It was written by frankie, one of the PellesC forum administrators. I'm not sure if/where on documentation.
I did not look at too it closely.

James
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 11, 2016, 03:59:55 AM
Ahhh yes!  The taste of ultimate and absolute success on this incredible three month journey into finally beating the C++ bloatware issue! 

Over the course of the past week or so I've tested a number of apps of ever increasing size and complexity with TCLib, and everything is succeeding.  ODBC simply works!  Have added fairly good file support with regard to text files which I'll post shortly.  And James Fuller has some nice code using memory mapped files which he has provided and which works with TCLib either directly through C++ or through BCX.   And just now I managed to crack the BIGGIE!  By that I mean the thing I felt that was likely beyond reach, and that is my ActiveX Grid Control. 

I guess it may be four or five years since I originally developed that in PowerBASIC and posted my work here.  With that work I was able to create a pretty fair grid control including all the self-registration code in about 49 k with PowerBASIC.  With UPX that compacted down to 22 k.  I've since incorporated that grid into all my mission critical work here where I live. 

I guess about three years ago, becomming interested in 64 bit development with C++, I re-coded the grid in that language but was not able to get the 32 bit or 64 bit binaries down to anything less than around 87 k I think.  Compacted it was comming in originally at something like 43 k, but then I added a few lines of high DPI aware code to it and blew through some compiler allocation limit and it ended up around 50 k.

Just now after about three days of struggle with TCLib trying to add dll support to it and get the grid to compile/link I finally succeeded and it works!  Its coming in 28 k and compacts with UPX down to 18,432 bytes!  So in 64 bit I'm actually seeing about 4 k smaller than my PowerBASIC 22 k dll.  Here are the lines of code in the *.cpp files...


Server.cpp     260 lines
WinCode.cpp   1365 lines
Grid.cpp      1128 lines
Registry.cpp   210 lines
Strings.cpp    165 lines


That comes to 3,128 lines.  Quite a few other files involved, i.e., *.def, *.idl, *.rc, *.h, etc., but the above files contain the meat of it.  The command line compiling I do with a text file I execute at the command prompt like so...


cl @Grid.txt


And that file looks like this...


Server.cpp
Grid.cpp
WinCode.cpp
Registry.cpp
GStrings.cpp
FHGrid.def

/O1 /Os /GS- /GR- /FeFHGrid.dll /LD
/link /DLL /nodefaultlib:libcmt.lib

TCLib.lib
FHGridRes.obj
Kernel32.lib
comctl32.lib
User32.lib
Gdi32.lib
UUID.lib
Advapi32.lib
Ole32.lib
OleAut32.lib


So with all that a 28k / 18 k binary seems really sweet to me.  I'm really glad I started out on the project of eliminating the C Runtime.  It worked out beyond my wildest hopes!  I'll get to posting the details and modifications as soon as I can for anyone who may wish to experiment with this.  I had to do quite a bit of ripping and tearing at things!



Title: Re: Moving TCLib Into Mainstream
Post by: James C. Fuller on April 12, 2016, 02:02:44 PM
Fred,
  congrats!!
Looking forward to your latest.

James
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 12, 2016, 09:54:37 PM
Been struggling with the file support.  I see you have been too, and had asked Patrice what he does.  Yesterday I was working with my grid to make sure registration and unregistration was working correctly.  I really got scarred testing that.  I'm always leery of testing registry code.  Once I blew out an entire registry on an XP machine.  Amazingly enough it actually started.  It was completely useless though.  Anyway, that seems to be working fine.  Then I got embroiled again in the whole FILE* thing with my new fprintf/fgets code I haven't posted yet.  I thought I had it all working but now its giving me the fits.  The way I had it if UNICODE was defined, it was writing wide character strings to the file, i.e., creating UNICODE UCS-2 files.  And my fgets() was designed around the idea that all files were ansi, and the wide character version of fgets(), i.e., fgetws() would read an ansi file and convert/return a wide character string using the MultiByteToWideChar() function.  So I had a disjunction there.  If set up that way I wouldn't be able to read the files I just wrote, as my fgetws is looking to read an ansi file and convert that to wide.  So I've got to decide what to do and straighten out the mess.  And tomorrow my wife gets a knee replacement so my time will be a bit disjointed for awhile.

But the grid thing!  Like wow, it works!  Can't believe I managed it.  Don't know how I did it but somehow I did.  There were a couple nasty surprises that I though would finally do me in, but luck was with me I guess. 
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 12, 2016, 11:23:46 PM
I got my snafu worked out.  Lost a good hour over something stupid.  But it taught me a lesson which I immediately incorporated into the code.  For years and years and years I've been writing functions to determine the length in lines of text files using either CRT or WinApi.  So what happens is you read every byte to the end of the file and count lines.  Then the file pointer is at EOF.  Should the function call SetFilePointer() to the beginning of the file again before returning, or let the calling code do it?  Usually I let the calling code do it under the assumption that maybe the calling code wants the read pointer somewhere else other than at the beginning of the file.  Well, there you have my lost hour and a half!  I forgot to call my version or rewind() which calls WinApi SetFilePointer()!  So I put a call to rewind(), which I implemented in a function called Lof(File*) in fgets.cpp.

In the zip are all the files I modified (I think) from my last post here.  Try Demo25.cpp after rebuilding TCLib with my additions.  I also included Tlib.lib (latest version) if you want to use that.  Updated make file too.  Check out particularly my fprintf.cpp, fopen.cpp, crt_dll.cpp (allows creation of small dlls).  Changes had to be made too to stdio.h, string.h, stdlib.h.  Also had to add memcmp.cpp.  If you use == in QueryInterface it calls memcmp under the hood to check out IIDs. 

Demo25 is fun.  It reads the Demo25.cpp file using fgets and displays it in a GUI scrolling window.  I've attached it.  Let me know if what I've done seems reasonable with files.  I'm thinking it is, but not 100% sure.  Oh!  Your strchr() should be in there too, so if you make the lib be sure to have that present.
Title: Re: Moving TCLib Into Mainstream
Post by: James C. Fuller on April 13, 2016, 12:35:51 AM
Good work Fred.
I'll  take a look first thing in the AM.
Yankees take precedent tonight :)

James
Title: Re: Moving TCLib Into Mainstream
Post by: James C. Fuller on April 13, 2016, 01:26:49 PM
Fred,
  I have just taken a very quick look.
This is what I am using for lof.
It could also be used as a file Exist function.
I almost always check if a file exists before trying to open it.
Is there a line input routine somewhere?
More later.
James


DWORD lof (const _TCHAR *FileName)
{
    WIN32_FIND_DATA W32FD;
    HANDLE hFile;
    int FSize;
    if(_tcslen(FileName) == 0) return 0;
    hFile = FindFirstFile(FileName, &W32FD);
    if(hFile != INVALID_HANDLE_VALUE)
    {
        FSize = W32FD.nFileSizeLow;
        FindClose(hFile);
        return FSize;
    }
    return 0;
}
Title: Re: Moving TCLib Into Mainstream
Post by: James C. Fuller on April 14, 2016, 02:37:51 PM
Fred,
  I'm sorry I don't have more time to explore this now, but PLEASE continue your investigations. I do enjoy reading about your work.
I am still a bit perplexed on your "c" file io direction as there appears to be Win32 answers lurking in the dark reaches of the api :)

James
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 16, 2016, 06:52:50 PM
I see I'm going to have to change the name of my Lof(FILE*) function Jim.  You misinterpreted what it does, but its not your fault.  I was afraid of that.  It doesn't return the number of bytes in the file like BASIC's LOF does, but applies only to text files and returns the number of lines in the file.  So I'm going back through my stuff and changing it to LineCount(FILE*).  Hopefully that will cause less confusion.

And what I've done with files only applies to text files.  My main intent in implementing it was to provide debugging log file support.  I've almost always used WinApi routines for random/binary file access.  I need Line Input though too.  Some of my work apps need that.

But the only way I know of to provide an implementation of BASIC's Line Input in C, other than what you've showed me recently with memory mapped files, and that was new to me, is through fgets.  Here are a few examples of it.  Assume a text file like so created with Notepad and named TestData.txt...

Quote
Here Is Line #1
And Here Is Line #2
But Lets Not Forget Line #3
And Finally, Here Is Line #4

Here's Test1.cpp which uses my fgets...


// cl Test1.cpp /O1 /Os /GS- /link TCLib.lib kernel32.lib
#include <windows.h>
#include "string.h"
#include "stdio.h"
#include "tchar.h"

int main()
{
TCHAR szBuffer[128];
TCHAR pCh=NULL;
FILE* fp=NULL;
size_t iLof=0;

fp=_tfopen(_T("TestData.txt"),_T("r"));
if(fp)
{
    _tprintf(_T("File Opened uccessfully!\n"));
    iLof=LineCount(fp);
    _tprintf(_T("iLof   = %u\n\n"),iLof);
    for(size_t i=1; i<=iLof; i++)
    {
        _fgetts(szBuffer,128,fp);   
        _tprintf(_T("%s"),szBuffer);
    }
    fclose(fp);   
}
getchar();

return 0;
}

/*
File Opened uccessfully!
iLof   = 4

Here Is Line #1
And Here Is Line #2
But Lets Not Forget Line #3
And Finally, Here Is Line #4
*/


The above call to my new LineCount() (formerly Lof())  will return 4 for four lines of text whether or not a CrLf terminates the file or not.  However, it counts Line Feeds in the file to determine the count of lines.  Its as simple as that.

The reason I like to get the line count of a text file before I read it is that I personally tend to prefer coding idioms such as above using a for loop to read through a specific and predetermined number of lines than using a do or while loop such as this to just exit upon hitting EOF...

Test2.cpp

// cl Test2.cpp /O1 /Os /GS- /link TCLib.lib kernel32.lib
#include <windows.h>
#include "stdio.h"
#include "string.h"
#include "tchar.h"

int main()
{
TCHAR szBuffer[128];
TCHAR* pCh=NULL;
FILE* fp=NULL;

fp=_tfopen(_T("TestData.txt"),_T("r"));
if(fp)
{
    _tprintf(_T("File Opened uccessfully!\n\n"));
    do
    {
      pCh=_fgetts(szBuffer,128,fp);
      if(!pCh)
         break;   
      _tprintf(_T("%s"),szBuffer);
    } while(true);
    fclose(fp);   
}
getchar();

return 0;
}


/*
File Opened uccessfully!
iLof   = 4

Here Is Line #1
And Here Is Line #2
But Lets Not Forget Line #3
And Finally, Here Is Line #4


The output is the same however.  Note in the above I used the return value from fgets() to terminate the loop...


pCh=_fgetts(szBuffer,128,fp);
if(!pCh)
   break;   


Another characteristic of fgets() is that the middle or 2nd parameter can be used to specify how many characters you want returned from the line of text being read.  Here is Test3.cpp where I specify I only want 12 characters from each line, and I added strlen calls to the output to display what the length of what was returned was...


// cl Test3.cpp /O1 /Os /GS- /link TCLib.lib kernel32.lib
#include <windows.h>
#include "stdio.h"
#include "string.h"
#include "tchar.h"

int main()
{
TCHAR szBuffer[128];
FILE* fp=NULL;
TCHAR pCh=NULL;
size_t iLof=0;

fp=_tfopen(_T("TestData.txt"),_T("r"));
if(fp)
{
    _tprintf(_T("File Opened uccessfully!\n"));
    iLof=LineCount(fp);
    _tprintf(_T("iLof   = %u\n\n"),iLof);
    for(size_t i=1; i<=iLof; i++)
    {
        _fgetts(szBuffer,12,fp);   
        _tprintf(_T("%u\t%s"),_tcslen(szBuffer),szBuffer);
    }
    fclose(fp);   
}
getchar();

return 0;
}


C:\Code\VStudio\VC++9\LibCTiny\fgets>test3
File Opened uccessfully!
iLof   = 4

12      Here Is Lin
12      And Here Is
12      But Lets No
12      And Finally


I believe if you test the CRT's fgets() you'll get about the same results.

I've never used the FindFile(), FindFirst(), etc., family of functions.  Too hard for me.  I like something easier.  I always rather test the return values of file opening functions, and the number of bytes or records contained within them to determine if something is amiss.

The only other comment I can make about the whole fopen(), fgets(), and fprintf() situation as I now have it is that my implementation of fprintf() requires the "CrLf" sequence, i.e., "\r\n" to terminate each line which is different from the CRT versions which just require "\n".  I think I'll fix that someday, but I'm letting it as is for now.  Its not that big a deal to me.  I'm amazed it works at all, actually. 
Title: Final Post
Post by: Frederick J. Harris on April 16, 2016, 06:54:43 PM
     Lets start with dlls.  Here is the crt_dll.cpp file to be added to TCLib.lib (after compiling to crt_dll.obj, of course)...


// cl crt_dll.cpp /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN
#include <windows.h>
#pragma comment(linker, "/defaultlib:kernel32.lib")
extern BOOL WINAPI DllMain(HINSTANCE hDllHandle, DWORD dwReason, LPVOID lpreserved);

extern "C" BOOL WINAPI _DllMainCRTStartup(HINSTANCE  hDllHandle, DWORD dwReason, LPVOID  lpreserved)
{
BOOL retcode = DllMain(hDllHandle, dwReason, lpreserved);
return retcode;
}


Here is about the simplest possible demo with dllServer.cpp and dllHost.cpp...

dllServer.cpp

// dllServer.cpp
// cl dllServer.cpp /O1 /Os /GS- /LD TCLib.lib kernel32.lib
#include <windows.h>
extern "C" __declspec(dllexport) int __cdecl AddOne(int iNumber);

int __cdecl AddOne(int iNumber)
{
return ++iNumber;
}


dllHost.cpp

// cl dllHost.cpp /O1 /Os /GS- dllServer.lib TCLib.lib kernel32.lib
#include <windows.h>
#include "stdio.h"
extern "C" __declspec(dllimport) int __cdecl AddOne(int iNumber);

int main()
{
printf("AddOne(5) = %d\n",AddOne(5));
getchar();

return 0;
}


And here would be the command line compilation session with the above...


C:\Code\VStudio\Grids\x64>cl dllServer.cpp /O1 /Os /GS- /LD TCLib.lib kernel32.lib
Microsoft (R) C/C++ Optimizing Compiler Version 15.00.21022.08 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

dllServer.cpp
Microsoft (R) Incremental Linker Version 9.00.21022.08
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:dllServer.dll
/dll
/implib:dllServer.lib
dllServer.obj
TCLib.lib
kernel32.lib
   Creating library dllServer.lib and object dllServer.exp

C:\Code\VStudio\Grids\x64>cl dllHost.cpp /O1 /Os /GS- dllServer.lib TCLib.lib kernel32.lib
Microsoft (R) C/C++ Optimizing Compiler Version 15.00.21022.08 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

dllHost.cpp
Microsoft (R) Incremental Linker Version 9.00.21022.08
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:dllHost.exe
dllHost.obj
dllServer.lib
TCLib.lib
kernel32.lib

C:\Code\VStudio\Grids\x64>dllHost.exe
AddOne(5) = 6


     So we've got dlls covered.  Lets move on to the file output situation, which is a bit more involved.  I made changes to my previous fprintf.cpp file, and it now looks like this...


//=============================================================
//   Developed As An Addition To Matt Pietrek's LibCTiny.lib
//                By Fred Harris, March 2016
//
//  cl fprintf.cpp /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN   
//=============================================================
// cl sprintf.cpp /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdarg.h>
#include "stdio.h"
#define EOF (-1)
#pragma comment(linker, "/defaultlib:user32.lib")

int __cdecl fprintf(FILE* fp, const char* format, ...)
{
char szBuff[1024];
DWORD cbWritten;
va_list argptr;
int retValue;

va_start(argptr, format);
retValue = wvsprintfA(szBuff, format, argptr);
va_end(argptr);
WriteFile((HANDLE)fp, szBuff, retValue, &cbWritten, 0);

return retValue;
}

int __cdecl fwprintf(FILE* fp, const wchar_t* pFmtStr, ...)
{
int retValue,iBytes;
wchar_t szBuff[512];
char szABuff[512];
DWORD cbWritten;
va_list argptr;
 
va_start(argptr, pFmtStr);
retValue = wvsprintfW(szBuff, pFmtStr, argptr);
va_end(argptr);
iBytes = WideCharToMultiByte(CP_ACP,0,szBuff,(int)wcslen(szBuff),szABuff,512,NULL,NULL);
WriteFile((HANDLE)fp, szABuff, iBytes, &cbWritten, 0);

return retValue;
}


/*
Writes UNICODE

int __cdecl fwprintf(FILE* fp, const wchar_t* format, ...)
{
wchar_t szBuff[1024];
DWORD cbWritten;
va_list argptr;
int retValue;

va_start(argptr, format);
retValue = wvsprintfW(szBuff, format, argptr);
va_end(argptr);
WriteFile((HANDLE)fp, szBuff, retValue*2, &cbWritten, 0);

return retValue;
}
*/

/*
iBytes=WideCharToMultiByte(CP_ACP,0,szBuffW,iChars,szBuffA,1024,NULL,NULL);

int WideCharToMultiByte
(
  UINT CodePage,             // code page
  DWORD dwFlags,             // performance and mapping flags
  LPCWSTR lpWideCharStr,     // address of wide-character string
  int cchWideChar,           // number of characters in string
  LPSTR lpMultiByteStr,      // address of buffer for new string
  int cchMultiByte,          // size of buffer
  LPCSTR lpDefaultChar,      // address of default for unmappable characters
  LPBOOL lpUsedDefaultChar   // address of flag set when default char. used
);
*/


And that's exactly how its supposed to look, actually - at least in terms of the prototypes.  You can see I have an alternate version of fwprintf() above in a comment block.  That one writes UNICODE two byte characters.  From what I gather, that's not a real popular file encoding.  So in the version not commented out above the wide character strings passed in are converted to ascii with WideCharToMultiByte() and written to the file that way.  Of course you have to open files first before you can fprintf() to them, so here is fopen.cpp...


//=============================================================
//   Developed As An Addition To Matt Pietrek's LibCTiny.lib
//                By Fred Harris, April 2016
//
//   cl fopen.cpp /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN   
//=============================================================
#include <windows.h>
#include "stdio.h"


FILE* fopen(const char* pszFile, const char* pszMode)
{
DWORD dwDesiredAccess;
HANDLE hFile=NULL;

if(*pszMode=='w')
    dwDesiredAccess=GENERIC_WRITE;
if(*pszMode=='r')
    dwDesiredAccess=GENERIC_READ;
hFile=CreateFileA(pszFile,dwDesiredAccess,0,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
if(hFile==INVALID_HANDLE_VALUE)
    return NULL;

return (FILE*)hFile;
}


FILE* _wfopen(const wchar_t* pszFile, const wchar_t* pszMode)
{
DWORD dwDesiredAccess;
HANDLE hFile=NULL;

if(*pszMode==L'w')
    dwDesiredAccess=GENERIC_WRITE;
if(*pszMode==L'r')
    dwDesiredAccess=GENERIC_READ;
hFile=CreateFileW(pszFile,dwDesiredAccess,0,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
if(hFile==INVALID_HANDLE_VALUE)
    return NULL;

return (FILE*)hFile;
}


int fclose(FILE* fp)
{
return !CloseHandle((HANDLE)fp);
}


     I don't fully understand the inner workings of this or why it is so, but a FILE* and a HANDLE returned by CreateFile() seem to be about the same thing.  So fopen uses Win32 Api CreateFile() to open the file or create it, and casts the return to a FILE* (if INVALID_HANDLE_VALUE isn't returned by CreateFile()).  It simple works with no untoward consequences that I've noted so far, so I don't know just how much more one can say about it!  If someone dislikes what I've done I'd be happy to hear about it and perhaps learn something.

     I might interject at this point that in doing Win32 coding in Windows one has two complete sets of functions available for dealing with files.  There are the C Runtime Functions prototyped in stdio.h, and then there are the Win32 functions.  And of course in Windows Microsoft no doubt used their native Win32 functions to implement the C Runtime Functions.  Personally, for dealing with text streams/files I've tended to use the CRT functions.  For binary/random file access I've tended to use the Win32 functions.  Perhaps that's just me, but that's what I've found easiest to do.  I mean, isn't this....


fp=fopen("Output.txt","w");


...easier than this...



hFile=CreateFileW("Output.txt",GENERIC_WRITE,0,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);


But suit yourself.  You'll note in my fopen() I'm only supporting the "w" and "r" modes at this time.  A lot of other limitations too.  But its enough for me at this time.  I might add that implementing the append mode likely isn't too hard.  I'm guessing open the file for writing and move the file pointer to the end of the file with SetFilePointer().  Haven't tried it yet, but that's my best guess.   

And if you are dealing with text files containing lines of text terminated by CrLf pairs, you'll want something like PowerBASIC's Line Input statement.  That would be fgets()...


//=====================================================================================
//               Developed As An Addition To Matt Pietrek's LibCTiny.lib
//                            By Fred Harris, April 2016
//
//                   cl fgets.cpp /GS- /c /W3 /DWIN32_LEAN_AND_MEAN
//=====================================================================================
#include <windows.h>
#include "stdlib.h"
#include "stdio.h"

char* __cdecl fgets(char* pBuffer, int iSize, FILE* fp)   // The only way I know of to implement fgets()
{                                                         // using the WinApi is to use ReadFile() with
bool blnAddCharToBuffer=true;                            // the 3rd parameter...
DWORD lpBytesRead=0;                                     //
int iCtr=0;                                              // DWORD nNumberOfBytesToRead
char c=0;                                                //
                                                          // set to 1 so as to indicate a read of 1 byte.
if(fp!=INVALID_HANDLE_VALUE)                             // The 4th parameter...
{                                                        //
    pBuffer[0]=0;                                         // LPDWORD lpNumberOfBytesRead
    do                                                    //
    {                                                     // ...will equal zero if the end of the file is
       ReadFile((HANDLE)fp,&c,1,&lpBytesRead,NULL);       // reached.  In that case (see left) a LF is
       if(lpBytesRead==0)   // hit eof                    // placed in the buffer pBuffer, and a NULL byte
       {                                                  // stored after that.  So the basic plan of
          if(iCtr)                                        // operation is to keep reading through the file
          {                                               // byte by byte testing just what is being read
             pBuffer[iCtr]=10;                            // in char c and of course checking for the end
             pBuffer[iCtr+1]=0;                           // of the file as indicated by an lpBytesRead
          }                                               // equal zero.  If an ordinary character is being
          if(pBuffer[0])                                  // read it gets stored in pBuffer at iCtr.  A
             return pBuffer;                              // CR (Carriage Return - 0x0D) indicates the end
          else                                            // of the text line being read, and a LF (Line
             return NULL;                                 // Feed - 0x0A) is written to that position (that
       }                                                  // is what CRT fgets() does).  That is followed by
       if(iCtr==iSize-1)  // user doesn't want anymore    // a NULL byte.  Where this all gets tricky is
       {                                                  // if the user specifies an iSize parameter in
          blnAddCharToBuffer=false;                       // the fgets() function call that is less than the
          pBuffer[iCtr+0] = 10;                           // length of the line as determined by the CR
          pBuffer[iCtr+1] = 0;                            // symbol.  By the way, if Notepad is used to
       }                                                  // create the text file, each line will be
       if(c==13)   // hit end of line                     // delimited by CrLf pairs, but fgets just term-
       {                                                  // inates returned strings with LF chars. 
          if(blnAddCharToBuffer)                          // Anyway, the code above left tests iCtr against
          {  // user wants whlole line                    // iSize, and chars have to be prevented from
             pBuffer[iCtr]=10;                            // being written to the pBuffer when iSize is
             iCtr++;                                      // approached.  That's what the blnAddCharsToBuffer
             pBuffer[iCtr]=0;                             // bool is for.  Because you see, the do loop must
             blnAddCharToBuffer=false;                    // continue reading characters from the FILE*
          }                                               // buffer even after iSize is reached, because if
       }                                                  // it doesn't, the next call to gets() for the next
       if(blnAddCharToBuffer)                             // line will start at the last FILE* position.  I
          pBuffer[iCtr]=c;                                // realize this is tricky to understand.  Think
       if(c==10)   // return                              // about it!  After reading a 0x0A (LF - 10 dec),
          break;                                          // the do loop is exited with a break, and the
       iCtr++;                                            // function returns pBuffer.  Note that the function
                                                          // returns NULL if end of file is hit.
    }while(true);
}

return pBuffer;
}


wchar_t* __cdecl fgetws(wchar_t* pBuffer, int iSize, FILE* fp)  // This code assumes the file being read
{                                                               // is an ansi file, so it calls
char* pAsciBuffer=NULL;                                        // MultiByteToWideChar() so as to return
char* pRet=NULL;                                               // the desired wide character string.

printf("Called fgetws()\n");
memset(pBuffer,0,iSize*2);
pAsciBuffer=(char*)malloc(iSize);
if(!pAsciBuffer)
    return NULL;
memset(pAsciBuffer,0,iSize);
pRet=fgets(pAsciBuffer,iSize,fp);
printf("%s",pAsciBuffer);
if(pRet)
    MultiByteToWideChar(CP_ACP,0,pAsciBuffer,(int)strlen(pAsciBuffer),pBuffer,iSize);
free(pAsciBuffer);
if(pRet)
    return pBuffer;
else
    return NULL;
}


void __cdecl rewind(FILE* fp)
{
SetFilePointer((HANDLE)fp,0,NULL,FILE_BEGIN);
}


size_t __cdecl LineCount(FILE* fp)
{
DWORD lpNumberOfBytesRead=0;
size_t iReadSomething=0;
size_t iCtr=0;
char c=0;

if(fp!=INVALID_HANDLE_VALUE)
{
    do
    {
       ReadFile((HANDLE)fp,&c,1,&lpNumberOfBytesRead,NULL);
       if(lpNumberOfBytesRead)
       {
          if(c==10)
          {  iReadSomething=0;
             iCtr++;
          }
          else
             iReadSomething=1;
       }
       else
          break;
    }while(true);
}
rewind(fp);

return iCtr+iReadSomething;
}


     Included above is a function of my own which isn't in the CRT.  That would be LineCount().  That's extremely useful for dealing with text files.  If you know how many lines are in the file you can iterate through with a for loop instead of having to use a do or while loop which must constantly test for EOF.

My LineCount() raises a question though.  If you look at the implementation you'll see it must run through the file counting either CRs or LFs.  At least, that's how I did it.  So what you end up with is the file pointer sitting right on EOF when the function returns.  So if your intent is to use the number of lines returned by that function to set up a for loop to read the data, the file pointer is going to have to be returned to the beginning of the file which is where it was initially when the file was opened.  So the CRT rewind() function does that, and I've implemented that with WinApi SetFilePointer().  My original intent was to have the user call rewind() after obtaining the LineCount(), but that backfired on me the other day and I forgot to do it.  What I did instead was lose about an hour trying to figure out why I couldn't read any data from the file using the code I'd just written and which I thought was working.  That was aggravating in the extreme, so I decided to put the rewind() call at the end of LineCount() and so relieve the user from having to call it (and possibly forgetting like I did) , so that when it returned, the file could be read from the start again with no more hassle. 

     So that's how I have it now.  But it occurred to me there might be a more elegant solution.  I didn't implement it, but here it is.  When LineCount() is called the 1st thing the function could do would be to obtain the current file pointer position and store it in a variable.  Then read the file to count CRs, LFs, or whatever.   Then right before returning, reinstate the file pointer to the exact position it was at when the user called the function.  In most cases that will likely be the beginning of the file, but other possibilities are possible.  If you look into it though there isn't any GetFilePointer() function.  But it can be determined with SetFilePointere().  Here's how (from MSDN)...



/*
Retrieving a File Pointer

The SetFilePointer function can be used to query the current file pointer position, by specifying a move method
of FILE_CURRENT and a distance of zero.

HANDLE hFile;
DWORD dwCurrentFilePosition;

dwCurrentFilePosition = SetFilePointer
(
    hFile, // must have GENERIC_READ and/or GENERIC_WRITE
    0,     // do not move pointer
    NULL,  // hFile is not large enough to need this pointer
    FILE_CURRENT
);  // provides offset from current position

The following macro can be used to obtain the current file pointer position for files smaller than 2 gigabytes in size:

#define GetFilePointer(hFile) SetFilePointer(hFile, 0, NULL, FILE_CURRENT)
*/


     So if you want a little 'extra credit', there's a project for you!
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 16, 2016, 06:57:01 PM
     Now I'll turn to my grid.  Getting that to compile/link/run with TCLib was really my ultimate dream for all this.  At the start I would have figured my chances for success were slim at best.  When I started this work in January 2016 I wasn't even sure it was possible to get a C++ Class working.  So that was the 1st hurdle which I had to overcome.  And of course I proved that was possible with my all important String Class.  After success with that though I was hit with all kinds of nasty and unanticipated problems any one of which could have brought an end to all this.  There was the terrible _fltused thing, then the VC++ 19 /Zc:sizedDealloc- bug, among others.  Finally I got through all them, and it was time to try the COM based Grid Control.  But first I had to add support for Dlls, and that went relatively easily.  Next I went through all 4000 lines of the grid code looking for CRT calls that I might not have implemented.  I found about a dozen items that were relatively easily fixed.  Then the moment of truth!  Try to link the grid to TCLib.lib without the C Runtime!  Here was my first attempt....


C:\Code\Grid>cl Server.cpp Grid.cpp WinCode.cpp Registry.cpp GStrings.cpp TCLib.lib FHGridRes.obj Kernel32.lib comctl32.lib User32.lib Gdi32.lib  UUID.lib Advapi32.lib Ole32.lib OleAut32.lib FHGrid.def /O1 /Os /GS- /FeFHGrid.dll /LD
Microsoft (R) C/C++ Optimizing Compiler Version 15.00.21022.08 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

Server.cpp
Grid.cpp
WinCode.cpp
Registry.cpp
GStrings.cpp
Generating Code...
Microsoft (R) Incremental Linker Version 9.00.21022.08
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:FHGrid.dll
/dll
/implib:FHGrid.lib
/DLL
/nodefaultlib:libcmt.lib
TCLib.lib
FHGridRes.obj
Kernel32.lib
comctl32.lib
User32.lib
Gdi32.lib
UUID.lib
Advapi32.lib
Ole32.lib
OleAut32.lib
/entry:_DllMainCRTStartup
/def:FHGrid.def
Server.obj
Grid.obj
WinCode.obj
Registry.obj
GStrings.obj
   Creating library FHGrid.lib and object FHGrid.exp
Server.obj : error LNK2019: unresolved external symbol memcmp referenced in function IsEqualGUID
Grid.obj : error LNK2001: unresolved external symbol memcmp
Grid.obj : error LNK2001: unresolved external symbol "const type_info::`vftable'" (??_7type_info@@6B@)
Grid.obj : error LNK2001: unresolved external symbol _purecall
WinCode.obj : error LNK2019: unresolved external symbol memcpy referenced in function "long __cdecl fnGridProc_OnCreate(struct WindowsEventArgum
ents *)" (?fnGridProc_OnCreate@@YAJPEAUWindowsEventArguments@@@Z)
WinCode.obj : error LNK2001: unresolved external symbol _fltused
GStrings.obj : error LNK2019: unresolved external symbol _wtoi referenced in function "public: int __cdecl String::iVal(void)" (?iVal@String@@QE
AAHXZ)
FHGrid.dll : fatal error LNK1120: 6 unresolved externals


Well, could have been worse I guess!  Six linker errors.  Several of them above didn't worry me, but two of them in particular did.  I'll start with the easy ones.  I'd forgotten about IsEqualGUID which is a function called within QueryInterface() when an internal IID is being tested for equality with a passed in IID from the parameter list.  The IsEqualGUID function is implemented with memcmp(), which I hadn't implemented.  But its not too hard, so here is another *.cpp file for TCLib.lib...


//=====================================================================================
//               Developed As An Addition To Matt Pietrek's LibCTiny.lib
//                              By Fred Harris, April 2016
//
//                     cl memcmp.cpp /c /W3 /DWIN32_LEAN_AND_MEAN
//=====================================================================================

extern "C" int __cdecl memcmp(const void* buf1, const void* buf2, size_t iCount)
{
char* p1=(char*)buf1;
char* p2=(char*)buf2;
for(size_t i=0; i<iCount; i++)
{
     if(*p1<*p2)
        return -1;
     if(*p1>*p2)
        return 1;
     p1++, p2++;
}

return 0;
}


Next easy problem is that _fltused thingie that we've encountered before.  I simply forgot to add it in the code.  So that knocks out two errors.  Next fairly easy error is the _wtoi() thingie at the end.  That's actually a function I had implemented but had inadvertently commented it out for some reason or other which I don't recall.  I believe atoi was also missing, but wasn't called in my grid code, so that error didn't show up.  So here's a fixed atoi.cpp file...


//==============================================================================================
//               Developed As An Addition To Matt Pietrek's LibCTiny.lib
//                             By Fred Harris, March 2016
//
//        cl atol.cpp /D "_CRT_SECURE_NO_WARNINGS" /c /W3 /DWIN32_LEAN_AND_MEAN
//==============================================================================================
#include "stdlib.h"


long __cdecl atol(const char* pStr)
{
char c,cNeg=NULL;           // c holds the char; cNeg holds the '-' sign.
long lTotal=0;              // The running total.

while(*pStr==32 || *pStr==8)
    pStr++; 
if(*pStr=='-')
{
    cNeg='-';
    pStr++;
}
while(*pStr)
{
    c=*pStr++;
    lTotal=10*lTotal+(c-48); // Add this digit to the total.
}
if(cNeg=='-')               // If we have a negative sign, convert the value.
    lTotal=-lTotal;

return lTotal;
}


int __cdecl atoi (const char* pStr)
{
return ((int)atol(pStr));
}


long __cdecl _wtol(const wchar_t* pStr)
{
wchar_t c,cNeg=NULL;        // c holds the char; cNeg holds the '-' sign.
long lTotal=0;              // The running total.

while(*pStr==32 || *pStr==8)
    pStr++; 
if(*pStr==L'-')
{
    cNeg=L'-';
    pStr++;
}
while(*pStr)
{
    c=*pStr++;
    lTotal=10*lTotal+(c-48); // Add this digit to the total.
}
if(cNeg==L'-')              // If we have a negative sign, convert the value.
    lTotal=-lTotal;

return lTotal;
}


int __cdecl _wtoi (const wchar_t* pStr)
{
return ((int)_wtol(pStr));
}


All that above required some minor changes and additions to my previously posted headers, so I'll repost my most current versions...


// stdio.h
#ifndef stdio_h
#define stdio_h

struct FILE
{
char*  _ptr;
int    _cnt;
char*  _base;
int    _flag;
int    _file;
int    _charbuf;
int    _bufsiz;
char*  _tmpfname;
};

extern "C" char     __cdecl getchar();

extern "C" FILE*    __cdecl fopen(const char* pszFile, const char* pszMode);
extern "C" FILE*    __cdecl _wfopen(const wchar_t* pszFile, const wchar_t* pszMode);

extern "C" char*    __cdecl fgets(char* pBuffer, int iSize, FILE* fp);
extern "C" wchar_t* __cdecl fgetws(wchar_t* pBuffer, int iSize, FILE* fp);

extern "C" int      __cdecl printf(const char* format, ...);
extern "C" int      __cdecl wprintf(const wchar_t* format, ...);

extern "C" int      __cdecl fprintf(FILE* fp, const char* format, ...);
extern "C" int      __cdecl fwprintf(FILE* fp, const wchar_t* format, ...);

extern "C" int      __cdecl sprintf(char* buffer, const char* format, ...);
extern "C" int      __cdecl swprintf(wchar_t* buffer, const wchar_t* format, ...);

extern "C" size_t   __cdecl FltToCh(char* p, double x, size_t iFldWthTChrs, int iDecPlaces, char cDecimalSeperator, bool blnRightJustified);
extern "C" size_t   __cdecl FltToWch(wchar_t* p, double x, size_t iFldWthTChrs, int iDecPlaces, wchar_t cDecimalSeperator, bool blnRightJustified);

extern "C" size_t   __cdecl LineCount(FILE* fp);
extern "C" void     __cdecl rewind(FILE* fp);
extern "C" int      __cdecl fclose(FILE* fp);
#endif



// stdlib.h
#ifndef stdlib_h
#define stdlib_h
   #define NULL 0
   extern "C" void*   __cdecl malloc  (size_t          size);
   extern "C" void    __cdecl free    (void*           pMem);
   extern "C" long    __cdecl atol    (const char*     pStr);
   extern "C" int     __cdecl atoi    (const char*     pStr);
   extern "C" long    __cdecl _wtol   (const wchar_t*  pStr);
   extern "C" int     __cdecl _wtoi   (const wchar_t*  pStr);
   extern "C" _int64  __cdecl _atoi64 (const char*     pStr);
   extern "C" _int64  __cdecl _wtoi64 (const wchar_t*  pStr);
   extern "C" int     __cdecl abs     (int             n   );
   extern "C" long    __cdecl labs    (long            n   );
   extern "C" _int64  __cdecl _abs64  (__int64         n   );
#endif



// tchar.h
#ifndef tchar_h
   #define tchar_h
   #ifdef  _UNICODE
      typedef wchar_t     TCHAR;
      #define _T(x)       L## x
      #define _tmain      wmain
      #define _tWinMain   wWinMain
      #define _tfopen     _wfopen
      #define _fgetts     fgetws
      #define _tprintf    wprintf
      #define _ftprintf   fwprintf
      #define _stprintf   swprintf
      #define _tcslen     wcslen
      #define _tcscpy     wcscpy
      #define _tcscat     wcscat
      #define _tcsncpy    wcsncpy
      #define _tcscmp     wcscmp
      #define _tcsicmp    _wcsicmp
      #define _tcsncmp    wcsncmp
      #define _tcsnicmp   _wcsnicmp
      #define _tcsrev     _wcsrev
      #define FltToTch    FltToWch
      #define _ttol       _wtol
      #define _ttoi       _wtoi
      #define _ttoi64     _wtoi64
   #else
      typedef char        TCHAR;
      #define _T(x)       x
      #define _tmain      main
      #define _tWinMain   WinMain
      #define _tfopen     fopen
      #define _fgetts     fgets
      #define _tprintf    printf
      #define _ftprintf   fprintf
      #define _stprintf   sprintf
      #define _tcslen     strlen
      #define _tcscpy     strcpy
      #define _tcscat     strcat
      #define _tcsncpy    strncpy
      #define _tcscmp     strcmp
      #define _tcsicmp    _stricmp
      #define _tcsncmp    strncmp
      #define _tcsnicmp   _strnicmp
      #define _tcsrev     _strrev
      #define FltToTch    FltToCh
      #define _ttol       atol
      #define  _ttoi      atoi
      #define _ttoi64     _atoi64
   #endif
#endif


I believe that's all you'll need.  Now for the bad and scary ones, which are these two...


Grid.obj : error LNK2001: unresolved external symbol "const type_info::`vftable'" (??_7type_info@@6B@)
Grid.obj : error LNK2001: unresolved external symbol _purecall


     Of the two, the top one scared me the most because I didn't have the faintest idea what that was all about.  It looked like something related to COM in some ways, which would make sense cuz it was a COM app, so I spent a couple futile hours hunting around the SDK and CRT includes to see if I could spot anything like that.  Didn't succeed.  I pretty much felt like this could finally be the end of the line.  Been dodging bullets for months but one finally had my name on it.  I figured my last and only chance would be to see what an internet search might come up with.  So I pasted the whole line in my browser and goggled on it.  Lo and behold some poor soul doing the same work as I attempting to eliminate the C Runtime had posted the question on StackOverflow and he found the answer to his own question and posted it.  The issue is related to RTTI or run time type info which has some implementation in the CRT, in Windows, or somewhere or other, and there is a compiler switch of all things to turn it off!!!  Its /GR-.

     So I held my breath, said a prayer, and recompiled.  It worked!  It still didn't link, but the...

"const type_info::`vftable'" (??_7type_info@@6B@)

linker error was gone, leaving me now with just one error...


Grid.obj : error LNK2001: unresolved external symbol _purecall

And while that's fearsome enough, I wasn't as afraid of that one as the last one because in my studying up on all this I had read something about that in that Dr. Dobbs Journal article I had previously posted a link to....

Avoiding the Visual C++ Runtime Library
By Matthew Wilson, February 01, 2003

Here is what he had to say about it...


Pure Virtual Members

The Visual C++ compiler/linker instantiates the vtable entries for pure virtual methods with a CRT Library function, _purecall(), which has the following signature:

extern "C" int _cdecl _purecall(void);

This function is called if a pure virtual method() is called within the constructor or destructor of an abstract class as part of the construction/destruction of a concrete derived class. (This is a big no-no in C++. Nonetheless, it is refreshing to see that the implementers of Visual SourceSafe are as human as the rest of us, since it regularly crashes with pure-call termination.) The CRT Library implementation pops a dialog informing the user that a pure call has occurred, and then terminates the application.

To link projects containing one or more classes with pure virtual members without the CRT Library, you must supply your own definition of this function. If you are feeling confident (that it will never be called), then this can be as simple as:

extern "C" int _cdecl _purecall(void)
{
   return 0;
}

or you can be more proactive, as in the implementation I use in the Synesis Software libraries (Listing 12), which informs the user of the problem and then closes the process.

 
That's a bit bizarre I admit, but my understanding of it is that there is zero chance of any calls to my grid's abstract base classes' methods during construction/destruction of the grid.  Fact is, if you look at the constructors for either my IClassFactiory or FHGrid classes you'll see they don't make any calls on anything or do anything at all!  So lacking any really concrete ideas as to just what in blazes I'd put in such an implementation as above I decided to just stick that ridiculous and useless thing above in the dll code and either sink or swim.  Unbelievably enough it worked!!!!  I finally had an FHGrid.dll.  In great anticipation I looked in my development directory to see if the file was there and how large it was, and it was there and it was 28 k uncompacted.  My PowerBASIC dll was 49 k uncompacted.  So close to success but will it work?  I renamed my previous FHGrid.dll linked with the CRT and put this new one there where it was registered in the Registry.  I started up an app that uses the grid and there it was in all its glory or lack thereof!  Couldn't hardly believe it!  But before I get into the grid code, here is TCLib.mak in its final form...




PROJ       = TCLib

OBJS       = crt_con_a.obj crt_con_w.obj crt_win_a.obj crt_win_w.obj memset.obj newdel.obj printf.obj \
             sprintf.obj _strnicmp.obj strncpy.obj strncmp.obj _strrev.obj strcat.obj strcmp.obj \
             strcpy.obj strlen.obj getchar.obj alloc.obj alloc2.obj allocsup.obj FltToCh.obj atol.obj \
             _atoi64.obj abs.obj memcpy.obj strchr.obj fopen.obj fprintf.obj _stricmp.obj fgets.obj \
             crt_dll.obj memcmp.obj win32_crt_math.obj
       
CC         = CL
CC_OPTIONS = /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN

$(PROJ).LIB: $(OBJS)
    LIB /NODEFAULTLIB /machine:x64 /OUT:$(PROJ).lib $(OBJS)

.CPP.OBJ:
    $(CC) $(CC_OPTIONS) $<


     So if you are interested in an 18 k or 19 k 64 bit ActiveX Grid Control, here's how to build it.  Start with the FHGrid.idl file and the FHGrid.rc file...

continued...
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 16, 2016, 07:01:45 PM

// FHGrid.idl
import "unknwn.idl";

[object, uuid(30000000-0000-0000-0000-000000000001), oleautomation] interface IGrid : IUnknown
{
HRESULT CreateGrid
(
  [in] HWND     hParent,
  [in] BSTR     strSetup,
  [in] int      x,
  [in] int      y,
  [in] int      cx,
  [in] int      cy,
  [in] int      iRows,
  [in] int      iCols,
  [in] int      iRowHt,
  [in] COLORREF iSelectionBackColor,
  [in] COLORREF iSelectionTextColor,
  [in] BSTR     strFontName,
  [in] int      iFontSize,
  [in] int      iFontWeight
);
HRESULT SetRowCount([in] int iRowCount, [in] int blnForce);
HRESULT GetRowCount([out, retval] int* iRowCount);
HRESULT SetData([in] int iRow, [in] int iCol, [in] BSTR strData);
HRESULT GetData([in] int iRow, [in] int iCol, [out, retval] BSTR* strData);
HRESULT FlushData();
HRESULT Refresh();
HRESULT GetVisibleRows([out, retval] int* iVisibleRows);
HRESULT GethGrid([out, retval] HWND* hWnd);
HRESULT GethCell([in] int iRow, [in] int iCol, [out, retval] HWND* hCell);
HRESULT GethComboBox([in] int iCol, [out, retval] HWND* hCombo);
HRESULT SetCellAttributes([in] int iRow, [in] int iCol, [in] int iBackColor, [in] int iTextColor);
HRESULT DeleteRow([in] int iRow);
};

[object, uuid(30000000-0000-0000-0000-000000000002), oleautomation] interface IGridEvents : IUnknown
{
HRESULT Grid_OnKeyPress([in] int iKeyCode, [in] int iKeyData, [in] int iRow, [in] int iCol, [out] int* blnCancel);
HRESULT Grid_OnKeyDown([in] int KeyCode, [in] int iKeyData, [in] int iCellRow, [in] int iGridRow, [in] int iCol, [out] int* blnCancel);
HRESULT Grid_OnLButtonDown([in] int iCellRow, [in] int iGridRow, [in] int iCol);
HRESULT Grid_OnLButtonDblClk([in] int iCellRow, [in] int iGridRow, [in] int iCol);
HRESULT Grid_OnPaste([in] int iCellRow, [in] int iGridRow, [in] int iCol);
HRESULT Grid_OnRowSelection([in] int iRow, [in] int iAction);
HRESULT Grid_OnDelete([in] int iRow);
};

[uuid(30000000-0000-0000-0000-000000000003), helpstring("FHGrid TypeLib"), version(1.0)] library FHGridLibrary
{
importlib("stdole32.tlb");
interface IGrid;
interface IGridEvents;
[uuid(30000000-0000-0000-0000-000000000000)] coclass FHGrid
{
           interface IGrid;
  [source] interface IGridEvents;
}
};



1 TYPELIB "FHGrid.tlb"


Then you would use midl.exe, rc.exe, and cvtres.exe to build the typelib and create FHGridRes.obj, the latter of which being the object file containing the Type Library which gets compiled into the grid dll....


C:\Code\VStudio\Grids\x64>midl FHGrid.idl
Microsoft (R) 32b/64b MIDL Compiler Version 7.00.0500
Copyright (c) Microsoft Corporation 1991-2006. All rights reserved.
64 bit Processing .\FHGrid.idl
FHGrid.idl
64 bit Processing C:\Program Files\\Microsoft SDKs\Windows\v6.0A\include\unknwn.idl
unknwn.idl
64 bit Processing C:\Program Files\\Microsoft SDKs\Windows\v6.0A\include\wtypes.idl
wtypes.idl
64 bit Processing C:\Program Files\\Microsoft SDKs\Windows\v6.0A\include\basetsd.h
basetsd.h
64 bit Processing C:\Program Files\\Microsoft SDKs\Windows\v6.0A\include\guiddef.h
guiddef.h
64 bit Processing C:\Program Files\\Microsoft SDKs\Windows\v6.0A\include\oaidl.idl
oaidl.idl
64 bit Processing C:\Program Files\\Microsoft SDKs\Windows\v6.0A\include\objidl.idl
objidl.idl
64 bit Processing C:\Program Files\\Microsoft SDKs\Windows\v6.0A\include\oaidl.acf
oaidl.acf

C:\Code\VStudio\Grids\x64>rc /v /foFHGridRes.res FHGrid.rc
Microsoft (R) Windows (R) Resource Compiler Version 6.0.5724.0
Copyright (C) Microsoft Corporation.  All rights reserved.

Using codepage 1252 as default
Creating FHGridRes.res

FHGrid.rc.
Writing TYPELIB:1,      lang:0x409,     size 5056

C:\Code\VStudio\Grids\x64>cvtres /MACHINE:X64 /v FHGridRes.res
Microsoft (R) Windows Resource To Object Converter Version 9.00.21022.08
Copyright (C) Microsoft Corporation.  All rights reserved.

adding resource. type:TYPELIB, name:1, language:0x0409, flags:0x30, size:5056


Next comes the big compile and link operation to create the dll based COM control.  Since there are so many files and libraries being compiled and linked, and the command line string became so long, I added it to a 'response' file to be executed at the command prompt, which looks like this if the name of the file containing the compilation and linkage parameters is grid.txt...


cl @Grid.txt [ENTER]


And  by the way, you'll need the FHGrid.def file....


;//FHGrid.def
LIBRARY      "FHGrid"
EXPORTS
    DllGetClassObject     PRIVATE 
    DllCanUnloadNow       PRIVATE
    DllRegisterServer     PRIVATE
    DllUnregisterServer   PRIVATE


And here is Grid.txt...


Server.cpp
Grid.cpp
WinCode.cpp
Registry.cpp
GStrings.cpp
FHGrid.def

/O1 /Os /GS- /GR- /Zc:sizedDealloc- /FeFHGrid.dll /LD
/link /DLL /nodefaultlib:libcmt.lib

TCLib.lib
FHGridRes.obj
Kernel32.lib
comctl32.lib
User32.lib
Gdi32.lib
UUID.lib
Advapi32.lib
Ole32.lib
OleAut32.lib


Note I added the /Zc:sizedDealloc- switch to the compilation string if you want to use VC19 from Visual Studio 2015.  I'm personally going to stick with VC15 from my Visual Studio 2008.  I'll have a few words to say about that after I post all the code.  There'll be a lot.  Lets start with Server.cpp...


/*
   Server.cpp

   In Grid.txt...

   Server.cpp
   Grid.cpp
   WinCode.cpp
   Registry.cpp
   GStrings.cpp
   FHGrid.def

   /O1 /Os /GS- /GR- /FeFHGrid.dll /LD
   /link /DLL /nodefaultlib:libcmt.lib

   TCLib.lib
   FHGridRes.obj
   Kernel32.lib
   comctl32.lib
   User32.lib
   Gdi32.lib
   UUID.lib
   Advapi32.lib
   Ole32.lib
   OleAut32.lib
*/

#define       UNICODE
#define       _UNICODE
#define       X64_GRID           
//#define       MYDEBUG             
#include      <windows.h>         
#include      <initguid.h>       
#include      <ocidl.h>           
#include      "string.h"
#include      "stdio.h"
#include      "Grid.h"           
#include      "tchar.h"
#include      "WinCode.h"
#include      "Registry.h"       
#if defined   MYDEBUG
FILE*         fp=NULL;
#endif


//Globals
HINSTANCE     g_hModule           = NULL;
const TCHAR   g_szFriendlyName[]  = _T("Fred Harris Grid Control v1");
const TCHAR   g_szVerIndProgID[]  = _T("FHGrid.Grid");
const TCHAR   g_szProgID[]        = _T("FHGrid.Grid.1");
extern "C"    int _fltused        = 1;
long          g_lObjs             = 0;
long          g_lLocks            = 0;
size_t        g_CtrlId            = 1500;


STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppv)
{
GridClassFactory* pCF=NULL;
TCHAR szBuffer[512];
HRESULT hr;

#if defined MYDEBUG
if(fp==NULL)
{
    GetCurrentDirectory(512,szBuffer);
    _tcscat(szBuffer,_T("\\Output1.txt"));
    fp=_tfopen(szBuffer,_T("w"));
}
printf("  Entering DllGetClassObject()\r\n");
_ftprintf(fp,_T("Entering DllGetClassObject()\r\n"));
_ftprintf(fp,_T("  szBuffer = %s\r\n"),szBuffer);
#endif
if(rclsid!=CLSID_FHGrid)
    return E_FAIL;
pCF=new GridClassFactory;
if(pCF==0)
    return E_OUTOFMEMORY;
#if defined MYDEBUG
printf("    pCF = %p\n",pCF);
_ftprintf(fp,_T("  pCF = %p\r\n"),pCF);
#endif
hr=pCF->QueryInterface(riid,ppv);
if(FAILED(hr))
    delete pCF;
else
    Initialize();
#if defined MYDEBUG
printf("  Leaving DllGetClassObject()\n");
_ftprintf(fp,_T("Leaving DllGetClassObject()\r\n\r\n"));
#endif

return hr;
}


STDAPI DllCanUnloadNow()
{
#if defined MYDEBUG
printf("Entering DllCanUnloadNow()\n");
printf("  g_lObjs  = %d\n",g_lObjs);
printf("  g_lLocks = %d\n",g_lLocks);
_ftprintf(fp,_T("Entering DllCanUnloadNow()\r\n"));
_ftprintf(fp,_T("  g_lObjs  = %d\r\n"),g_lObjs);
_ftprintf(fp,_T("  g_lLocks = %d\r\n"),g_lLocks);
#endif
if(g_lObjs||g_lLocks)
{
    #if defined MYDEBUG
    printf("  Gonna Hang Around A Bit Yet Wheather You Like It Or Not!\n");
    printf("Leaving DllCanUnloadNow()\n");
    _ftprintf(fp,_T("  Gonna Hang Around A Bit Yet Wheather You Like It Or Not!\r\n"));
    _ftprintf(fp,_T("Leaving DllCanUnloadNow()\r\n"));
    #endif
    return S_FALSE;
}
else
{
    #if defined MYDEBUG
    printf("  I'm Outta Here!\n");
    printf("Leaving DllCanUnloadNow()\n");
    _ftprintf(fp,_T("  I'm Outta Here!\r\n"));
    _ftprintf(fp,_T("Leaving DllCanUnloadNow()\r\n"));
    fclose(fp);
    #endif
    return S_OK;
}
}


STDAPI DllRegisterServer()
{
ITypeLib* pTypeLib=NULL;
TCHAR szBuffer[512];
wchar_t szWide[512];
HRESULT hr=E_FAIL;

#if defined MYDEBUG
GetCurrentDirectory(512,szBuffer);
_tcscat(szBuffer,_T("\\Output.txt"));
fp=_tfopen(szBuffer,_T("w"));
if(!fp)
    return E_FAIL;
_ftprintf(fp,_T("Entering DLLRegisterServer\r\n"));
_ftprintf(fp,_T("  szBuffer = %s\r\n"),szBuffer);
#endif
if(GetModuleFileName(g_hModule,szBuffer,512)>=512)
{
    #if defined MYDEBUG
    _ftprintf(fp,_T("  GetModuleFileName() Failed!\r\n"));
    _ftprintf(fp,_T("Leaving DllRegisterServer()\r\n"));
    fclose(fp);
    #endif
    return E_FAIL;
}
#if defined UNICODE
     _tcscpy(szWide,szBuffer);
#else
     MultiByteToWideChar(CP_ACP,0,szBuffer,(int)strlen(szBuffer),szWide,511);
#endif
#if defined MYDEBUG
_ftprintf(fp,_T("  szBuffer = %s\r\n"),szBuffer);
_ftprintf(fp,_T("  szWide   = %s\r\n"),szWide);
#endif
hr=LoadTypeLibEx(szWide, REGKIND_REGISTER, &pTypeLib);
if(SUCCEEDED(hr))
{
    #if defined MYDEBUG
    _ftprintf(fp,_T("  LoadTypeLibEx() Succeeded!\r\n"));
    #endif
    pTypeLib->Release();
}
else
{
    #if defined MYDEBUG
    _ftprintf(fp,_T("  LoadTypeLib() Failed!\r\n"));
    _ftprintf(fp,_T("Leaving DllRegisterServer()\r\n"));
    fclose(fp);
    #endif
    return E_FAIL;
}
hr=RegisterServer(szBuffer,CLSID_FHGrid,g_szFriendlyName,g_szVerIndProgID,g_szProgID);
if(SUCCEEDED(hr))
{
    #if defined MYDEBUG
    _ftprintf(fp,_T("  RegisterServer() Succeeded!\r\n"));
    #endif
}
else
{
    #if defined MYDEBUG
    _ftprintf(fp,_T("  RegisterServer() Failed!\r\n"));
    #endif
}
#if defined MYDEBUG
_ftprintf(fp,_T("Leaving DllRegisterServer()\r\n"));
fclose(fp);
#endif

return hr;
}


STDAPI DllUnregisterServer()
{
TCHAR szBuffer[512];
HRESULT hr=E_FAIL;

#if defined MYDEBUG
GetCurrentDirectory(512,szBuffer);
_tcscat(szBuffer,_T("\\Output.txt"));
fp=_tfopen(szBuffer,_T("w"));
if(!fp)
    return E_FAIL;
_ftprintf(fp,_T("Entering DllUnregisterServer()\r\n"));
#endif
hr=UnregisterServer(CLSID_FHGrid,g_szVerIndProgID,g_szProgID);
#if defined MYDEBUG
_ftprintf(fp,_T("  hr = %d\r\n"),hr);
#endif
if(SUCCEEDED(hr))
{
    #if defined MYDEBUG
    _ftprintf(fp,_T("  UnregisterServer() Succeeded!\r\n"));
    #endif
    #if defined X64_GRID
    hr=UnRegisterTypeLib(LIBID_FHGrid,1,0,LANG_NEUTRAL,SYS_WIN64);
    #else
    hr=UnRegisterTypeLib(LIBID_FHGrid,1,0,LANG_NEUTRAL,SYS_WIN32);
    #endif
    if(SUCCEEDED(hr))
    {
       #if defined MYDEBUG
       _ftprintf(fp,_T("  UnRegisterTypeLib() Succeeded!\r\n"));
       #endif
    }
    else
    {
       #if defined MYDEBUG
       _ftprintf(fp,_T("  UnRegisterTypeLib() Failed!\r\n"));
       #endif
    }
}
#if defined MYDEBUG
_ftprintf(fp,_T("Leaving DllUnregisterServer()\r\n"));
fclose(fp);
#endif

return hr;
}


BOOL WINAPI DllMain(HINSTANCE hInst, DWORD reason, LPVOID reserved)
{
switch (reason)
{
  case DLL_PROCESS_ATTACH:
    g_hModule=hInst;
    break;
  case DLL_PROCESS_DETACH:
    break;
}

return TRUE;
}

Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 16, 2016, 07:04:27 PM
And here's Grid.cpp...


// cl Grid.cpp /O1 /Os /GS- /c
//Grid.cpp
#define   UNICODE
#define   _UNICODE
//#define   MYDEBUG
#include  <windows.h>
#include  <commctrl.h>
#include  <objbase.h>
#include  <olectl.h>
#include  "memory.h"
#include  "stdio.h"
#include  "tchar.h"
#include  "Grid.h"
#include  "WinCode.h"

extern WNDPROC   fnEditWndProc;
extern HINSTANCE g_hModule;
extern size_t    g_CtrlId;

#if defined MYDEBUG
extern FILE*     fp;
#endif


int _cdecl _purecall(void)   // Won't link against TCLib.lib without this useless function!
{
return 0;
}


FHGrid::FHGrid()   //C++ Constructor for class FHGrid
{
#if defined MYDEBUG
printf("  Entering FHGrid Constructor!\n");
#endif
this->pISink=NULL,  this->hWndCtrl=NULL,  m_lRef=0;
#if defined MYDEBUG
printf("    this->m_lRef = %d\n", this->m_lRef);
#endif
InterlockedIncrement(&g_lObjs);
#if defined MYDEBUG
printf("    g_lObjs       = %d\n", g_lObjs);
printf("    sizeof(*this) = %d\n",sizeof(*this));
#endif
#if defined MYDEBUG
printf("  Leaving FHGrid Constructor!\n");
#endif
}


HRESULT STDMETHODCALLTYPE FHGrid::QueryInterface(REFIID riid, void** ppv)
{
#if defined MYDEBUG
printf("    Entering FHGrid::QueryInterface()\n");
printf("      this = %d\n", this);
#endif
*ppv=0;
if(riid==IID_IUnknown)
    *ppv=(IGrid*)this;
else if(riid==IID_IFHGrid)
    *ppv=(IGrid*)this;
else if(riid==IID_IConnectionPointContainer)
    *ppv=(IConnectionPointContainer*)this;
else if(riid==IID_IConnectionPoint)
    *ppv=(IConnectionPoint*)this;
if(*ppv)
{
    AddRef();
    #if defined MYDEBUG
    printf("      *ppv = %u\n",*ppv);
    printf("    Leaving FHGrid::QueryInterface()\n");
    #endif
    return S_OK;
}
#if defined MYDEBUG
printf("    Leaving FHGrid::QueryInterface()\n");
#endif

return(E_NOINTERFACE);
}


ULONG STDMETHODCALLTYPE FHGrid::AddRef()
{
#if defined MYDEBUG
printf("    Entering FHGrid::AddRef()\n");
printf("      m_lRef = %d\n", m_lRef);
#endif
InterlockedIncrement(&m_lRef);
#if defined MYDEBUG
printf("      m_lRef = %d\n", m_lRef);
printf("    Leaving FHGrid::AddRef()\n");
#endif

return m_lRef;
}


ULONG STDMETHODCALLTYPE FHGrid::Release()
{
#if defined MYDEBUG
printf("    Entering FHGrid::Release()\n");
printf("      m_lRef = %d\n", m_lRef);
#endif
if(InterlockedDecrement(&m_lRef)==0)
{
    #if defined MYDEBUG
    printf("      m_lRef = %d\n", m_lRef);
    printf("      Will Now Delete this ...\n");
    printf("      this->pISink = %u\n\n",this->pISink);
    printf("      i\t\t&this->pISink[i]\t\tthis->pISink[i]\n");
    printf("      ============================================================\n");
    for(size_t i=0; i<MAX_CONNECTIONS; i++)
        printf("      %u\t\t%u\t\t\t\t%u\n",i,&this->pISink[i],this->pISink[i]);
    printf("    \nLeaving FHGrid::Release()\n");
    #endif
    DestroyWindow(this->hWndCtrl);
    CoTaskMemFree(this->pISink);
    delete this;
    return 0;
}
#if defined MYDEBUG
printf("      m_lRef = %d\n", m_lRef);
printf("    Leaving FHGrid::Release()\n");
#endif

return m_lRef;
}


HRESULT STDMETHODCALLTYPE FHGrid::CreateGrid(HWND hParent, BSTR strSetup, int x, int y, int cx, int cy, int iRows, int iCols, int iRowHt, COLORREF iSelBckClr, COLORREF iSelTxtClr, BSTR strFntNme, int iFntSize, int iFntWt)
{
GridData* pGridData=NULL;
DWORD dwStyle;
HWND hGrid=0;
GridData gd;

#ifndef UNICODE
return E_FAIL;
#endif
#if defined MYDEBUG
printf("Entering FHGrid::CreateGrid()\n");
_ftprintf(fp,_T("Entering FHGrid::CreateGrid()\r\n"));
BOOL blnDPIAware=FALSE;
blnDPIAware=IsProcessDPIAware();
_ftprintf(fp,_T("  blnDPIAware= %d\r\n"),blnDPIAware);
_ftprintf(fp,_T("  this       = %u\r\n"),this);
_ftprintf(fp,_T("  hParent    = %u\r\n"),hParent);
_ftprintf(fp,_T("  strSetup   = %s\r\n"),strSetup);
_ftprintf(fp,_T("  x          = %d\r\n"),x);
_ftprintf(fp,_T("  y          = %d\r\n"),y);
_ftprintf(fp,_T("  cx         = %d\r\n"),cx);
_ftprintf(fp,_T("  cy         = %d\r\n"),cy);
_ftprintf(fp,_T("  iRows      = %d\r\n"),iRows);
_ftprintf(fp,_T("  iCols      = %d\r\n"),iCols);
_ftprintf(fp,_T("  iRowHt     = %d\r\n"),iRowHt);
_ftprintf(fp,_T("  iSelBckClr = %d\r\n"),iSelBckClr);
_ftprintf(fp,_T("  iSelTxtClr = %d\r\n"),iSelTxtClr);
_ftprintf(fp,_T("  strFntNme  = %s\r\n"),strFntNme);
_ftprintf(fp,_T("  iFntSize   = %d\r\n"),iFntSize);
_ftprintf(fp,_T("  iFntWt     = %d\r\n"),iFntWt);
#endif
//memset(&gd,0,sizeof(GridData));
dwStyle                       = WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL;
gd.hParent                    = hParent;
gd.cx                         = cx;
gd.cy                         = cy;
gd.iRows                      = iRows;
gd.iCols                      = iCols;
gd.iRowHeight                 = iRowHt;
gd.iFontSize                  = iFntSize;
gd.iFontWeight                = iFntWt;
gd.iSelectionBackColor        = iSelBckClr;
gd.iSelectionTextColor        = iSelTxtClr;
gd.iCtrlId=g_CtrlId;
wcscpy(gd.szFontName,strFntNme);
#if defined MYDEBUG
_ftprintf(fp,_T("  gd.szFontName       = %s\r\n\r\n"),gd.szFontName);
_ftprintf(fp,_T("  g_CtrlId            = %d\r\n"),g_CtrlId);
#endif
hGrid=CreateWindowEx(WS_EX_OVERLAPPEDWINDOW,L"Grid",strSetup,dwStyle,x,y,cx,cy,hParent,(HMENU)g_CtrlId,g_hModule,&gd);
if(hGrid==0)
    return E_FAIL;
g_CtrlId++;
this->hWndCtrl=hGrid;
pGridData=(GridData*)GetWindowLongPtr(hGrid,0);
if(!pGridData)
    return E_FAIL;
pGridData->pComObj=this;
if(iSelBckClr==0)
    pGridData->iSelectionBackColor=RGB(0x41,0x69,0xE1);  //  RGB_ROYALBLUE;    // %RGB_ROYALBLUE = 0xE16941
if(iSelTxtClr==0)
    pGridData->iSelectionTextColor=RGB(0x80,0x00,0x00);  //  RGB_MAROON;       // %RGB_MAROON    = 0x000080
pGridData->pCreatedBrushes[0]=(HBRUSH)1;
pGridData->pCreatedBrushes[1]=CreateSolidBrush(iSelBckClr);
pGridData->pCreatedColors[0]=(COLORREF)1;
pGridData->pCreatedColors[1]=iSelBckClr;
SetFocus(hGrid);

#if defined MYDEBUG
_ftprintf(fp,_T("  hGrid                          = %u\r\n"),hGrid);
_ftprintf(fp,_T("  pGridData                      = %u\r\n"),pGridData);
_ftprintf(fp,_T("  pGridData->pComObj             = %u\r\n"),pGridData->pComObj);
_ftprintf(fp,_T("  this->hWndCtrl                 = %u\r\n"),this->hWndCtrl);
_ftprintf(fp,_T("  &gd                            = %u\r\n"),&gd);
_ftprintf(fp,_T("  pGridData->blnRowSelected      = %d\r\n"),pGridData->blnRowSelected);
_ftprintf(fp,_T("  pGridData->iSelectionBackColor = %0x\r\n"),pGridData->iSelectionBackColor);
_ftprintf(fp,_T("  pGridData->iSelectionTextColor = %0x\r\n"),pGridData->iSelectionTextColor);
_ftprintf(fp,_T("Leaving FHGrid::CreateGrid()\r\n\r\n"));
printf("  Leaving FHGrid::CreateGrid()\n");
#endif

return S_OK;
}


HRESULT STDMETHODCALLTYPE FHGrid::SetRowCount(int iRowCount, int blnForce)
{
GridData* pGridData=NULL;
int iSize=0,blnFree=0;
HANDLE hHeap=0;
SCROLLINFO si;
int i;

#ifdef MYDEBUG
_ftprintf(fp,_T("Entering FHGrid::SetRowCount()\r\n"));
_ftprintf(fp,_T("  i         blnFree\r\n"));
_ftprintf(fp,_T("  =================\r\n"));
#endif
pGridData=(GridData*)GetWindowLongPtr(this->hWndCtrl,0);
iSize=pGridData->iRows*pGridData->iCols;
hHeap=GetProcessHeap();
for(i=0; i<iSize; i++)
{
     blnFree=HeapFree(hHeap,0,pGridData->pGridMemory[i]);
     #ifdef MYDEBUG
     _ftprintf(fp,_T("  %d\t%d\r\n"),i,blnFree);
     #endif
}
blnFree=HeapFree(hHeap,0,pGridData->pGridMemory);
#ifdef MYDEBUG
_ftprintf(fp,_T("\n  HeapFree(hHeap,0,pGridData->pGridMemory) = %d\r\n"),blnFree);
#endif
blnFree=HeapFree(hHeap,0,pGridData->pTextColor);
#ifdef MYDEBUG
_ftprintf(fp,_T("  HeapFree(hHeap,0,pGridData->pTextColor) = %d\r\n"),blnFree);
#endif
blnFree=HeapFree(hHeap,0,pGridData->pBackColor);
#ifdef MYDEBUG
_ftprintf(fp,_T("  HeapFree(hHeap,0,pGridData->pBackColor) = %d\r\n"),blnFree);
#endif

//Create A New Memory Block
if(iRowCount < pGridData->iVisibleRows)
{
    #ifdef MYDEBUG
    _ftprintf(fp,_T("  Got In Where iRowCount < iVisibleRows"));
    #endif
    iRowCount=pGridData->iVisibleRows+1;
}
iSize = iRowCount * pGridData->iCols;
pGridData->pGridMemory=(TCHAR**)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,iSize*sizeof(void*));
if(!pGridData->pGridMemory)
    return E_FAIL;
pGridData->iRows=iRowCount;
//ZeroMemory(&si, sizeof(SCROLLINFO));
memset(&si,0,sizeof(SCROLLINFO));
si.cbSize=sizeof(SCROLLINFO);
si.fMask=SIF_RANGE | SIF_PAGE | SIF_POS;
si.nMin=1;
si.nMax=pGridData->iRows;
si.nPage=pGridData->iVisibleRows;
si.nPos=1;
SetScrollInfo(this->hWndCtrl,SB_VERT,&si,TRUE);
pGridData->pTextColor=(COLORREF*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,iSize*sizeof(void*));
#ifdef MYDEBUG
_ftprintf(fp,_T("  pGridData->pTextColor = %u\r\n"),pGridData->pTextColor);
#endif
if(!pGridData->pTextColor)
    return E_FAIL;
pGridData->pBackColor=(HBRUSH*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,iSize*sizeof(void*));
#ifdef MYDEBUG
_ftprintf(fp,_T("  pGridData->pBackColor = %u\r\n"),pGridData->pBackColor);
#endif
if(!pGridData->pBackColor)
    return E_FAIL;
#ifdef MYDEBUG
_ftprintf(fp,_T("Leaving FHGrid::SetRowCount()\r\n\r\n"));
#endif

return S_OK;
}


HRESULT STDMETHODCALLTYPE FHGrid::GetRowCount(int* iRowCount)
{
#ifdef MYDEBUG
_ftprintf(fp,_T("Entering FHGrid::GetRowCount()\r\n"));
#endif
GridData* pGridData=(GridData*)GetWindowLongPtr(this->hWndCtrl,0);
if(pGridData)
{
    *iRowCount=pGridData->iRows;
    #ifdef MYDEBUG
    _ftprintf(fp,_T("  *iRowCount = %d\r\n"),*iRowCount);
    _ftprintf(fp,_T("Leaving FHGrid::GetRowCount()\r\n"));
    #endif
    return S_OK;
}
#ifdef MYDEBUG
_ftprintf(fp,_T("  *iRowCount = %d\r\n"),*iRowCount);
_ftprintf(fp,_T("Leaving FHGrid::GetRowCount()\r\n"));
#endif

return E_FAIL;
}


HRESULT STDMETHODCALLTYPE FHGrid::SetData(int iRow, int iCol, BSTR strData)
{
GridData* pGridData=NULL;
HANDLE hHeap=NULL;
int iIndex;

#ifdef MYDEBUG
printf("Called FHGrid::SetData()\n");
_ftprintf(fp,_T("Entering FHGrid::SetData()\r\n"));
_ftprintf(fp,_T("  this         = %u\r\n"),this);
_ftprintf(fp,_T("  iRow         = %d\r\n"),iRow);
_ftprintf(fp,_T("  iCol         = %d\r\n"),iCol);
_ftprintf(fp,_T("  strData      = %s\r\n"),strData);
#endif
pGridData=(GridData*)GetWindowLongPtr(this->hWndCtrl,0);
if(pGridData)
{
    if(iRow && iRow<=pGridData->iRows)
    {
       if(iCol && iCol<=pGridData->iCols)
       {
          iIndex=dwIdx(iRow,iCol);
          hHeap=GetProcessHeap();
          #ifdef MYDEBUG
          _ftprintf(fp,_T("  iIndex       = %d\r\n"),iIndex);
          _ftprintf(fp,_T("  pGridData->pGridMemory[iIndex] = %u\r\n"),pGridData->pGridMemory[iIndex]);
          _ftprintf(fp,_T("  pGridData->pGridMemory[iIndex] = %s\r\n"),pGridData->pGridMemory[iIndex]);
          #endif
          if(pGridData->pGridMemory[iIndex]==NULL)
          {
             #ifdef MYDEBUG
             _ftprintf(fp,_T("  Got In Where Memory Is Null\r\n"));
             #endif
             pGridData->pGridMemory[iIndex]=(TCHAR*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,(SysStringLen(strData)*sizeof(TCHAR))+sizeof(TCHAR));
             if(pGridData->pGridMemory[iIndex])
                _tcscpy(pGridData->pGridMemory[iIndex],strData);
             else
                return S_FALSE;
          }
          else
          {
             if(wcscmp(pGridData->pGridMemory[iIndex],strData)==0)  // Both Strings Are The Same, So No Need To Replace.  Therefore, Just Return.
             {
                #ifdef MYDEBUG
                _ftprintf(fp,_T("  Got In Where Parameter Is Same As Memory, So Just Return.\r\n"));
                #endif
                return S_OK;
             }
             else
             {
                #ifdef MYDEBUG
                _ftprintf(fp,_T("  Got In Where We'll Allocate Memory And Copy Parameter To it.\r\n"));
                #endif
                HeapFree(hHeap,0,pGridData->pGridMemory[iIndex]);
                pGridData->pGridMemory[iIndex]=(TCHAR*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,(SysStringLen(strData)*sizeof(TCHAR))+sizeof(TCHAR));
                if(pGridData->pGridMemory[iIndex])
                   _tcscpy(pGridData->pGridMemory[iIndex],strData);
                else
                   return S_FALSE;
             }
          }
       }
       else
          return S_FALSE;
    }
    else
       return S_FALSE;
}
else
    return S_FALSE;
#ifdef MYDEBUG
_ftprintf(fp,_T("Leaving FHGrid::SetData()\r\n\r\n"));
#endif

return S_OK;
}


HRESULT STDMETHODCALLTYPE FHGrid::GetData(int iRow, int iCol, BSTR* strData)
{
#ifdef MYDEBUG
printf("Called FHGrid::GetData()\r\n");
#endif
GridData* pGridData=(GridData*)GetWindowLongPtr(this->hWndCtrl,0);
if(pGridData)
{
    if(iRow<=pGridData->iRows && iRow>0 && iCol<=pGridData->iCols && iCol>0)
    {
       int iIndex=dwIdx(iRow,iCol);
       SysReAllocString(strData,pGridData->pGridMemory[iIndex]);
       return S_OK;
    }
}

return E_FAIL;
}


HRESULT STDMETHODCALLTYPE FHGrid::FlushData()
{
GridData* pGridData=NULL;
HWND hGrid;

#ifdef MYDEBUG
printf("Called FHGrid::FlushData()\r\n");
_ftprintf(fp,_T("  Entering FHGrid::FlushData()\r\n"));
#endif
hGrid=this->hWndCtrl;
pGridData=(GridData*)GetWindowLongPtr(hGrid,0);
#ifdef MYDEBUG
_ftprintf(fp,_T("    hGrid     = %u\r\n"),hGrid);
_ftprintf(fp,_T("    pGridData = %u\r\n"),pGridData);
#endif
if(pGridData->hCtrlInCell)
{
    #ifdef MYDEBUG
    _ftprintf(fp,_T("    Got In Where pGridData->hCtrlInCell = TRUE!\r\n"));
    #endif
    int iLen=GetWindowTextLength(pGridData->hCtrlInCell);
    HANDLE hHeap=GetProcessHeap();
    TCHAR* pZStr=(TCHAR*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,(iLen+1)*sizeof(TCHAR));
    if(pZStr)
    {
       GetWindowText(pGridData->hCtrlInCell,pZStr,iLen+1);
       BSTR strData=SysAllocString(pZStr);
       this->SetData(pGridData->iEditedRow,pGridData->iEditedCol,strData);
       HeapFree(hHeap,0,pZStr);
       SysFreeString(strData);
       SetWindowLongPtr(pGridData->hCtrlInCell,GWLP_WNDPROC,(LONG_PTR)fnEditWndProc);
       SetParent(pGridData->hCtrlInCell,hGrid);
       SetWindowPos(pGridData->hCtrlInCell,HWND_BOTTOM,0,0,0,0,SWP_HIDEWINDOW);
       pGridData->hCtrlInCell=0;
       this->Refresh();
    }
    else
    {
       return E_FAIL;
    }
}
#ifdef MYDEBUG
_ftprintf(fp,_T("  Leaving FHGrid::FlushData()\r\n"));;
#endif


return S_OK;
}


HRESULT STDMETHODCALLTYPE FHGrid::Refresh()
{
int iRows,iCols,iCountCells,iReturn,iIdx;
GridData* pGridData=NULL;
SCROLLINFO si;

#ifdef MYDEBUG
printf("Called FHGrid::Refresh()\n");
_ftprintf(fp,_T("Entering FHGrid::Refresh()\r\n"));
#endif
pGridData=(GridData*)GetWindowLongPtr(this->hWndCtrl,0);
#ifdef MYDEBUG
_ftprintf(fp,_T("  pGridData    = %p\r\n"),pGridData);
#endif
if(pGridData)
{
    iRows       = pGridData->iVisibleRows;
    iCols       = pGridData->iCols;
    iCountCells =iRows*iCols;
    si.cbSize   = sizeof(SCROLLINFO);
    si.fMask    = SIF_POS;
    iReturn     = GetScrollInfo(this->hWndCtrl,SB_VERT,&si);
    #ifdef MYDEBUG
    _ftprintf(fp,_T("  iReturn                 = %d\r\n"),iReturn);
    _ftprintf(fp,_T("  pGridData->iVisibleRows = %d\r\n"),pGridData->iVisibleRows);
    _ftprintf(fp,_T("  pGridData->iCols        = %d\r\n"),pGridData->iCols);
    _ftprintf(fp,_T("  iCountCells             = %d\r\n"),iCountCells);
    _ftprintf(fp,_T("  si.nPos                 = %d\r\n\r\n"),si.nPos);
    _ftprintf(fp,_T("  i    pGridData->pCellHandles[i]  pGridData->pGridMemory[i]    pText   pGridData->pBackColor[iIdx]  pGridData->pTextColor[iIdx]\r\n"));
    _ftprintf(fp,_T("  ==============================================================================================================================\r\n"));
    #endif
    if(iReturn)
    {
       for(int i=0; i<iCountCells; i++)
       {
           iIdx=iCols*(si.nPos-1)+i;
           SetWindowLongPtr(pGridData->pCellHandles[i],0,(LONG_PTR)pGridData->pGridMemory[iIdx]);
           SetWindowLongPtr(pGridData->pCellHandles[i],2*sizeof(void*),(LONG_PTR)pGridData->pTextColor[iIdx]);
           SetWindowLongPtr(pGridData->pCellHandles[i],3*sizeof(void*),(LONG_PTR)pGridData->pBackColor[iIdx]);
           InvalidateRect(pGridData->pCellHandles[i],NULL, TRUE);
           #ifdef MYDEBUG
           TCHAR* pText=(TCHAR*)pGridData->pGridMemory[i];
           _ftprintf
           (
             fp,
             _T("  %d\t\t%u\t\t\t%u\t\t\t%s\t\t\t0x%x\t\t\t0x%x\r\n"),
             i,pGridData->pCellHandles[i],pGridData->pGridMemory[i],pText,pGridData->pBackColor[iIdx],pGridData->pTextColor[iIdx]
           );
           #endif
       }
    }
}
#ifdef MYDEBUG
_ftprintf(fp,_T("Leaving FHGrid::Refresh()\r\n"));
#endif

return S_OK;
}


HRESULT STDMETHODCALLTYPE FHGrid::GetVisibleRows(int* iVisibleRows)
{
GridData* pGridData=(GridData*)GetWindowLongPtr(this->hWndCtrl,0);
if(pGridData)
{
    *iVisibleRows=pGridData->iVisibleRows;
    return S_OK;
}

return E_FAIL;
}



HRESULT STDMETHODCALLTYPE FHGrid::GethGrid(HWND* hWnd)
{
GridData* pGridData=(GridData*)GetWindowLongPtr(this->hWndCtrl,0);
if(pGridData)
{
    *hWnd=pGridData->hGrid;
    return S_OK;
}

return E_FAIL;
}


HRESULT STDMETHODCALLTYPE FHGrid::GethCell(int iRow, int iCol, HWND* hCell)
{
GridData* pGridData=(GridData*)GetWindowLongPtr(this->hWndCtrl,0);
if(pGridData)
{
    if(iRow<=pGridData->iRows && iRow)
    {
       if(iCol<=pGridData->iCols && iCol)
       {
          int iIndex=dwIdx(iRow,iCol);
          *hCell=pGridData->pCellHandles[iIndex];
          return S_OK;
       }
    }
}

return E_FAIL;
}


HRESULT STDMETHODCALLTYPE FHGrid::GethComboBox(int iCol, HWND* hCombo)
{
#ifdef MYDEBUG
_ftprintf(fp,_T("Entering FHGrid::GethComboBox()\r\n"));
#endif
GridData* pGridData=(GridData*)GetWindowLongPtr(this->hWndCtrl,0);
if(pGridData->pCellCtrlTypes[iCol-1]==GRID_CELL_CTRL_COMBO)
    *hCombo=(HWND)pGridData->pCtrlHdls[iCol-1];
else
    return E_FAIL;
#ifdef MYDEBUG
_ftprintf(fp,_T("  *hCombo = %p\r\n"),*hCombo);
_ftprintf(fp,_T("Leaving FHGrid::GethComboBox()\r\n\r\n"));
#endif

return S_OK;
}


HRESULT STDMETHODCALLTYPE FHGrid::SetCellAttributes(int iRow, int iCol, COLORREF iBackColor, COLORREF iTextColor)
{
int iIdx=0,i=0,blnFound=0;
GridData* pGridData=NULL;

#if defined MYDEBUG
printf("Called FHGrid::SetCellAttributes()\n");
_ftprintf(fp,_T("  Entering FHGrid::SetCellAttributes()\r\n"));
_ftprintf(fp,_T("    iRow       = %u\r\n"),iRow);
_ftprintf(fp,_T("    iCol       = %u\r\n"),iCol);
_ftprintf(fp,_T("    iBackColor = 0x%x\r\n"),iBackColor);
_ftprintf(fp,_T("    iTextColor = 0x%x\r\n"),iTextColor);
#endif
pGridData=(GridData*)GetWindowLongPtr(this->hWndCtrl,0);
if(iRow>pGridData->iRows || iCol>pGridData->iCols)
    return E_FAIL;
if(iRow<1 || iCol<1)
    return E_FAIL;
iIdx=dwIdx(iRow,iCol);
pGridData->pTextColor[iIdx] = iTextColor;
#if defined MYDEBUG
_ftprintf(fp,_T("    iIdx       = %u\r\n"),iIdx);
#endif
#if defined MYDEBUG
_ftprintf(fp,_T("    pGridData->iCols               = %d\r\n"),pGridData->iCols);
_ftprintf(fp,_T("    iIdx                           = %u\r\n"),iIdx);
_ftprintf(fp,_T("    pGridData->pTextColor          = %d\r\n"),pGridData->pTextColor);
_ftprintf(fp,_T("    pGridData->pTextColor[iIdx]    = 0x%x\r\n"),pGridData->pTextColor[iIdx]);
_ftprintf(fp,_T("    pGridData->pCellHandles[iIdx]  = %u\r\n"),pGridData->pCellHandles[iIdx]);
_ftprintf(fp,_T("    pGridData->pCreatedColors[0]   = %u\r\n"),pGridData->pCreatedColors[0]);
_ftprintf(fp,_T("    pGridData->pCreatedBrushes[0]  = %u\r\n\r\n"),pGridData->pCreatedBrushes[0]);
_ftprintf(fp,_T("    i          pGridData->pCreatedColors[i]            iBackColor\r\n"));
_ftprintf(fp,_T("    =============================================================\r\n"));
#endif
//pGridMemory                         As Dword Ptr  'Will be storing ZStr Ptrs here.  We need a
//pTextColor                          As Dword Ptr  'Will be storing RGB values here, i.e., %Red, %Blue, etc
//pBackColor                          As Dword Ptr  'Will be storing HBRUSHs here.  May be zero for default brush.
//pCreatedColors                      As Dword Ptr  'Colors so far asked for by user per grid instance, e.g., %Red, %Yellow, %Blue, etc.
//pCreatedBrushes                     As Dword Ptr  'Will be storing created HBRUSHs here.  Accumulate them. Numbers such as &HA0556789
for(i=1; i<=pGridData->pCreatedColors[0]; i++)
{
     #if defined MYDEBUG
     _ftprintf(fp,_T("    %d\t\t0x%x\t\t0x%x\r\n"),i,pGridData->pCreatedColors[i],iBackColor);
     #endif
     if(pGridData->pCreatedColors[i]==iBackColor)
     {
        blnFound=TRUE;
        break;
     }
}
#if defined MYDEBUG
_ftprintf(fp,_T("    blnFound = %d\r\n"),blnFound);
_ftprintf(fp,_T("    i        = %d\r\n"),i);
#endif
if(blnFound)  // Color Was Already Created
{
    if(iRow && iCol)
    {
       pGridData->pBackColor[iIdx]=pGridData->pCreatedBrushes[i];
    }
    #if defined MYDEBUG
    _ftprintf(fp,_T("\r\n"));
    _ftprintf(fp,_T("    Got In Where blnFound = %True!\r\n"));
    _ftprintf(fp,_T("    pGridData->pCreatedBrushes[i] = 0x%x\r\n"),pGridData->pCreatedBrushes[i]);
    #endif
}
else          // Need To Create Brush And Store It
{
    #if defined MYDEBUG
    _ftprintf(fp,_T("\r\n"));
    _ftprintf(fp,_T("    Got In Where blnFound = False!\r\n"));
    _ftprintf(fp,_T("    pGridData->pCreatedBrushes[0] = %d   << Before\r\n"),pGridData->pCreatedBrushes[0]);
    #endif
    if((int)pGridData->pCreatedBrushes[0]<MAX_COLORS)  // Test to see if pGridData->pCreatedBrushes[0]
    {                                                  // is less than 15, i.e., %MAX_COLORS
       int iCreatedBrushes=(int)pGridData->pCreatedBrushes[0];
       iCreatedBrushes++;
       pGridData->pCreatedBrushes[0]=(HBRUSH)iCreatedBrushes;
       int iCreatedColors=(int)pGridData->pCreatedColors[0];
       iCreatedColors++;
       pGridData->pCreatedColors[0]=(COLORREF)iCreatedColors;
       #if defined MYDEBUG
       _ftprintf(fp,_T("    pGridData->pCreatedColors[0] = %d\r\n"),pGridData->pCreatedColors[0]);
       _ftprintf(fp,_T("    Will Be Able To Create Another Brush!\r\n"));
       #endif
    }
    else
    {
       #if defined MYDEBUG
       _ftprintf(fp,_T("    Can't Create Another Brush!\r\n"));
       _ftprintf(fp,_T("  Leaving IGrid_SetCellAttributes()\r\n"));
       #endif
       return E_FAIL;                                // We've already created 15 brushes (Or Whatever MAX_COLORS Is).
    }
    #if defined MYDEBUG
    _ftprintf(fp,_T("    pGridData->pCreatedBrushes[0] = %d   << After\r\n"),pGridData->pCreatedBrushes[0]);
    #endif
    pGridData->pCreatedBrushes[(int)pGridData->pCreatedBrushes[0]] = CreateSolidBrush(iBackColor);
    pGridData->pCreatedColors[pGridData->pCreatedColors[0]]   = iBackColor;
    #if defined MYDEBUG
    _ftprintf(fp,_T("    pGridData->pCreatedBrushes[(int)pGridData->pCreatedBrushes[0]] = 0x%x\r\n"),pGridData->pCreatedBrushes[(int)pGridData->pCreatedBrushes[0]]);
    #endif
    pGridData->pBackColor[iIdx] = pGridData->pCreatedBrushes[(int)pGridData->pCreatedBrushes[0]];
    #if defined MYDEBUG
    _ftprintf(fp,_T("    Have Just Assigned A Newly Created Brush To pBackColor[] At correct Location\r\n"));
    _ftprintf(fp,_T("    pGridData->pBackColor[iIdx] = 0x%x\r\n"),pGridData->pBackColor[iIdx]);
    #endif
}
#if defined MYDEBUG
_ftprintf(fp,_T("  Leaving FHGrid::SetCellAttributes()\r\n\r\n"));
#endif

return S_OK;
}


HRESULT STDMETHODCALLTYPE FHGrid::DeleteRow(int iRow)
{
#if defined MYDEBUG
_ftprintf(fp,_T("Entering FHGrid::DeleteRow()\r\n"));
#endif
GridData* pGridData=(GridData*)GetWindowLongPtr(this->hWndCtrl,0);
int iSize=(pGridData->iRows-1)*pGridData->iCols-1;
int iStart=dwIdx(iRow,1);
int iCols=pGridData->iCols;
for(int i=iStart; i<=iSize; i++)
{
     pGridData->pGridMemory[i] = pGridData->pGridMemory[i+iCols];
     pGridData->pTextColor[i]  = pGridData->pTextColor[i+iCols];
     pGridData->pBackColor[i]  = pGridData->pBackColor[i+iCols];
}
iStart=dwIdx(pGridData->iRows,1);
for(int i=iStart; i<iStart+iCols; i++)
{
     pGridData->pGridMemory[i] = 0;
     pGridData->pTextColor[i]  = 0;
     pGridData->pBackColor[i]  = 0;
}
for(int i=1; i<=iCols; i++)
     pGridData->pComObj->SetCellAttributes(iRow,i,pGridData->iSelectionBackColor,pGridData->iSelectionTextColor);
#if defined MYDEBUG
_ftprintf(fp,_T("Leaving FHGrid::DeleteRow()\r\n"));
#endif

return S_OK;
}


HRESULT FHGrid::EnumConnectionPoints(IEnumConnectionPoints** ppEnum)
{
#ifdef MYDEBUG
printf("Called FHGrid::EnumConnectionPoints()\r\n");
#endif
return E_NOTIMPL;
}


HRESULT FHGrid::FindConnectionPoint(REFIID riid, IConnectionPoint** ppCP)
{
#ifdef MYDEBUG
printf("Entering FHGrid::FindConnectionPoint()\n");
printf("  this  = %u\n",this);
printf("  *ppCP = %u\n",*ppCP);
#endif
HRESULT hr=this->QueryInterface(IID_IConnectionPoint,(void**)ppCP);
if(SUCCEEDED(hr))
{
    #ifdef MYDEBUG
    printf("  QueryInterface(IID_IConnectionPoint) Succeeded!\n");
    printf("  *ppCP = %u\n",*ppCP);
    printf("Leaving FHGrid::FindConnectionPoint()\n");
    #endif
    return hr;
}
else
{
    #ifdef MYDEBUG
    printf("  QueryInterface(IID_IConnectionPoint) Failed!\n");
    printf("Leaving FHGrid::FindConnectionPoint()\n");
    #endif
    return E_NOINTERFACE;
}
}


HRESULT FHGrid::GetConnectionInterface(IID* pIID)
{
return E_NOTIMPL;
}


HRESULT FHGrid::GetConnectionPointContainer(IConnectionPointContainer** ppCPC)
{
return E_NOTIMPL;
}


HRESULT FHGrid::Advise(IUnknown* pUnkSink, DWORD* pdwCookie)
{
int blnFoundOpenSlot=FALSE,i;

#ifdef MYDEBUG
printf("  Entering FHGrid::Advise()\n");
printf("    this     = %u\n",this);
printf("    pUnkSink = %u\n\n",pUnkSink);
printf("    i   &this->pISink[i]     this->pISink[i]\n");
printf("    ===========================================\n");
#endif
for(i=0; i<MAX_CONNECTIONS; i++)
{
     #ifdef MYDEBUG
     printf("    %d\t%u\t\t\t\t%d\n",i,&this->pISink[i],this->pISink[i]);
     #endif
     if(this->pISink[i]==0)
     {
        blnFoundOpenSlot=TRUE;
        break;
     }
}
if(blnFoundOpenSlot)
{
    HRESULT hr=pUnkSink->QueryInterface(IID_IFHGridEvents,(void**)&this->pISink[i]);
    if(SUCCEEDED(hr))
    {
       #ifdef MYDEBUG
       printf("    i               = %d\n",i);
       printf("    pUnkSink->QueryInterface() For Client Sink Succeeded!\n");
       printf("    this->pISink[i] = %u\n",this->pISink[i]);
       printf("  Leaving FHGrid::Advise()\n");
       #endif
       *pdwCookie=(DWORD)i;
       return S_OK;
    }
    else
    {
       #ifdef MYDEBUG
       printf("    pUnkSink->QueryInterface() For Client Sink Failed!\n");
       #endif
    }
}
else
{
    #ifdef MYDEBUG
    printf("    Couldn't Find An Open Slot To Store Connection!\n");
    #endif
}
#ifdef MYDEBUG
printf("  Leaving FHGrid::Advise()\n");
#endif

return E_FAIL;
}


HRESULT FHGrid::Unadvise(DWORD dwCookie)
{
#ifdef MYDEBUG
printf("Entering FHGrid::Unadvise()\n");
printf("  dwCookie = %u\n",dwCookie);
#endif
IUnknown* pIUnknown=this->pISink[dwCookie];
pIUnknown->Release();
this->pISink[dwCookie]=0;
#ifdef MYDEBUG
printf("  pIUnknown = %u\n",pIUnknown);
printf("Leaving FHGrid::Unadvise()\n");
#endif

return NOERROR;
}


HRESULT FHGrid::EnumConnections(IEnumConnections** ppEnum)
{
return E_NOTIMPL;
}


FHGrid::~FHGrid()  //C++ Destructor for class FHGrid
{
#if defined MYDEBUG
printf("  Entering FHGrid Destructor!\n");
printf("    g_lObjs = %d\n", g_lObjs);
#endif
InterlockedDecrement(&g_lObjs);
#if defined MYDEBUG
printf("    g_lObjs = %d\n", g_lObjs);
printf("  Leaving FHGrid Destructor!\n");
#endif
}

// Grid Class Factory
GridClassFactory::GridClassFactory()
{
#if defined MYDEBUG
printf("    Entering GridClassFactory Constructor!\n");
#endif
m_lRef=0;
#if defined MYDEBUG
printf("      this->m_lRef = %d\n", this->m_lRef);
#endif
#if defined MYDEBUG
printf("    Leaving GridClassFactory Constructor!\n");
#endif
}


GridClassFactory::~GridClassFactory()  //GridClassFactory Destructor
{
#if defined MYDEBUG
printf("  Entering GridClassFactory Destructor!\n");
printf("    this->m_lRef = %d\n", this->m_lRef);
printf("  Leaving GridClassFactory Destructor!\n");
#endif
}


HRESULT STDMETHODCALLTYPE GridClassFactory::QueryInterface(REFIID riid, void** ppv)
{
#if defined MYDEBUG
printf("    Entering GridClassFactory::QueryInterface()\n");
#endif
*ppv=0;
if(riid==IID_IUnknown || riid==IID_IClassFactory)
    *ppv=this;
#if defined MYDEBUG
printf("      *ppv = %u\n", *ppv);
#endif
if(*ppv)
{
    AddRef();
    #if defined MYDEBUG
    printf("    Leaving GridClassFactory::QueryInterface()\n");
    #endif
    return S_OK;
}

return E_NOINTERFACE;
}


ULONG STDMETHODCALLTYPE GridClassFactory::AddRef()
{
#if defined MYDEBUG
printf("      Entering GridClassFactory::AddRef()!\n");
printf("        this->m_lRef = %d\n", this->m_lRef);
#endif
InterlockedIncrement(&m_lRef);
#if defined MYDEBUG
printf("        this->m_lRef = %d\n", this->m_lRef);
printf("      Leaving GridClassFactory::AddRef()!\n");
#endif

return this->m_lRef;
}


ULONG STDMETHODCALLTYPE GridClassFactory::Release()
{
#if defined MYDEBUG
printf("  Entering GridClassFactory::Release()!\n");
printf("    this->m_lRef = %d\n", this->m_lRef);
#endif
InterlockedDecrement(&m_lRef);
if(this->m_lRef==0)
{
    #if defined MYDEBUG
    printf("    this->m_lRef = %d\n", this->m_lRef);
    #endif
    delete this;
    #if defined MYDEBUG
    printf("    GridClassFactory Has Been Destroyed!\n");
    printf("  Leaving GridClassFactory::Release()!\n");
    #endif
    return 0;
}
#if defined MYDEBUG
printf("    this->m_lRef = %d\n", this->m_lRef);
printf("  Leaving GridClassFactory::Release()!\n");
#endif

return m_lRef;
}


HRESULT STDMETHODCALLTYPE GridClassFactory::CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid, void** ppvObj)
{
FHGrid* pGrid=NULL;
HRESULT hr;

#if defined MYDEBUG
printf("  Entering GridClassFactory::CreateInstance()\n");
#endif
*ppvObj=0;
pGrid=new FHGrid;
#if defined MYDEBUG
printf("    pGrid          = %u\n",pGrid);
printf("    sizeof(*pGrid) = %d\n",sizeof(*pGrid));
#endif
if(pGrid==NULL)
    return E_OUTOFMEMORY;
hr=pGrid->QueryInterface(riid,ppvObj);
if(FAILED(hr))
    delete pGrid;
else
{
    pGrid->pISink=(IGridEvents**)CoTaskMemAlloc(MAX_CONNECTIONS*sizeof(void*));
    #if defined MYDEBUG
    printf("    pGrid->pISink = %u\n",pGrid->pISink);
    printf("    pGrid->pISink[0] = %u\n",pGrid->pISink[0]);
    printf("    pGrid->pISink[1] = %u\n",pGrid->pISink[1]);
    printf("    pGrid->pISink[2] = %u\n",pGrid->pISink[2]);
    printf("    pGrid->pISink[3] = %u\n",pGrid->pISink[3]);
    #endif
    if(pGrid->pISink==NULL)
       return E_OUTOFMEMORY;
    else
    {
       pGrid->pISink[0]=(IGridEvents*)0;
       pGrid->pISink[1]=(IGridEvents*)NULL;
       pGrid->pISink[2]=(IGridEvents*)NULL;
       pGrid->pISink[3]=(IGridEvents*)NULL;
       #if defined MYDEBUG
       printf("    pGrid->pISink = %u\n",pGrid->pISink);
       printf("    pGrid->pISink[0] = %u\n",pGrid->pISink[0]);
       printf("    pGrid->pISink[1] = %u\n",pGrid->pISink[1]);
       printf("    pGrid->pISink[2] = %u\n",pGrid->pISink[2]);
       printf("    pGrid->pISink[3] = %u\n",pGrid->pISink[3]);
       #endif
    }
}
#if defined MYDEBUG
printf("  Leaving GridClassFactory::CreateInstance()\n");
#endif

return hr;
}


HRESULT STDMETHODCALLTYPE GridClassFactory::LockServer(BOOL fLock)
{
if(fLock)
    InterlockedIncrement(&g_lLocks);
else
    InterlockedDecrement(&g_lLocks);

return S_OK;
}


void Initialize()
{
INITCOMMONCONTROLSEX uCC;
TCHAR szClassName[16];
short int iRet=0;
WNDCLASSEX wc;

#if defined MYDEBUG
printf("    Entering Initialize()\n");
#endif

uCC.dwSize = sizeof(uCC);             // Initialize Common Controls (need header control).
uCC.dwICC  = ICC_LISTVIEW_CLASSES;
InitCommonControlsEx(&uCC);

_tcscpy(szClassName,_T("Grid"));      // Register Grid Class
wc.lpszClassName  = szClassName;
wc.lpfnWndProc    = fnGridProc;
wc.cbSize         = sizeof(wc);
wc.style          = 0;
wc.cbClsExtra     = 0;
wc.cbWndExtra     = sizeof(void*);
wc.hInstance      = g_hModule;
wc.hIcon          = LoadIcon(NULL,IDI_APPLICATION);
wc.hCursor        = LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground  = (HBRUSH)GetStockObject(LTGRAY_BRUSH);
wc.lpszMenuName   = NULL;
wc.hIconSm        = NULL;
iRet              = RegisterClassEx(&wc);
#if defined MYDEBUG
printf("      RegisterClassEx(Grid) = %d\n",iRet);
#endif

_tcscpy(szClassName,_T("Pane"));      // Register Pane Class
wc.lpszClassName  = szClassName;
wc.lpfnWndProc    = fnPaneProc;
wc.cbSize         = sizeof(wc);
wc.style          = 0;
wc.cbClsExtra     = 0;
wc.cbWndExtra     = sizeof(void*);
wc.hInstance      = g_hModule;
wc.hIcon          = LoadIcon(NULL,IDI_APPLICATION);
wc.hCursor        = LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground  = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.lpszMenuName   = NULL;
wc.hIconSm        = NULL;
iRet              = RegisterClassEx(&wc);
#if defined MYDEBUG
printf("      RegisterClassEx(Pane) = %d\n",iRet);
#endif

_tcscpy(szClassName,_T("Base"));      // Register Pane Class
wc.lpszClassName  = szClassName;
wc.lpfnWndProc    = fnBaseProc;
wc.cbSize         = sizeof(wc);
wc.style          = 0;
wc.cbClsExtra     = 0;
wc.cbWndExtra     = 0;
wc.hInstance      = g_hModule;
wc.hIcon          = LoadIcon(NULL,IDI_APPLICATION);
wc.hCursor        = LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground  = (HBRUSH)GetStockObject(GRAY_BRUSH);
wc.lpszMenuName   = NULL;
wc.hIconSm        = NULL;
iRet              = RegisterClassEx(&wc);
#if defined MYDEBUG
printf("      RegisterClassEx(Pane) = %d\n",iRet);
#endif

_tcscpy(szClassName,_T("Cell"));      // Register Cell Class
wc.lpszClassName  = szClassName;
wc.lpfnWndProc    = fnCellProc;
wc.cbSize         = sizeof(wc);
wc.style          = 0;
wc.cbClsExtra     = 0;
wc.cbWndExtra     = 4*sizeof(void*);
wc.hInstance      = g_hModule;
wc.hIcon          = LoadIcon(NULL,IDI_APPLICATION);
wc.hCursor        = LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground  = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.lpszMenuName   = NULL;
wc.hIconSm        = NULL;
iRet              = RegisterClassEx(&wc);
#if defined MYDEBUG
printf("      RegisterClassEx(Cell) = %d\n",iRet);
printf("    Leaving Initialize()\n");
#endif
}
//End Grid.cpp

Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 16, 2016, 07:05:38 PM
Here's Grid.h


//Grid.h
#ifndef     GRID_H_INCLUDED
#define     GRID_H_INCLUDED
#include    "Interfaces.h"
#define     MAX_CONNECTIONS  4
#define     dwIdx(r,c) ((r-1)*pGridData->iCols+(c-1))
extern long g_lObjs;
extern long g_lLocks;

class FHGrid : public IGrid, public IConnectionPointContainer, public IConnectionPoint
{
public:
FHGrid();            //Constructor
virtual ~FHGrid();   //Destructor

//Iunknown Functions
virtual HRESULT STDMETHODCALLTYPE QueryInterface(const IID& iid, void** ppv);
virtual ULONG   STDMETHODCALLTYPE AddRef();
virtual ULONG   STDMETHODCALLTYPE Release();

//IGrid Interface Methods/Functions
virtual HRESULT STDMETHODCALLTYPE CreateGrid(HWND, BSTR, int, int, int, int, int, int, int, COLORREF, COLORREF, BSTR, int, int);
virtual HRESULT STDMETHODCALLTYPE SetRowCount(int, int);
virtual HRESULT STDMETHODCALLTYPE GetRowCount(int*);
virtual HRESULT STDMETHODCALLTYPE SetData(int, int, BSTR);
virtual HRESULT STDMETHODCALLTYPE GetData(int, int, BSTR*);
virtual HRESULT STDMETHODCALLTYPE FlushData(void);
virtual HRESULT STDMETHODCALLTYPE Refresh(void);
virtual HRESULT STDMETHODCALLTYPE GetVisibleRows(int*);
virtual HRESULT STDMETHODCALLTYPE GethGrid(HWND*);
virtual HRESULT STDMETHODCALLTYPE GethCell(int, int, HWND*);
virtual HRESULT STDMETHODCALLTYPE GethComboBox(int, HWND*);
virtual HRESULT STDMETHODCALLTYPE SetCellAttributes(int, int, COLORREF, COLORREF);
virtual HRESULT STDMETHODCALLTYPE DeleteRow(int);

//IConnectionPointContainer Interface Methods/Functions
virtual HRESULT STDMETHODCALLTYPE EnumConnectionPoints(IEnumConnectionPoints** ppEnum);            //not implemented
virtual HRESULT STDMETHODCALLTYPE FindConnectionPoint(REFIID riid, IConnectionPoint** ppCP);

// IConnectionPoint
virtual HRESULT STDMETHODCALLTYPE GetConnectionInterface(IID* pIID);                               //not implemented
virtual HRESULT STDMETHODCALLTYPE GetConnectionPointContainer(IConnectionPointContainer** ppCPC);  //not implemented
virtual HRESULT STDMETHODCALLTYPE Advise(IUnknown* pUnknown, DWORD* pdwCookie);
virtual HRESULT STDMETHODCALLTYPE Unadvise(DWORD dwCookie);
virtual HRESULT STDMETHODCALLTYPE EnumConnections(IEnumConnections** ppEnum);                      //not implemented

public:
HWND          hWndCtrl;
IGridEvents** pISink;
long          m_lRef;
};


class GridClassFactory : public IClassFactory
{
public:
GridClassFactory();
virtual ~GridClassFactory();

public:
//IUnknown
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, void**);
virtual ULONG   STDMETHODCALLTYPE AddRef();
virtual ULONG   STDMETHODCALLTYPE Release();

//IclassFactory
virtual HRESULT STDMETHODCALLTYPE CreateInstance(LPUNKNOWN, REFIID, void**);
virtual HRESULT STDMETHODCALLTYPE LockServer(BOOL);

protected:
long  m_lRef;
};


struct GridData
{
int         iCtrlId;
HWND        hParent;
HWND        hGrid;
HWND        hBase;
HWND        hPane;
HWND        hCtrlInCell;
int         cx;
int         cy;
HWND        hHeader;
int         iCols;
int         iRows;
int         iVisibleRows;
int         iRowHeight;
int         iPaneHeight;
int         iEditedCellRow;
int         iEditedRow;
int         iEditedCol;
FHGrid*     pComObj;
DWORD*      pColWidths;
DWORD*      pCellCtrlTypes;
HWND*       pCellHandles;
TCHAR**     pGridMemory;
COLORREF*   pTextColor;
HBRUSH*     pBackColor;
COLORREF*   pCreatedColors;
HBRUSH*     pCreatedBrushes;
HWND*       pVButtons;
HWND*       pCtrlHdls;
COLORREF    iSelectionBackColor;
COLORREF    iSelectionTextColor;
int         blnRowSelected;
int         iSelectedRow;
int         iFontSize;
int         iFontWeight;
HFONT       hFont;
TCHAR       szFontName[28];
};

void Initialize();
#endif
//End Grid.h

Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 16, 2016, 07:07:06 PM
Next is WinCode.cpp...


[CODE]
// cl WinCode.cpp /O1 /Os /GS- /c
#define  UNICODE
#define  _UNICODE
#include <windows.h>
#include <commctrl.h>
#include <objbase.h>
#include <ocidl.h>
#include <olectl.h>
#include "memory.h"
#include "stdio.h"
#include "tchar.h"
#include "GStrings.h"
#include "Grid.h"
#include "WinCode.h"

#define SizX(x)  x * rxRatio
#define SizY(y)  y * ryRatio
//#define  MYDEBUG

#if defined MYDEBUG
extern   FILE* fp;
#endif

WNDPROC  fnEditWndProc;  // edit control subclass


long fnGridProc_OnCreate(lpWndEventArgs Wea)
{
int iEndRectHt,iHalf,iRemainder,iDifference,iNewGridHt;
int iCols=0,iFlds=0,iHdlCount=0,iCtr=0,iSize=0;
CREATESTRUCT* pCreateStruct=NULL;
GridData* pGridData1=NULL;
GridData* pGridData2=NULL;
String* strParseData;
HANDLE hHeap=NULL;
TCHAR szText[64];
String strSetup;
HDITEM hdrItem;
DWORD dwStyle;
HWND hCell;
HDC hDC;
RECT rc;

#if defined MYDEBUG
_ftprintf(fp,_T("  Entering fnGridProc_OnCreate()\r\n"));
#endif
pCreateStruct=(CREATESTRUCT*)Wea->lParam;
Wea->hIns=pCreateStruct->hInstance;
pGridData1=(GridData*)pCreateStruct->lpCreateParams;
strSetup=(TCHAR*)pCreateStruct->lpszName;
#if defined MYDEBUG
_ftprintf(fp,_T("    Wea->hWnd                  = %u\r\n"),Wea->hWnd);
_ftprintf(fp,_T("    pCreateStruct              = %u\r\n"),pCreateStruct);
_ftprintf(fp,_T("    pGridData1                 = %u\r\n"),pGridData1);
_ftprintf(fp,_T("    strSetup.lpStr()           = %s\r\n"),strSetup.lpStr());
_ftprintf(fp,_T("    pCreateStruct->hwndParent  = %u\r\n"),pCreateStruct->hwndParent);
_ftprintf(fp,_T("    pCreateStruct->x           = %d\r\n"),pCreateStruct->x);
_ftprintf(fp,_T("    pCreateStruct->y           = %d\r\n"),pCreateStruct->y);
_ftprintf(fp,_T("    pCreateStruct->cx          = %d\r\n"),pCreateStruct->cx);
_ftprintf(fp,_T("    pCreateStruct->cy          = %d\r\n"),pCreateStruct->cy);
_ftprintf(fp,_T("    pCreateStruct->hMenu       = %u\r\n"),pCreateStruct->hMenu);
_ftprintf(fp,_T("    pGridData1->cx             = %d\r\n"),pGridData1->cx);
_ftprintf(fp,_T("    pGridData1->iRows          = %d\r\n"),pGridData1->iRows);
_ftprintf(fp,_T("    pGridData1->iCols          = %d\r\n"),pGridData1->iCols);
_ftprintf(fp,_T("    pGridData1->iRowHeight     = %d\r\n"),pGridData1->iRowHeight);
_ftprintf(fp,_T("    pGridData1->szFontName     = %s\r\n"),pGridData1->szFontName);
_ftprintf(fp,_T("    pGridData1->iFontSize      = %d\r\n"),pGridData1->iFontSize);
_ftprintf(fp,_T("    pGridData1->iFontWeight    = %d\r\n"),pGridData1->iFontWeight);
#endif
pGridData1->hGrid=Wea->hWnd;
GetClientRect(Wea->hWnd,&rc);
iCols=strSetup.ParseCount(_T(','));
#if defined MYDEBUG
_ftprintf(fp,_T("    rc.left                    = %d\r\n"),rc.left);
_ftprintf(fp,_T("    rc.top                     = %d\r\n"),rc.top);
_ftprintf(fp,_T("    rc.right                   = %d\r\n"),rc.right);
_ftprintf(fp,_T("    rc.bottom                  = %d\r\n"),rc.bottom);
_ftprintf(fp,_T("    iCols                      = %d\r\n"),iCols);
#endif

if(iCols!=pGridData1->iCols)
    return -1;
hHeap=GetProcessHeap();
if(!hHeap)
    return -1;
pGridData2=(GridData*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,sizeof(GridData));
if(!pGridData2)
    return -1;
SetWindowLongPtr(Wea->hWnd,0,(LONG_PTR)pGridData2);
pGridData1->blnRowSelected=FALSE;
#if defined MYDEBUG
_ftprintf(fp,_T("    pGridData2                 = %u\r\n"),pGridData2);
#endif
memcpy(pGridData2,pGridData1,sizeof(GridData));

// DPI Handling
double dpiX, dpiY;
double rxRatio, ryRatio;
hDC = GetDC(NULL);
dpiX=GetDeviceCaps(hDC, LOGPIXELSX);
dpiY=GetDeviceCaps(hDC, LOGPIXELSY);
rxRatio=(dpiX/96);
ryRatio=(dpiY/96);
#ifdef MYDEBUG
//_ftprintf(fp,_T("    dpiX                       = %6.2f\n"),dpiX);
//_ftprintf(fp,_T("    dpiY                       = %6.2f\n"),dpiY);
//_ftprintf(fp,_T("    rxRatio                    = %6.2f\n"),rxRatio);
//_ftprintf(fp,_T("    ryRatio                    = %6.2f\n"),ryRatio);
#endif
// DPI Handling End

// Now gonna try to create font ...
#if defined MYDEBUG
_ftprintf(fp,_T("\r\n    Now Gonna Try To Create Font...\r\n"));
_ftprintf(fp,_T("    pGridData2->szFontName = %s\r\n"),pGridData2->szFontName);
#endif
if(pGridData2->szFontName)
{
    pGridData2->hFont=CreateFont(-1*(pGridData2->iFontSize*dpiY)/72,0,0,0,pGridData2->iFontWeight,0,0,0,ANSI_CHARSET,0,0,DEFAULT_QUALITY,0,pGridData2->szFontName);
    #if defined MYDEBUG
    TEXTMETRIC tm;     // DPI Added!!!!!!
    HFONT hTmp=(HFONT)SelectObject(hDC,pGridData2->hFont);
    GetTextMetrics(hDC,&tm);  // DPI Added!!!!!!
    _ftprintf(fp,_T("    pGridData2->hFont = %u\r\n"),pGridData2->hFont);
    _ftprintf(fp,_T("    tm.tmHeight       = %d\r\n"),tm.tmHeight);
    _ftprintf(fp,_T("    tm.tmAveCharWidth = %d\r\n\r\n"),tm.tmAveCharWidth);
    SelectObject(hDC,hTmp);
    #endif
    ReleaseDC(Wea->hWnd,hDC);
}
else
    return -1;

// Deal With Resizing Grid And Over-riding Client's Dimensions (a little) If Unsightly Extra
// Space Exists Between Bottom Of Last Row And Top Of Horizontal Scroll Bar
iNewGridHt=pCreateStruct->cy;
iHalf=pGridData2->iRowHeight/2;
iRemainder=rc.bottom % pGridData2->iRowHeight;
#ifdef MYDEBUG
_ftprintf(fp,_T("    iNewGridHt                 = %d\r\n"),iNewGridHt);
_ftprintf(fp,_T("    pGridData2->iRowHeight     = %d\r\n"),pGridData2->iRowHeight);
_ftprintf(fp,_T("    iHalf                      = %d\r\n"),iHalf);
_ftprintf(fp,_T("    iRemainder                 = %d\r\n"),iRemainder);
#endif
if(iRemainder>=iHalf)
{
    iDifference=pGridData2->iRowHeight-iRemainder;
    iEndRectHt=rc.bottom+(pGridData2->iRowHeight-iRemainder);
}
else
{
    iDifference=-iRemainder;
    iEndRectHt=rc.bottom-iRemainder;
}
iNewGridHt=iNewGridHt+iDifference;
#ifdef MYDEBUG
_ftprintf(fp,_T("    iEndRectHt                 = %d\r\n"),iEndRectHt);
_ftprintf(fp,_T("    iDifference                = %d\r\n"),iDifference);
_ftprintf(fp,_T("    iNewGridHt                 = %d\r\n"),iNewGridHt);
#endif
pGridData2->iVisibleRows=(iEndRectHt-pGridData2->iRowHeight)/pGridData2->iRowHeight;
if(pGridData2->iRows<pGridData2->iVisibleRows)
    pGridData2->iRows=pGridData2->iVisibleRows+1;
pGridData2->iPaneHeight=(pGridData2->iVisibleRows+1)*pGridData2->iRowHeight;
#if defined MYDEBUG
_ftprintf(fp,_T("    pGridData2->hParent        = %u\r\n"),pGridData2->hParent);
_ftprintf(fp,_T("    pGridData2->hGrid          = %u\r\n"),pGridData2->hGrid);
_ftprintf(fp,_T("    pGridData2->iCtrlId        = %d\r\n"),pGridData2->iCtrlId);
_ftprintf(fp,_T("    pGridData2->cx             = %u\r\n"),pGridData2->cx);
_ftprintf(fp,_T("    pGridData2->cy             = %u\r\n"),pGridData2->cy);
_ftprintf(fp,_T("    pGridData2->iRows          = %d\r\n"),pGridData2->iRows);
_ftprintf(fp,_T("    pGridData2->iCols          = %d\r\n"),pGridData2->iCols);
_ftprintf(fp,_T("    pGridData2->iRowHeight     = %d\r\n"),pGridData2->iRowHeight);
_ftprintf(fp,_T("    pGridData2->szFontName     = %s\r\n"),pGridData2->szFontName);
_ftprintf(fp,_T("    pGridData2->iFontSize      = %d\r\n"),pGridData2->iFontSize);
_ftprintf(fp,_T("    pGridData2->iFontWeight    = %d\r\n"),pGridData2->iFontWeight);
_ftprintf(fp,_T("    pGridData2->iVisibleRows   = %d\r\n"),pGridData2->iVisibleRows);
_ftprintf(fp,_T("    pGridData2->iPaneHeight    = %d\r\n"),pGridData2->iPaneHeight);
_ftprintf(fp,_T("    pGridData2->blnRowSelected = %d\r\n\r\n"),pGridData2->blnRowSelected);
_ftprintf(fp,_T("    i         strParseData(i) \r\n"));
_ftprintf(fp,_T("    ============================\r\n"));
#endif
strParseData=new String[iCols];
strSetup.Parse(strParseData,_T(','));
for(unsigned int i=0; i<iCols; i++)
{
     strParseData[i].LTrim();
     #if defined MYDEBUG
     _ftprintf(fp,_T("    %d\t%s\r\n"),i,strParseData[i].lpStr());
     #endif
}
pGridData2->pColWidths=(DWORD*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,sizeof(DWORD)*(iCols+1));
if(!pGridData2->pColWidths)
    goto CleanUp;
#if defined MYDEBUG
_ftprintf(fp,_T("\n    pGridData2->pColWidths    = %u\r\n"),pGridData2->pColWidths);
#endif
pGridData2->hBase=CreateWindowEx(0,_T("Base"),_T(""),WS_CHILD|WS_VISIBLE,0,0,0,0,Wea->hWnd,(HMENU)1499,Wea->hIns,0);
pGridData2->hPane=CreateWindowEx(0,_T("Pane"),_T(""),WS_CHILD|WS_VISIBLE,0,0,0,0,pGridData2->hBase,(HMENU)ID_PANE,Wea->hIns,0);
dwStyle=WS_CHILD | WS_BORDER | WS_VISIBLE | HDS_HOTTRACK | HDS_HORZ;
pGridData2->hHeader=CreateWindowEx(0,WC_HEADER,_T(""),dwStyle,0,0,0,0,pGridData2->hPane,(HMENU)ID_HEADER,Wea->hIns,0);
#if defined MYDEBUG
_ftprintf(fp,_T("    pGridData2->hBase         = %u\r\n"),pGridData2->hBase);
_ftprintf(fp,_T("    pGridData2->hPane         = %u\r\n"),pGridData2->hPane);
_ftprintf(fp,_T("    pGridData2->hHeader       = %u\r\n"),pGridData2->hHeader);
_ftprintf(fp,_T("    HDF_LEFT                  = %d\r\n"),HDF_LEFT);
_ftprintf(fp,_T("    HDF_CENTER                = %d\r\n"),HDF_CENTER);
_ftprintf(fp,_T("    HDF_RIGHT                 = %d\r\n\r\n"),HDF_RIGHT);
#endif
SetWindowLongPtr(pGridData2->hPane,0,(LONG_PTR)pGridData2);
pGridData2->pCellCtrlTypes=(DWORD*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,iCols*sizeof(DWORD));    // no extra bytes allocated here, so its zero based
if(!pGridData2->pCellCtrlTypes)
    goto CleanUp;
pGridData2->pCtrlHdls=(HWND*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,iCols*sizeof(HWND*));          // no extra bytes allocated here either, so its zero based
if(!pGridData2->pCtrlHdls)
    goto CleanUp;
hdrItem.mask=HDI_FORMAT | HDI_WIDTH | HDI_TEXT;
#if defined MYDEBUG
_ftprintf(fp,_T("    i\tstrFieldData[0]\tpGridData2->pColWidths[i]\tstrFieldData[1]\tstrFieldData[2]\tstrFieldData[3]\tpPos[i]\t\tpGridData2->pCellCtrlTypes[i]\r\n"));
_ftprintf(fp,_T("    ============================================================================================================================================\r\n"));
#endif
int* pPos=(int*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,iCols*sizeof(int));    // and no extra bytes allocated here
if(!pPos)
    goto CleanUp;
pGridData2->pColWidths[iCols]=0;
for(unsigned int i=0; i<iCols; i++)
{
     iFlds=strParseData[i].ParseCount(_T(':'));
     String* strFieldData=new String[iFlds];
     strParseData[i].Parse(strFieldData,_T(':'));
     pGridData2->pColWidths[i]=SizX((DWORD)strFieldData[0].iVal());
     pGridData2->pColWidths[iCols]=pGridData2->pColWidths[iCols]+pGridData2->pColWidths[i];
     hdrItem.cxy=pGridData2->pColWidths[i];
     _tcscpy(szText,strFieldData[1].lpStr());
     hdrItem.pszText=szText;
     hdrItem.cchTextMax=_tcslen(szText);
     hdrItem.fmt=0;
     if(strFieldData[2]==_T("<"))
        hdrItem.fmt=HDF_LEFT;
     else
     {
        if(strFieldData[2]==_T("^"))
           hdrItem.fmt=HDF_CENTER;
        else
           hdrItem.fmt=HDF_RIGHT;
     }
     hdrItem.fmt=hdrItem.fmt|HDF_STRING;
     Header_InsertItem(pGridData2->hHeader,i,&hdrItem);
     if(i)
        pPos[i]=pPos[i-1]+pGridData2->pColWidths[i-1];
     if(strFieldData[3]==_T("none"))
        pGridData2->pCellCtrlTypes[i]=0;
     else
     {
        if(strFieldData[3]==_T("edit"))
           pGridData2->pCellCtrlTypes[i]=1;
        else
        {
           if(strFieldData[3]==_T("combo"))
              pGridData2->pCellCtrlTypes[i]=2;
           if(strFieldData[3]==_T("check"))
              pGridData2->pCellCtrlTypes[i]=0;
        }
     }
     #if defined MYDEBUG
     _ftprintf
     (
      fp,
      _T("    %d\t%s\t\t%u\t\t\t\t%s\t%s\t\t%s\t\t%d\t\t%u\r\n"),
      i,strFieldData[0].lpStr(),pGridData2->pColWidths[i],strFieldData[1].lpStr(),strFieldData[2].lpStr(),strFieldData[3].lpStr(),pPos[i],pGridData2->pCellCtrlTypes[i]
     );
     #endif
     delete [] strFieldData;
}
#if defined MYDEBUG
_ftprintf(fp,_T("\r\n"));
_ftprintf(fp,_T("    iCols = %u\r\n\r\n"),iCols);
_ftprintf(fp,_T("    i\tpGridData2->pColWidths[i]\r\n"));
_ftprintf(fp,_T("    ================================\r\n"));
for(unsigned int i=0; i<=iCols; i++)
     _ftprintf(fp,_T("    %u\t\t%u\r\n"),i,pGridData2->pColWidths[i]);
_ftprintf(fp,_T("\r\n"));
_ftprintf(fp,_T("    i\tpGridData2->pCtrlHdls[i]\r\n"));
_ftprintf(fp,_T("    ==================================\r\n"));
#endif
int blnEditCreated = 0;
int iCboCtr        = 0;
for(unsigned int i=0; i<iCols; i++)
{
     // Edit Control In Column
     if(pGridData2->pCellCtrlTypes[i]==GRID_CELL_CTRL_EDIT)
     {
        if(blnEditCreated==FALSE)
        {
           dwStyle = WS_CHILD | ES_AUTOHSCROLL;
           pGridData2->pCtrlHdls[i]=CreateWindowEx(0,_T("edit"),_T(""),dwStyle,0,0,0,0,Wea->hWnd,(HMENU)IDC_EDIT,Wea->hIns,0);
           pGridData2->hCtrlInCell=pGridData2->pCtrlHdls[i];
           blnEditCreated=TRUE;
        }
        else
        {
           pGridData2->pCtrlHdls[i]=pGridData2->hCtrlInCell;
        }
     }

     // Combo Box In Column
     if(pGridData2->pCellCtrlTypes[i]==GRID_CELL_CTRL_COMBO)
     {
        dwStyle=WS_CHILD | CBS_DROPDOWNLIST | WS_VSCROLL; //Or %CBS_NOINTEGRALHEIGHT
        pGridData2->pCtrlHdls[i]=CreateWindowEx(0,_T("combobox"),_T(""),dwStyle,0,0,0,0,Wea->hWnd,(HMENU)(IDC_COMBO+iCboCtr),Wea->hIns,0);
        iCboCtr++;
     }

     #if defined MYDEBUG
     _ftprintf(fp,_T("    %u\t\t%u\r\n"),i,pGridData2->pCtrlHdls[i]);
     #endif
}
pGridData2->hCtrlInCell=0;    //    DANGER!!!!!! Addition!  I don't know why this needes to be initially set?
#if defined MYDEBUG
_ftprintf(fp,_T("\r\n"));
_ftprintf(fp,_T("    pGridData2->pColWidths[iCols] = %d\r\n"),pGridData2->pColWidths[iCols]);
_ftprintf(fp,_T("\r\n"));
#endif
MoveWindow(Wea->hWnd,pCreateStruct->x,pCreateStruct->y,pCreateStruct->cx,iNewGridHt,FALSE);
MoveWindow(pGridData2->hBase,SizX(12),0,rc.right-SizX(12),pGridData2->iPaneHeight,FALSE);
MoveWindow(pGridData2->hPane,0,0,pGridData2->pColWidths[iCols],pGridData2->iPaneHeight,FALSE);
MoveWindow(pGridData2->hHeader,0,0,pGridData2->pColWidths[iCols],pGridData2->iRowHeight,TRUE);
delete [] strParseData;
pGridData2->pVButtons=(HWND*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,(pGridData2->iVisibleRows+1)*sizeof(void*));
#if defined MYDEBUG
_ftprintf(fp,_T("    pGridData2->pVButtons = %u\r\n\r\n"),pGridData2->pVButtons);
_ftprintf(fp,_T("    i\tpGridData2->pVButtons[i]\r\n"));
_ftprintf(fp,_T("    ===================================\r\n"));
#endif
if(!pGridData2->pVButtons)
    goto CleanUp;
else
{
    for(unsigned int i=0; i<=pGridData2->iVisibleRows; i++)
    {                                                                                                                  // possibly SizX(12) ???
        pGridData2->pVButtons[i]=CreateWindowEx(0,_T("button"),_T(""),WS_CHILD|WS_VISIBLE|BS_FLAT,0,pGridData2->iRowHeight*i,SizX(12),pGridData2->iRowHeight,Wea->hWnd,(HMENU)(20000+i),Wea->hIns,0);
        #if defined MYDEBUG
        _ftprintf(fp,_T("    %u\t%u\r\n"),i,pGridData2->pVButtons[i]);
        #endif
    }
}

iHdlCount=pGridData2->iCols*pGridData2->iVisibleRows;
pGridData2->pCellHandles=(HWND*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,iHdlCount*sizeof(void*));
#if defined MYDEBUG
_ftprintf(fp,_T("    iCols                    = %d\r\n"),iCols);
_ftprintf(fp,_T("    pGridData2->iCols        = %u\r\n"),pGridData2->iCols);
_ftprintf(fp,_T("    iHdlCount                = %d\r\n"),iHdlCount);
_ftprintf(fp,_T("    pGridData2->pCellHandles = %u\r\n"),pGridData2->pCellHandles);
#endif
if(!pGridData2->pCellHandles)
    goto CleanUp;
dwStyle=WS_CHILD | WS_VISIBLE | WS_BORDER;
#if defined MYDEBUG
_ftprintf(fp,_T("\r\n"));
_ftprintf(fp,_T("    i\t\tj\tiPos(j)\tyLoc\thCell\r\n"));
_ftprintf(fp,_T("    =============================================================\r\n"));
#endif
for(int i=0; i<pGridData2->iVisibleRows; i++)
{
     for(int j=0; j<pGridData2->iCols; j++)
     {
         hCell=CreateWindowEx(0,_T("Cell"),_T(""),dwStyle,pPos[j],pGridData2->iRowHeight+(i*pGridData2->iRowHeight),pGridData2->pColWidths[j],pGridData2->iRowHeight,pGridData2->hPane,(HMENU)(ID_CELL+iCtr),Wea->hIns,0);
         pGridData2->pCellHandles[iCtr]=hCell;
         SetWindowLongPtr(hCell,sizeof(void*),(LONG_PTR)pGridData2->hFont);   // 4 for 32 bit; 8 for 64 bit
         #if defined MYDEBUG
         _ftprintf(fp,_T("    %d\t\t%d\t%u\t%d\t%u\r\n"),i,j,pPos[j],pGridData2->iRowHeight+(i*pGridData2->iRowHeight),hCell);
         #endif
         iCtr++;
     }
}

// grid memory
iSize=pGridData2->iCols*pGridData2->iRows;
pGridData2->pGridMemory=(TCHAR**)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,iSize*sizeof(void*));
if(!pGridData2->pGridMemory)
    goto CleanUp;
pGridData2->pTextColor=(COLORREF*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,iSize*sizeof(void*));
if(!pGridData2->pTextColor)
    goto CleanUp;
pGridData2->pBackColor=(HBRUSH*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,iSize*sizeof(void*));
if(!pGridData2->pBackColor)
    goto CleanUp;
pGridData2->pCreatedColors=(COLORREF*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,(MAX_COLORS+1)*sizeof(void*));
if(!pGridData2->pCreatedColors)
    goto CleanUp;
pGridData2->pCreatedBrushes=(HBRUSH*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,(MAX_COLORS+1)*sizeof(void*));
if(!pGridData2->pCreatedBrushes)
    goto CleanUp;
#ifdef MYDEBUG
_ftprintf(fp,_T("    iSize                   = %d\r\n"),iSize);
_ftprintf(fp,_T("    pGridData2->pGridMemory = %u\r\n"),pGridData2->pGridMemory);
#endif

//Done
if(pPos)
    HeapFree(hHeap,0,pPos);
#if defined MYDEBUG
_ftprintf(fp,_T("    Everything Allocated OK!\r\n"));
_ftprintf(fp,_T("  Leaving fnGridProc_OnCreate()\r\n\r\n"));
#endif
return 0;

CleanUp:
if(pGridData2->pColWidths)
{
    HeapFree(hHeap,0,pGridData2->pColWidths);
    pGridData2->pColWidths=NULL;
}
if(pGridData2->pCellCtrlTypes)
{
    HeapFree(hHeap,0,pGridData2->pCellCtrlTypes);
    pGridData2->pCellCtrlTypes=NULL;
}
if(pGridData2->pCtrlHdls)
{
    HeapFree(hHeap,0,pGridData2->pCtrlHdls);
    pGridData2->pCtrlHdls=NULL;
}
if(pPos)
    HeapFree(hHeap,0,pPos);
if(pGridData2->pVButtons)
{
    HeapFree(hHeap,0,pGridData2->pVButtons);
    pGridData2->pVButtons=NULL;
}
if(pGridData2->hFont)
    DeleteObject(pGridData2->hFont);
if(pGridData2->pCellHandles)
    HeapFree(hHeap,0,pGridData2->pCellHandles);
if(pGridData2->pGridMemory)
    HeapFree(hHeap,0,pGridData2->pGridMemory);
if(pGridData2->pTextColor)
    HeapFree(hHeap,0,pGridData2->pTextColor);
if(pGridData2->pBackColor)
    HeapFree(hHeap,0,pGridData2->pBackColor);
if(pGridData2->pCreatedColors)
    HeapFree(hHeap,0,pGridData2->pCreatedColors);
if(pGridData2->pCreatedBrushes)
    HeapFree(hHeap,0,pGridData2->pCreatedBrushes);
#if defined MYDEBUG
_ftprintf(fp,_T("  Leaving fnGridProc_OnCreate()\r\n\r\n"));
#endif

return 0;
}


long fnGridProc_OnSize(lpWndEventArgs Wea)
{
GridData* pGridData=NULL;
SCROLLINFO si;
int iCols;

#if defined MYDEBUG
_ftprintf(fp,_T("  Entering fnGridProc_OnSize()\r\n"));
_ftprintf(fp,_T("    Wea->hWnd = %u\r\n"),Wea->hWnd);
#endif
pGridData=(GridData*)GetWindowLongPtr(Wea->hWnd,0);
iCols=pGridData->iCols;
#ifdef MYDEBUG
_ftprintf(fp,_T("    pGridData    = %p\r\n"),pGridData);
_ftprintf(fp,_T("    iCols        = %d\r\n"),iCols);
#endif

//Set Up Horizontal Scrollbar
memset(&si, 0, sizeof(SCROLLINFO));
si.cbSize = sizeof(SCROLLINFO);
si.fMask  = SIF_RANGE | SIF_PAGE | SIF_POS | SIF_DISABLENOSCROLL;
si.nMin   = 0;
si.nMax   = pGridData->pColWidths[iCols];
si.nPage  = pGridData->cx-33; // 33 is the width of vert
si.nPos   = 0;                // btns + width scroll bar + window edge
SetScrollInfo(Wea->hWnd,SB_HORZ,&si,TRUE);
#ifdef MYDEBUG
_ftprintf(fp,_T("    Horizontal Scrollbar....\r\n"));
_ftprintf(fp,_T("    si.nMin    = %d\r\n"),si.nMin);
_ftprintf(fp,_T("    si.nMax    = %d\r\n"),si.nMax);
_ftprintf(fp,_T("    si.nPos    = %d\r\n"),si.nPos);
#endif

//Set Up Verticle Scrollbar
//ZeroMemory(&si, sizeof(SCROLLINFO));
memset(&si, 0, sizeof(SCROLLINFO));
si.cbSize = sizeof(SCROLLINFO);
si.fMask  = SIF_RANGE | SIF_PAGE | SIF_POS;
si.nMin   = 1;
si.nMax   = pGridData->iRows;
si.nPage  = pGridData->iVisibleRows;
si.nPos   = 1;
SetScrollInfo(Wea->hWnd,SB_VERT,&si,TRUE);
#ifdef MYDEBUG
_ftprintf(fp,_T("    Verticle Scrollbar....\r\n"));
_ftprintf(fp,_T("    si.nMin    = %d\r\n"), si.nMin);
_ftprintf(fp,_T("    si.nMax    = %d\r\n"), si.nMax);
_ftprintf(fp,_T("    si.nPos    = %d\r\n"), si.nPos);
_ftprintf(fp,_T("    Leaving %WM_SIZE Case\r\n"));
_ftprintf(fp,_T("  Leaving fnGridProc_OnSize()\r\n\r\n"));
#endif

return 0;
}


long fnGridProc_OnHScroll(lpWndEventArgs Wea)
{
GridData* pGridData=NULL;
int iCols,iScrollPos;
SCROLLINFO si;

#if defined MYDEBUG
_ftprintf(fp,_T("Entering fnGridProc_OnHScroll()\r\n"));
_ftprintf(fp,_T("  Wea->hWnd = %p\r\n"),Wea->hWnd);
#endif
//ZeroMemory(&si, sizeof(SCROLLINFO));
memset(&si, 0, sizeof(SCROLLINFO));
pGridData = (GridData*)GetWindowLongPtr(Wea->hWnd,0);
iCols     = pGridData->iCols;
si.cbSize = sizeof(SCROLLINFO);
si.fMask  = SIF_ALL;
GetScrollInfo(pGridData->hGrid,SB_HORZ,&si);
iScrollPos=si.nPos;
#ifdef MYDEBUG
_ftprintf(fp,_T("    Before Adjustments\r\n"));
_ftprintf(fp,_T("    si.nMin    = %d\r\n"), si.nMin);
_ftprintf(fp,_T("    si.nMax    = %d\r\n"), si.nMax);
_ftprintf(fp,_T("    si.nPos    = %d\r\n"), si.nPos);
#endif
switch(LOWORD(Wea->wParam))
{
   case SB_LINELEFT:
     {
        if(si.nPos > si.nMin)
           si.nPos=si.nPos-50;
        break;
     }
   case SB_PAGELEFT:
     {
        si.nPos = si.nPos - si.nPage;
        break;
     }
   case SB_LINERIGHT:
     {
        if(si.nPos<si.nMax)
           si.nPos=si.nPos+50;
        break;
     }
   case SB_PAGERIGHT:
     {
        si.nPos = si.nPos + si.nPage;
        break;
     }
   case SB_THUMBTRACK:
     {
        si.nPos=si.nTrackPos;
        break;
     }
}
si.fMask = SIF_POS;
SetScrollInfo(pGridData->hGrid,SB_HORZ,&si,TRUE);
GetScrollInfo(pGridData->hGrid,SB_HORZ,&si);
if(iScrollPos!=si.nPos)
{
    if(si.nPos==0)
       SetWindowPos(pGridData->hPane,HWND_TOP,0,0,pGridData->pColWidths[iCols],pGridData->iPaneHeight,SWP_SHOWWINDOW);
    else
       SetWindowPos(pGridData->hPane,HWND_TOP,-si.nPos,0,pGridData->pColWidths[iCols],pGridData->iPaneHeight,SWP_SHOWWINDOW);
}
#ifdef MYDEBUG
_ftprintf(fp,_T("    After All Adjustments\r\n"));
_ftprintf(fp,_T("    si.nMin    = %d\r\n"),si.nMin);
_ftprintf(fp,_T("    si.nMax    = %d\r\n"),si.nMax);
_ftprintf(fp,_T("    si.nPos    = %d\r\n"),si.nPos);
_ftprintf(fp,_T("    Leaving %WM_HSCROLL Case\r\n"));
_ftprintf(fp,_T("Leaving fnGridProc_OnHScroll()\r\n\r\n"));
#endif

return 0;
}


long fnGridProc_OnVScroll(lpWndEventArgs Wea)
{
GridData* pGridData=NULL;
int iCols,iScrollPos;
SCROLLINFO si;
HWND hCell;

#if defined MYDEBUG
_ftprintf(fp,_T("Entering fnGridProc_OnVScroll()\r\n"));
_ftprintf(fp,_T("  Wea->hWnd        = %u\r\n"),Wea->hWnd);
#endif
//ZeroMemory(&si, sizeof(SCROLLINFO));
memset(&si, 0, sizeof(SCROLLINFO));
pGridData = (GridData*)GetWindowLongPtr(Wea->hWnd,0);
pGridData->pComObj->FlushData();
si.cbSize = sizeof(SCROLLINFO);
si.fMask  = SIF_ALL;
GetScrollInfo(pGridData->hGrid,SB_VERT,&si);
iScrollPos=si.nPos;
#ifdef MYDEBUG
_ftprintf(fp,_T("  Before Adjustments\r\n"));
_ftprintf(fp,_T("  si.nMin          = %d\r\n"), si.nMin);
_ftprintf(fp,_T("  si.nMax          = %d\r\n"), si.nMax);
_ftprintf(fp,_T("  si.nPos          = %d\r\n"), si.nPos);
_ftprintf(fp,_T("  pGridData->hGrid = %u\r\n"), pGridData->hGrid);
_ftprintf(fp,_T("  pGridData        = %u\r\n"), pGridData);
#endif
switch(LOWORD(Wea->wParam))             // this originally didn't work
{
    case SB_LINEUP:
      {
         if(si.nPos > si.nMin)
            si.nPos=si.nPos-1;
         break;
      }
    case SB_PAGEUP:
      {
         si.nPos = si.nPos - si.nPage;
         break;
      }
    case SB_LINEDOWN:
      {
         if(si.nPos<si.nMax)
            si.nPos=si.nPos+1;
         break;
      }
    case SB_PAGEDOWN:
      {
         si.nPos = si.nPos + si.nPage;
         break;
      }
    case SB_THUMBTRACK:
      {
         si.nPos=si.nTrackPos;
         break;
      }
}
si.fMask=SIF_POS;
SetScrollInfo(pGridData->hGrid,SB_VERT,&si,TRUE);
GetScrollInfo(pGridData->hGrid,SB_VERT,&si);
if(iScrollPos!=si.nPos)
{
     int iNum,iLast;
     iNum=pGridData->iCols*(si.nPos-1);
     iLast=(pGridData->iCols * pGridData->iVisibleRows) - 1;
     for(int i=0; i<=iLast; i++)
     {
       hCell=pGridData->pCellHandles[i];
       SetWindowLongPtr(hCell,0,(LONG_PTR)pGridData->pGridMemory[iNum]);
       SetWindowLongPtr(hCell,2*sizeof(void*),(LONG_PTR)pGridData->pTextColor[iNum]);
       SetWindowLongPtr(hCell,3*sizeof(void*),(LONG_PTR)pGridData->pBackColor[iNum]);
       iNum++;
     }
}
InvalidateRect(pGridData->hGrid,NULL,TRUE);
#ifdef MYDEBUG
_ftprintf(fp,_T("  After All Adjustments\r\n"));
_ftprintf(fp,_T("  si.nMin    = %d\r\n"),si.nMin);
_ftprintf(fp,_T("  si.nMax    = %d\r\n"),si.nMax);
_ftprintf(fp,_T("  si.nPos    = %d\r\n"),si.nPos);
_ftprintf(fp,_T("  Leaving %WM_VSCROLL Case\r\n"));
_ftprintf(fp,_T("Leaving fnGridProc_OnVScroll()\r\n\r\n"));
#endif

return 0;
}


long fnGridProc_OnKeyDown(lpWndEventArgs Wea)
{
#if defined MYDEBUG
_ftprintf(fp,_T("Entering fnGridProc_OnKeyDown()\r\n"));
_ftprintf(fp,_T("  Wea->hWnd = %p\r\n"),Wea->hWnd);
#endif
if(Wea->wParam==VK_DELETE)
{
    GridData* pGridData=(GridData*)GetWindowLongPtr(Wea->hWnd,0);
    if(pGridData->blnRowSelected)
    {
       #if defined MYDEBUG
       _ftprintf(fp,_T("  pGridData = %p\r\n"),pGridData);
       _ftprintf(fp,_T("  A Row Is Selected, Namely, Row #%d\r\n"),pGridData->iSelectedRow);
       #endif
       for(int i=0; i<MAX_CONNECTIONS; i++)
       {
           if(pGridData->pComObj->pISink[i])
              pGridData->pComObj->pISink[i]->Grid_OnDelete(pGridData->iSelectedRow);
       }
       pGridData->pComObj->Refresh();
    }
}
#if defined MYDEBUG
_ftprintf(fp,_T("Leaving fnGridProc_OnKeyDown()\r\n\r\n"));
#endif

return 0;
}


long fnGridProc_OnCommand(lpWndEventArgs Wea)
{
#if defined MYDEBUG
_ftprintf(fp,_T("Entering fnGridProc_OnCommand()\r\n"));
_ftprintf(fp,_T("  Wea->hWnd = %p\r\n"),Wea->hWnd);
_ftprintf(fp,_T("  LOWORD(Wea->wParam) = %d\r\n"),LOWORD(Wea->wParam));
#endif
if(LOWORD(Wea->wParam)>20000)
{
    GridData* pGridData=(GridData*)GetWindowLongPtr(Wea->hWnd,0);
    pGridData->pComObj->FlushData();
    SCROLLINFO si;
    //ZeroMemory(&si, sizeof(SCROLLINFO));
    memset(&si, 0, sizeof(SCROLLINFO));
    si.cbSize = sizeof(SCROLLINFO);
    si.fMask=SIF_POS;
    GetScrollInfo(pGridData->hGrid,SB_VERT,&si);
    int iCellRow=LOWORD(Wea->wParam)-20000;
    int iGridRow=si.nPos+iCellRow-1;
    #if defined MYDEBUG
    _ftprintf(fp,_T("  iCellRow = %d\r\n"),iCellRow);
    _ftprintf(fp,_T("  iGridRow = %d\r\n"),iGridRow);
    _ftprintf(fp,_T("  pGridData->blnRowSelected = %d\r\n"),pGridData->blnRowSelected);
    #endif
    if(pGridData->blnRowSelected)
    {
       if(iGridRow==pGridData->iSelectedRow)
       {
           #ifdef MYDEBUG
           _ftprintf(fp,_T("  We Got In Where iGridRow = @pGridData.iSelectedRow!\r\n"));
           #endif
           for(int i=1; i<=pGridData->iCols; i++)
               pGridData->pComObj->SetCellAttributes(pGridData->iSelectedRow,i,0x00FFFFFF,0);
           pGridData->iSelectedRow=0, pGridData->blnRowSelected=FALSE;
       }
       else
       {
           #ifdef MYDEBUG
           _ftprintf(fp,_T("  We Got In Where iGridRow != pGridData->iSelectedRow!\r\n"));
           #endif
           for(int i=1; i<=pGridData->iCols; i++)
               pGridData->pComObj->SetCellAttributes(pGridData->iSelectedRow,i,0x00FFFFFF,0);
           pGridData->iSelectedRow=iGridRow;
           for(int i=1; i<=pGridData->iCols; i++)
               pGridData->pComObj->SetCellAttributes(iGridRow,i,pGridData->iSelectionBackColor,pGridData->iSelectionTextColor);
       }
    }
    else
    {
       #ifdef MYDEBUG
       _ftprintf(fp,_T("  We Got In Where pGridData->blnSelected = FALSE!\r\n"));
       _ftprintf(fp,_T("  pGridData->iSelectionBackColor         = 0x%x\r\n"),pGridData->iSelectionBackColor);
       _ftprintf(fp,_T("  pGridData->iSelectionTextColor         = 0x%x\r\n"),pGridData->iSelectionTextColor);
       #endif
       for(int i=1; i<=pGridData->iCols; i++)
           pGridData->pComObj->SetCellAttributes(iGridRow,i,pGridData->iSelectionBackColor,pGridData->iSelectionTextColor);
       pGridData->blnRowSelected=TRUE;
       pGridData->iSelectedRow=iGridRow;
       #ifdef MYDEBUG
       _ftprintf(fp,_T("  pGridData.iSelectedRow    = %d\r\n"),pGridData->iSelectedRow);
       _ftprintf(fp,_T("  pGridData->blnRowSelected = %d\r\n"),pGridData->blnRowSelected);
       #endif
    }
    pGridData->pComObj->Refresh();
    for(int i=0; i<MAX_CONNECTIONS; i++)
    {
        if(pGridData->pComObj->pISink[i])
           pGridData->pComObj->pISink[i]->Grid_OnRowSelection(iGridRow,pGridData->blnRowSelected);
    }
    SetFocus(Wea->hWnd);
}
#if defined MYDEBUG
_ftprintf(fp,_T("Leaving fnGridProc_OnCommand()\r\n\r\n"));
#endif

return 0;
}


long fnGridProc_OnDestroy(lpWndEventArgs Wea)
{
GridData* pGridData=NULL;
HANDLE hHeap=NULL;
TCHAR* pMem=NULL;
BOOL blnFree=0;
int iCtr=0,i,j;

#if defined MYDEBUG
_ftprintf(fp,_T("Entering fnGridProc_OnDestroy()\r\n"));
#endif
pGridData=(GridData*)GetWindowLongPtr(Wea->hWnd,0);
#if defined MYDEBUG
_ftprintf(fp,_T("  Wea->hWnd                           = %p\r\n"),Wea->hWnd);
_ftprintf(fp,_T("  pGridData                           = %p\r\n\r\n"),pGridData);
#endif
if(pGridData)
{
    #if defined MYDEBUG
    _ftprintf(fp,_T("  i\tpGridData->pColWidths[i]\r\n"));
    _ftprintf(fp,_T("  =============================\r\n"));
    for(i=0; i<5; i++)
        _ftprintf(fp,_T("  %u\t%u\r\n"),i,pGridData->pColWidths[i]);
    _ftprintf(fp,_T("\n  pGridData->pColWidths[5]            = %u\r\n"),pGridData->pColWidths[5]);
    #endif

    hHeap=GetProcessHeap();
    if(hHeap)
    {
       #if defined MYDEBUG
       _ftprintf(fp,_T("  pGridData->pColWidths               = %u\r\n"),pGridData->pColWidths);
       #endif
       if(pGridData->pColWidths)
          blnFree=HeapFree(hHeap,0,pGridData->pColWidths);
       #if defined MYDEBUG
       _ftprintf(fp,_T("  blnFree(pGridData->pColWidths)      = %u\r\n"),blnFree);
       #endif
       if(pGridData->pCellCtrlTypes)
          blnFree=HeapFree(hHeap,0,pGridData->pCellCtrlTypes);
       #if defined MYDEBUG
       _ftprintf(fp,_T("  blnFree(pGridData->pCellCtrlTypes)  = %u\r\n"),blnFree);
       #endif
       if(pGridData->pCtrlHdls)
          blnFree=HeapFree(hHeap,0,pGridData->pCtrlHdls);
       #if defined MYDEBUG
       _ftprintf(fp,_T("  blnFree(pGridData->pCtrlHdls)       = %u\r\n"),blnFree);
       #endif
       if(pGridData->pVButtons)
       {
          blnFree=HeapFree(hHeap,0,pGridData->pVButtons);
          pGridData->pVButtons=NULL;
          #if defined MYDEBUG
          _ftprintf(fp,_T("  blnFree(pGridData->pVButtons)       = %u\r\n"),blnFree);
          #endif
       }

       if(pGridData->hFont)
       {
          blnFree=DeleteObject(pGridData->hFont);
          #if defined MYDEBUG
          _ftprintf(fp,_T("  blnFree(pGridData->hFont)           = %u\r\n"),blnFree);
          #endif
       }

       if(pGridData->pCellHandles)
       {
          blnFree=HeapFree(hHeap,0,pGridData->pCellHandles);
          #if defined MYDEBUG
          _ftprintf(fp,_T("  blnFree(pGridData->pCellHandles)    = %u\r\n"),blnFree);
          #endif
          blnFree=0;
       }

       #if defined MYDEBUG
       _ftprintf(fp,_T("\n"));
       _ftprintf(fp,_T("  i             j               iCtr            pMem            pMem            blnFree\r\n"));
       _ftprintf(fp,_T("  =====================================================================================\r\n"));
       #endif
       for(i=1; i<=pGridData->iRows; i++)
       {
         for(j=1; j<=pGridData->iCols; j++)
         {
           pMem=pGridData->pGridMemory[iCtr];
           if(pMem)
           {
              #if defined MYDEBUG
              _ftprintf(fp,_T("  %d\t\t%d\t\t%d\t\t%u\t\t%s\t\t"),i,j,iCtr,pMem,pMem);
              #endif
              blnFree=HeapFree(hHeap,0,pMem);
              pMem=NULL;
              #ifdef MYDEBUG
              _ftprintf(fp,_T("%d\r\n"),blnFree);
              #endif
              blnFree=0;
           }
           else
           {
              #if defined MYDEBUG
              _ftprintf(fp,_T("  %d\t\t%d\t\t%d\t\t%u\t\t%s\t\t%d\r\n"),i,j,iCtr,pMem,pMem,blnFree);
              #endif
           }
           iCtr++;
         }
       }

       if(pGridData->pGridMemory)
       {
          blnFree=HeapFree(hHeap,0,pGridData->pGridMemory);
          #if defined MYDEBUG
          _ftprintf(fp,_T("\n  blnFree(pGridData->pGridMemory)     = %u\r\n"),blnFree);
          #endif
       }
       if(pGridData->pTextColor)
       {
          blnFree=HeapFree(hHeap,0,pGridData->pTextColor);
          #if defined MYDEBUG
          _ftprintf(fp,_T("  blnFree(pGridData->pTextColor)      = %u\r\n"),blnFree);
          #endif
       }
       if(pGridData->pBackColor)
       {
          blnFree=HeapFree(hHeap,0,pGridData->pBackColor);
          #if defined MYDEBUG
          _ftprintf(fp,_T("  blnFree(pGridData->pBackColor)      = %u\r\n"),blnFree);
          #endif
       }

       #if defined MYDEBUG
       _ftprintf(fp,_T("  pGridData->pCreatedBrushes[0]       = %d\r\n"),pGridData->pCreatedBrushes[0]);
       _ftprintf(fp,_T("  pGridData->pCreatedColors[0]        = %d\r\n\r\n"),pGridData->pCreatedColors[0]);
       _ftprintf(fp,_T("  i\tDeleteObject(i)\r\n"));
       _ftprintf(fp,_T("  =====================\r\n"));
       #endif
       for(i=1; i<=(int)pGridData->pCreatedBrushes[0]; i++)
       {
           if(pGridData->pCreatedBrushes[i])
           {
              blnFree=DeleteObject(pGridData->pCreatedBrushes[i]);
              #if defined MYDEBUG
              _ftprintf(fp,_T("  %d\t%d\r\n"),i,blnFree);
              #endif
           }
       }
       if(pGridData->pCreatedColors)
       {
          blnFree=HeapFree(hHeap,0,pGridData->pCreatedColors);
          #if defined MYDEBUG
          _ftprintf(fp,_T("\n  blnFree(pGridData->pCreatedColors)  = %d\r\n"),blnFree);
          #endif
       }
       if(pGridData->pCreatedBrushes)
       {
          blnFree=HeapFree(hHeap,0,pGridData->pCreatedBrushes);
          #if defined MYDEBUG
          _ftprintf(fp,_T("  blnFree(pGridData->pCreatedBrushes) = %d\r\n"),blnFree);
          #endif
       }
       blnFree=HeapFree(hHeap,0,pGridData);
       #if defined MYDEBUG
       _ftprintf(fp,_T("  blnFree                             = %d\r\n"),blnFree);
       #endif
    }
}
#if defined MYDEBUG
_ftprintf(fp,_T("Leaving fnGridProc_OnDestroy()\r\n\r\n"));
#endif

return 0;
}


continued...
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 16, 2016, 07:13:45 PM
The remainder of WinCode.cpp...


LRESULT CALLBACK fnGridProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
WindowsEventArguments Wea;

for(unsigned int i=0; i<7; 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));
}


LRESULT CALLBACK fnPaneProc(HWND hPane, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
if(msg==WM_NOTIFY)
{
    int iCols,iRange,index,iHt;
    GridData* pGridData=NULL;
    NMHEADER* pNotify=NULL;
    SCROLLINFO si;

    #if defined MYDEBUG
    _ftprintf(fp,_T("Entering fnPaneProc() -- WM_NOTIFY Case\r\n"));
    #endif
    pNotify=(NMHEADER*)lParam;
    pGridData=(GridData*)GetWindowLongPtr(hPane,0);
    iCols=pGridData->iCols;
    #ifdef MYDEBUG
    _ftprintf(fp,_T("  pGridData              = %p\r\n"),pGridData);
    _ftprintf(fp,_T("  pGridData->iCols       = %d\r\n"),pGridData->iCols);
    _ftprintf(fp,_T("  pGridData->pComObj     = %p\r\n"),pGridData->pComObj);
    _ftprintf(fp,_T("  pGridData->hCtrlInCell = %p\r\n"),pGridData->hCtrlInCell);
    #endif
    switch(pNotify->hdr.code)
    {
      case HDN_TRACK:
        {
           #ifdef MYDEBUG
           _ftprintf(fp,_T("  Got HDN_TRACK Notification\r\n"));
           #endif
           if(pGridData->hCtrlInCell)
           {
              pGridData->pComObj->FlushData();
              pGridData->pComObj->Refresh();
           }
           if(pGridData->pColWidths)
           {
              pGridData->pColWidths[pNotify->iItem]=pNotify->pitem->cxy;
              #ifdef MYDEBUG
              _ftprintf(fp,_T("  pNotify->iItem         = %d\r\n"),pNotify->iItem);
              _ftprintf(fp,_T("  pNotify->pitem->cxy    = %d\r\n"),pNotify->pitem->cxy);
              #endif
           }
           pGridData->pColWidths[iCols]=0;
           for(int i=0; i<iCols; i++)
               pGridData->pColWidths[iCols]=pGridData->pColWidths[iCols]+pGridData->pColWidths[i];

           si.cbSize = sizeof(SCROLLINFO);
           si.fMask  = SIF_RANGE|SIF_PAGE|SIF_DISABLENOSCROLL;
           si.nMin   = 0;
           si.nMax   = pGridData->pColWidths[iCols];
           si.nPage  = pGridData->cx-33;
           iRange    = si.nMax-si.nMin;
           SetScrollInfo(pGridData->hGrid,SB_HORZ,&si,TRUE);
           if(iRange>si.nPage)
              SetWindowPos(pGridData->hPane,HWND_TOP,0,0,pGridData->pColWidths[iCols],pGridData->cy,SWP_NOMOVE|SWP_SHOWWINDOW);
           else
              SetWindowPos(pGridData->hPane,HWND_TOP,0,0,pGridData->pColWidths[iCols],pGridData->cy,SWP_SHOWWINDOW);
           SetWindowPos(pGridData->hHeader,HWND_BOTTOM,0,0,pGridData->pColWidths[iCols],pGridData->iRowHeight,SWP_NOMOVE|SWP_SHOWWINDOW);
           #ifdef MYDEBUG
           _ftprintf(fp,_T("    si.nMin                       = %u\r\n"),si.nMin);
           _ftprintf(fp,_T("    si.nMax                       = %u\r\n"),si.nMax);
           _ftprintf(fp,_T("    si.nPage                      = %u\r\n"),si.nPage);
           _ftprintf(fp,_T("    iRange                        = %d\r\n"),iRange);
           _ftprintf(fp,_T("    pGridData->pColWidths[iCols]  = %d\r\n"),pGridData->pColWidths[iCols]);
           #endif

           HANDLE hHeap=GetProcessHeap();
           int* pPos=(int*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,iCols*sizeof(int));
           if(!pPos)
              return 0;
           for(int i=1; i<iCols; i++)
               pPos[i]=pPos[i-1]+pGridData->pColWidths[i-1];
           if(pGridData->pCellHandles)
           {
              for(int i=0; i<pGridData->iVisibleRows; i++)
              {
                  for(int j=0; j<iCols; j++)
                  {
                      index=iCols*i+j;
                      iHt=pGridData->iRowHeight;
                      MoveWindow(pGridData->pCellHandles[index], pPos[j], iHt+(i*iHt), pGridData->pColWidths[j], iHt, FALSE);
                  }
              }
              InvalidateRect(pGridData->hGrid,NULL,TRUE);
           }
           HeapFree(hHeap,0,pPos);
           break;
        }
      case HDN_ENDTRACK:
        {
           #if defined MYDEBUG
           _ftprintf(fp,_T("  Got HDN_TRACK Notification\r\n"));
           #endif
           InvalidateRect(pGridData->hGrid,0,TRUE);
           break;
        }
    }
    #if defined MYDEBUG
    _ftprintf(fp,_T("Leaving fnPaneProc() -- WM_NOTIFY Case\r\n\r\n"));
    #endif
}

return (DefWindowProc(hPane, msg, wParam, lParam));
}


LRESULT CALLBACK fnBaseProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
return (DefWindowProc(hwnd, msg, wParam, lParam));
}


LRESULT CALLBACK fnEditSubClass(HWND hEdit, UINT msg, WPARAM wParam, LPARAM lParam)
{
int blnCancel=0;
HWND hCell=GetParent(hEdit);
HWND hPane=GetParent(hCell);
HWND hBase=GetParent(hPane);
HWND hGrid=GetParent(hBase);
GridData* pGridData=(GridData*)GetWindowLongPtr(hGrid,0);
switch(msg)
{
   case WM_CHAR:
     {
        #ifdef MYDEBUG
        _ftprintf(fp,_T("Entering fnEditSubClass()  -- Case WM_CHAR\r\n"));
        _ftprintf(fp,_T("  hEdit     = %p\r\n"),hEdit);
        _ftprintf(fp,_T("  hCell     = %p\r\n"),hCell);
        _ftprintf(fp,_T("  hPane     = %p\r\n"),hPane);
        _ftprintf(fp,_T("  hBase     = %p\r\n"),hBase);
        _ftprintf(fp,_T("  hGrid     = %p\r\n"),hGrid);
        _ftprintf(fp,_T("  pGridData = %p\r\n"),pGridData);
        _ftprintf(fp,_T("  wParam    = %c\r\n"),wParam);
        #endif
        for(int i=0; i<MAX_CONNECTIONS; i++)
        {
            if(pGridData->pComObj->pISink[i])
            {
               pGridData->pComObj->pISink[i]->Grid_OnKeyPress((int)wParam, (int)lParam, pGridData->iEditedRow, pGridData->iEditedCol, &blnCancel);
               if(blnCancel)
                  return 0;
            }
        }
        if(wParam==VK_RETURN)
        {
           pGridData->pComObj->FlushData();
           pGridData->pComObj->Refresh();
           return 0;
        }
        else
           pGridData->hCtrlInCell=hEdit;
        #ifdef MYDEBUG
        _ftprintf(fp,_T("Leaving fnEditSubClass()\r\n"));
        #endif
        break;
     }
   case WM_KEYDOWN:
     {
        #ifdef MYDEBUG
        _ftprintf(fp,_T("Entering fnEditSubClass()  -- Case WM_KEYDOWN\n"));
        _ftprintf(fp,_T("  hEdit = %p\r\n"),hEdit);
        _ftprintf(fp,_T("  hCell = %p\r\n"),hCell);
        _ftprintf(fp,_T("  hPane = %p\r\n"),hPane);
        _ftprintf(fp,_T("  hBase = %p\r\n"),hBase);
        _ftprintf(fp,_T("  hGrid = %p\r\n"),hGrid);
        _ftprintf(fp,_T("  pGridData = %p\r\n"),pGridData);
        #endif
        for(int i=0; i<MAX_CONNECTIONS; i++)
        {
            if(pGridData->pComObj->pISink[i])
            {
               pGridData->pComObj->pISink[i]->Grid_OnKeyDown((int)wParam, (int)lParam, pGridData->iEditedCellRow, pGridData->iEditedRow, pGridData->iEditedCol, &blnCancel);
               if(blnCancel)
                  return 0;
            }
        }
        #ifdef MYDEBUG
        _ftprintf(fp,_T("Leaving fnEditSubClass()\r\n"));
        #endif
        break;
     }
   case WM_PASTE:
     {
        for(int i=0; i<MAX_CONNECTIONS; i++)
        {
            if(pGridData->pComObj->pISink[i])
            {
               pGridData->pComObj->pISink[i]->Grid_OnPaste(pGridData->iEditedCellRow, pGridData->iEditedRow, pGridData->iEditedCol);
               if(blnCancel)
                  return 0;
            }
        }
        break;
     }
   case WM_LBUTTONDBLCLK:
     {
        for(int i=0; i<MAX_CONNECTIONS; i++)
        {
            if(pGridData->pComObj->pISink[i])
            {
               pGridData->pComObj->pISink[i]->Grid_OnLButtonDblClk(pGridData->iEditedCellRow, pGridData->iEditedRow, pGridData->iEditedCol);
               if(blnCancel)
                  return 0;
            }
        }
        break;
     }

}

return CallWindowProc(fnEditWndProc,hEdit,msg,wParam,lParam);
}


BOOL CALLBACK EnumGridProc(HWND hWnd, LPARAM lParam)
{
#ifdef MYDEBUG
_ftprintf(fp,_T("  Called EnumGridProc() - %p\t%u\r\n"),hWnd,lParam);
#endif
if(GetClassLongPtr(hWnd,GCLP_WNDPROC)==lParam)
{
    #ifdef MYDEBUG
    _ftprintf(fp,_T("\r\n  Made A Match! - %p\t%u\r\n\r\n"),hWnd,lParam);
    #endif
    GridData* pGridData=(GridData*)GetWindowLongPtr(hWnd,0);
    pGridData->pComObj->FlushData();
}

return TRUE;
}


/*
' Offset     What's Stored There in WNDCLASSEX::cbWndExtra Bytes
' ==============================================================
' 0  -  3    pZStr, i.e., pointer to string data (ZStr) for cell      0  -  7
' 4  -  7    hFont, i.e., this was created in fnWndProc_OnCreate      8  -  15
' 8  - 11    dwColor, i.e., text color, and an RGB value             16  -  23
' 12 - 15    HBrush, and this is a HANDLE                            24  -  31
*/

LRESULT CALLBACK fnCellProc(HWND hCell, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
   case WM_CREATE:
     {
        SetWindowLongPtr(hCell,0,NULL);
        return 0;
     }
   case WM_LBUTTONDOWN:
     {
        int iCellBufferPos=0,blnFound=0,iGridMemOffset=0,iRow,iCol,i,j;
        HWND hGrid=NULL,hBase=NULL,hPane=NULL;
        GridData* pGridData=NULL;
        TCHAR* pZStr=NULL;
        SCROLLINFO si;
        HRESULT hr;
        #ifdef MYDEBUG
        _ftprintf(fp,_T("\r\nEntering fnCellProc()  --  case WM_LBUTTONDOWN\r\n"));
        #endif
        hPane=GetParent(hCell);
        hBase=GetParent(hPane);
        hGrid=GetParent(hBase);
        pGridData=(GridData*)GetWindowLongPtr(hPane,0);
        #ifdef MYDEBUG
        _ftprintf(fp,_T("  hCell              = %p\r\n"),hCell);
        _ftprintf(fp,_T("  hPane              = %p\r\n"),hPane);
        _ftprintf(fp,_T("  hBase              = %p\r\n"),hBase);
        _ftprintf(fp,_T("  hGrid              = %p\r\n"),hGrid);
        _ftprintf(fp,_T("  pGridData->hParent = %p\r\n"),pGridData->hParent);
        #endif
        EnumChildWindows(pGridData->hParent,EnumGridProc,(LPARAM)fnGridProc);
        //ZeroMemory(&si, sizeof(SCROLLINFO));
        memset(&si, 0, sizeof(SCROLLINFO));
        si.cbSize = sizeof(SCROLLINFO), si.fMask=SIF_POS;
        GetScrollInfo(hGrid,SB_VERT,&si);
        for(i=1; i<=pGridData->iVisibleRows; i++)
        {
            for(j=1; j<=pGridData->iCols; j++)
            {
                iCellBufferPos = dwIdx(i,j);
                if(pGridData->pCellHandles[iCellBufferPos]==hCell)
                {
                   iGridMemOffset = pGridData->iCols * (si.nPos -1) + iCellBufferPos;
                   pZStr=pGridData->pGridMemory[iGridMemOffset];
                   iRow=i, iCol=j;
                   pGridData->iEditedCellRow=iRow;
                   pGridData->iEditedRow=iRow+si.nPos-1;
                   pGridData->iEditedCol=iCol;
                   blnFound=TRUE;
                   goto Finished;
                }
          }
        }
        Finished:
        if(blnFound)
        {
           #ifdef MYDEBUG
           _ftprintf(fp,_T("  Found ZStr at %d\t%d\r\n"),i,j);
           _ftprintf(fp,_T("  si.nPos                = %u\r\n"),si.nPos);
           _ftprintf(fp,_T("  pZStr                  = %p\r\n"),pZStr);
           _ftprintf(fp,_T("  pZStr                  = %s\r\n"),pZStr);
           _ftprintf(fp,_T("  pGridData->hCtrlInCell = %p\r\n"),pGridData->hCtrlInCell);
           #endif
           pGridData->hCtrlInCell=pGridData->pCtrlHdls[iCol-1];
           SetParent(pGridData->hCtrlInCell,hCell);
           fnEditWndProc=(WNDPROC)SetWindowLongPtr(pGridData->hCtrlInCell,GWLP_WNDPROC,(LONG_PTR)fnEditSubClass);
           if(pGridData->hFont)
              SendMessage(pGridData->hCtrlInCell,WM_SETFONT,(WPARAM)pGridData->hFont,(LPARAM)TRUE);
           if(pGridData->pCellCtrlTypes[iCol-1]==GRID_CELL_CTRL_EDIT)
           {
              SetWindowPos(pGridData->hCtrlInCell,HWND_TOP,1,0,pGridData->pColWidths[iCol-1]-2,pGridData->iRowHeight,SWP_SHOWWINDOW);
              SetWindowText(pGridData->hCtrlInCell,pZStr);
              SetFocus(pGridData->hCtrlInCell);
              SendMessage(pGridData->hCtrlInCell,EM_SETSEL,(WPARAM)0,(LPARAM)-1);
           }
           if(pGridData->pCellCtrlTypes[iCol-1]==GRID_CELL_CTRL_COMBO)
           {
              SetWindowPos(pGridData->hCtrlInCell,HWND_TOP,1,0,pGridData->pColWidths[iCol-1]-2,180,SWP_SHOWWINDOW);
              SendMessage(pGridData->hCtrlInCell,CB_SETCURSEL,(WPARAM)-1,(LPARAM)0);
           }
           for(i=0; i<MAX_CONNECTIONS; i++)
           {
               if(pGridData->pComObj->pISink[i])
               {
                  pGridData->pComObj->pISink[i]->Grid_OnLButtonDown(iRow,pGridData->iEditedRow,iCol);
               }
           }
        }
        #ifdef MYDEBUG
        _ftprintf(fp,_T("Leaving fnCellProc()  --  case WM_LBUTTONDOWN\r\n\r\n"));
        #endif
        return 0;
     }
   case WM_PAINT:
     {
        #ifdef MYDEBUG
        _ftprintf(fp,_T("\r\nEntering fnCellProc()  --  case WM_PAINT\r\n"));
        #endif
        PAINTSTRUCT ps;
        HDC hDC=BeginPaint(hCell,&ps);
        TCHAR* pBuffer=(TCHAR*)GetWindowLongPtr(hCell,0);                   // 0
        HFONT hFont=(HFONT)GetWindowLongPtr(hCell,sizeof(void*));           // 8
        COLORREF dwColor=(COLORREF)GetWindowLongPtr(hCell,2*sizeof(void*)); // 16
        HBRUSH hBrush=(HBRUSH)GetWindowLongPtr(hCell,3*sizeof(void*));      // 24
        #ifdef MYDEBUG
        _ftprintf(fp,_T("  pBuffer = %p\t%s\r\n"),pBuffer,pBuffer);
        _ftprintf(fp,_T("  hBrush  = %p\r\n"),hBrush);
        #endif
        if(hBrush)                                           // had a tremendous amount of trouble here.
        {                                                    // if this block of code dealing with FillRect()
           RECT rc;                                          // is inside the if(pBuffer) if, it won't execute
           HBRUSH hTmpBr=(HBRUSH)SelectObject(hDC,hBrush);   // to change the backround color of a cell if
           GetClientRect(hCell,&rc);                         // there is no text in the memory pointed to by
           FillRect(hDC,&rc,hBrush);                         // pBuffer, i.e., it is null or zero.  In other
           SelectObject(hDC,hTmpBr);                         // words, if you try to change the color of grid
        }                                                    // cells where there is no text, the cell color
        if(pBuffer)                                          // won't change from white to your chosen color
        {                                                    // cuz pBuffer is null.  So you need the FillRect()
           HFONT hTmp=NULL;                                  // call out here - not inside the if(pBuffer)
           COLORREF dwTmpClr=0;
           if(hFont)
              hTmp=(HFONT)SelectObject(hDC,hFont);
           if(dwColor)
              dwTmpClr=SetTextColor(hDC,dwColor);
           /*
           if(hBrush)
           {
              RECT rc;
              HBRUSH hTmpBr=(HBRUSH)SelectObject(hDC,hBrush);
              GetClientRect(hCell,&rc);
              FillRect(hDC,&rc,hBrush);
              SelectObject(hDC,hTmpBr);
           }
           */
           int iBkMode=SetBkMode(hDC,TRANSPARENT);
           TextOut(hDC,1,0,pBuffer,_tcslen(pBuffer));
           if(hFont && hTmp)
              hFont=(HFONT)SelectObject(hDC,hTmp);
           if(dwColor)
              SetTextColor(hDC,dwTmpClr);
           SetBkMode(hDC,iBkMode);
        }
        EndPaint(hCell,&ps);
        #ifdef MYDEBUG
        _ftprintf(fp,_T("Leaving fnCellProc()  --  case WM_PAINT\r\n\r\n"));
        #endif
        return 0;
     }
}

return (DefWindowProc(hCell, msg, wParam, lParam));
}

Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 16, 2016, 07:14:56 PM
Then Wincode.h...


//WinCode.h
#ifndef WIN_CODE_H_INCLUDED
#define WIN_CODE_H_INCLUDED

#define GRID_CELL_CTRL_NONE         0
#define GRID_CELL_CTRL_EDIT         1
#define GRID_CELL_CTRL_COMBO        2
#define GRID_CELL_CTRL_CHECK        3
#define IDC_STATIC                  -1
#define MAX_COLORS                  15
#define ID_PANE                     1500
#define ID_HEADER                   1505
#define ID_CELL                     1600
#define IDC_EDIT                    1605
#define IDC_COMBO                   1705

#define dim(x) (sizeof(x) / sizeof(x[0]))
LRESULT CALLBACK fnGridProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK fnPaneProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK fnBaseProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK fnCellProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam);


struct                              WindowsEventArguments
{
HWND                               hWnd;
WPARAM                             wParam;
LPARAM                             lParam;
HINSTANCE                          hIns;
};

typedef WindowsEventArguments*      lpWndEventArgs;

long fnGridProc_OnCreate            (lpWndEventArgs Wea);
long fnGridProc_OnSize              (lpWndEventArgs Wea);
long fnGridProc_OnHScroll           (lpWndEventArgs Wea);
long fnGridProc_OnVScroll           (lpWndEventArgs Wea);
long fnGridProc_OnKeyDown           (lpWndEventArgs Wea);
long fnGridProc_OnCommand           (lpWndEventArgs Wea);
long fnGridProc_OnDestroy           (lpWndEventArgs Wea);

struct EVENTHANDLER
{
unsigned int                       iMsg;
long                               (*fnPtr)(lpWndEventArgs);
};

const EVENTHANDLER EventHandler[]=
{
{WM_CREATE,                        fnGridProc_OnCreate},
{WM_SIZE,                          fnGridProc_OnSize},
{WM_HSCROLL,                       fnGridProc_OnHScroll},
{WM_VSCROLL,                       fnGridProc_OnVScroll},
{WM_KEYDOWN,                       fnGridProc_OnKeyDown},
{WM_COMMAND,                       fnGridProc_OnCommand},
{WM_DESTROY,                       fnGridProc_OnDestroy}
};

#endif


Next Registry.cpp...


// cl Registry.cpp /O1 /Os /GS- /c
// Registry.cpp
#define   UNICODE
#define   _UNICODE
//#define   MYDEBUG
#include  <objbase.h>
#include  "string.h"
#include  "stdio.h"
#include  "tchar.h"
#include  "Registry.h"
const int CLSID_STRING_BUFFER_LENGTH = 39; //A CLSID Converted Needs 38 chars, i.e., {30000000-0000-0000-0000-000000000000}. The 39 number is for the terminating NULL.
#if defined MYDEBUG
extern FILE* fp;
#endif


BOOL SetKeyAndValue(const TCHAR* szKey, const TCHAR* szSubkey, const TCHAR* szValue)
{
TCHAR szKeyBuf[1024];
long lResult;
HKEY hKey;

#if defined MYDEBUG
_ftprintf(fp,_T("    Entering SetKeyAndValue()\r\n"));
_ftprintf(fp,_T("      szKey    = %s\r\n"),szKey);
_ftprintf(fp,_T("      szSubkey = %s\r\n"),szSubkey);
_ftprintf(fp,_T("      szValue  = %s\r\n"),szValue);
#endif
_tcscpy(szKeyBuf,szKey);
if(szSubkey!=NULL)          // Add subkey name to buffer.
{
     _tcscat(szKeyBuf, _T("\\"));
     _tcscat(szKeyBuf, szSubkey);
     #if defined MYDEBUG
     _ftprintf(fp,_T("      szKeyBuf = %s\r\n"),szKeyBuf);
     #endif
}
//Create and open key and subkey.
lResult=RegCreateKeyEx(HKEY_CLASSES_ROOT,szKeyBuf,0,NULL,REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,NULL,&hKey,NULL);
if(lResult!=ERROR_SUCCESS)
    return FALSE ;
if(szValue!=NULL)         //Set the Value.
    RegSetValueEx(hKey,NULL,0,REG_SZ,(BYTE*)szValue,(_tcslen(szValue)*sizeof(TCHAR))+sizeof(TCHAR));
RegCloseKey(hKey);
#if defined MYDEBUG
_ftprintf(fp,_T("    Leaving SetKeyAndValue()\r\n"));
#endif

return TRUE;
}


void CLSIDToTChar(const CLSID& clsid, TCHAR* szCLSID, int iStrlen)    // Convert a CLSID to a TCHAR string.
{
LPOLESTR wszCLSID=NULL;
size_t pRet=0;
HRESULT hr;

#if defined MYDEBUG
_ftprintf(fp,_T("    Entering CLSIDToTChar()\r\n"));
#endif
hr=StringFromCLSID(clsid,&wszCLSID);      // Get CLSID
#if defined MYDEBUG
_ftprintf(fp,_T("      iStrLen  = %d\r\n"),iStrlen);
_ftprintf(fp,   _T("      wszCLSID = %s\r\n"),wszCLSID);
#endif
if(SUCCEEDED(hr))
{
    #if defined UNICODE
        _tcscpy(szCLSID,wszCLSID);
        #if defined MYDEBUG
        _ftprintf(fp,_T("      szCLSID  = %s\r\n"),szCLSID);
        #endif
    #else
        //wcstombs_s(&pRet,szCLSID,iStrlen,wszCLSID,iStrlen); // Covert from wide characters to non-wide.
        WideCharToMultiByte(CP_ACP,0,wszCLSID,iStrLen,szCLSID,iStrLen,NULL,NULL);
        /*
        errno_t wcstombs_s
        (
          size_t *pReturnValue,      // [out] pReturnValue   The number of characters converted.
          char *mbstr,               // [out] mbstr          The address of a buffer for the resulting converted multibyte character string.
          size_t sizeInBytes,        // [in]sizeInBytes      The size in bytes of the mbstr buffer.
          const wchar_t *wcstr,      // [in] wcstr           Points to the wide character string to be converted.
          size_t count               // [in] count           The maximum number of bytes to be stored in the mbstr buffer, or _TRUNCATE.
        );
       
        int WideCharToMultiByte
        (
          UINT CodePage,         // code page
          DWORD dwFlags,         // performance and mapping flags
          LPCWSTR lpWideCharStr, // address of wide-character string
          int cchWideChar,       // number of characters in string
          LPSTR lpMultiByteStr,  // address of buffer for new string
          int cchMultiByte,      // size of buffer
          LPCSTR lpDefaultChar,  // address of default for unmappable characters
          LPBOOL lpUsedDefaultChar   // address of flag set when default char. used
         );
         
         int MultiByteToWideChar
         (
           UINT CodePage,         // code page
           DWORD dwFlags,         // character-type options
           LPCSTR lpMultiByteStr, // address of string to map
           int cchMultiByte,      // number of bytes in string
           LPWSTR lpWideCharStr,  // address of wide-character buffer
           int cchWideChar        // size of buffer
         );
         
         MultiByteToWideChar(CP_ACP,0,szBuffer,(int)strlen(szBuffer),szWide,511);
        */
        #if defined MYDEBUG
        _ftprintf(fp,  _T("      szCLSID  = %s\r\n"),szCLSID);
        _ftprintf(fp,  _T("      pRet     = %u\r\n"),pRet);
        #endif
    #endif
    CoTaskMemFree(wszCLSID);               // Free memory.
}
#if defined MYDEBUG
_ftprintf(fp,_T("    Leaving CLSIDToTChar()\r\n"));
#endif
}


LONG RecursiveDeleteKey(HKEY hKeyParent, const TCHAR* lpszKeyChild)       // Key to delete
{
TCHAR szBuffer[256];
DWORD dwSize=256 ;
HKEY hKeyChild;
FILETIME time;
LONG lRes;

lRes=RegOpenKeyEx(hKeyParent,lpszKeyChild,0,KEY_ALL_ACCESS,&hKeyChild); //Open the child.
if(lRes!=ERROR_SUCCESS)
    return lRes;
while(RegEnumKeyEx(hKeyChild,0,szBuffer,&dwSize,NULL,NULL,NULL,&time)==S_OK) //Enumerate all of the decendents of this child.
{
  lRes=RecursiveDeleteKey(hKeyChild,szBuffer);  //Delete the decendents of this child.
  if(lRes!=ERROR_SUCCESS)
  {
     RegCloseKey(hKeyChild);  //Cleanup before exiting.
     return lRes;
  }
  dwSize=256;
}
RegCloseKey(hKeyChild);      // Close the child.

return RegDeleteKey(hKeyParent,lpszKeyChild);  //Delete this child.
}


HRESULT RegisterServer(TCHAR* szModule, const CLSID& clsid, const TCHAR* szFriendlyName, const TCHAR* szVerIndProgID, const TCHAR* szProgID)
{
TCHAR szCLSID[CLSID_STRING_BUFFER_LENGTH];   // GetModuleFileName(hModule,szModule,sizeof(szModule)/sizeof(TCHAR))
TCHAR szKey[64];

#if defined MYDEBUG
_ftprintf(fp,_T("  Entering RegisterServer()\r\n"));
_ftprintf(fp,_T("    szFriendlyName = %s\r\n"),szFriendlyName);
_ftprintf(fp,_T("    szVerIndProgID = %s\r\n"),szVerIndProgID);
_ftprintf(fp,_T("    szProgID       = %s\r\n"),szProgID);
_ftprintf(fp,_T("    szModule       = %s\r\n"),szModule);
#endif
CLSIDToTChar(clsid, szCLSID, CLSID_STRING_BUFFER_LENGTH);            //Get server location &Convert the CLSID into a char.
//_tcscpy_s(szKey, _T("CLSID\\"));                                   //Build the key CLSID\\{...}
_tcscpy(szKey, _T("CLSID\\"));
//_tcscat_s(szKey,szCLSID);
_tcscat(szKey,szCLSID);
#if defined MYDEBUG
_ftprintf(fp,_T("    szCLSID        = %s\r\n"),szCLSID);
_ftprintf(fp,_T("    szKey   = %s\r\n"),szCLSID);
#endif
SetKeyAndValue(szKey,NULL,szFriendlyName);                           //Add the CLSID to the registry.
SetKeyAndValue(szKey, _T("InprocServer32"), szModule);               //Add the server filename subkey under the CLSID key.
SetKeyAndValue(szKey, _T("ProgID"), szProgID);                       //Add the ProgID subkey under the CLSID key.
SetKeyAndValue(szKey,_T("VersionIndependentProgID"),szVerIndProgID); //Add the version-independent ProgID subkey under CLSID key.
SetKeyAndValue(szVerIndProgID, NULL, szFriendlyName);                //Add the version-independent ProgID subkey under HKEY_CLASSES_ROOT.
SetKeyAndValue(szVerIndProgID, _T("CLSID"), szCLSID);
SetKeyAndValue(szVerIndProgID, _T("CurVer"), szProgID);
SetKeyAndValue(szProgID, NULL, szFriendlyName);                      //Add the versioned ProgID subkey under HKEY_CLASSES_ROOT.
SetKeyAndValue(szProgID, _T("CLSID"), szCLSID);
#if defined MYDEBUG
_ftprintf(fp,_T("  Leaving RegisterServer()\r\n"));
#endif

return S_OK ;
}


HRESULT UnregisterServer(const CLSID& clsid, const TCHAR* szVerIndProgID, const TCHAR* szProgID)
{
TCHAR szCLSID[CLSID_STRING_BUFFER_LENGTH];
TCHAR szKey[64];
LONG lResult;

#if defined MYDEBUG
_ftprintf(fp,  _T("  Entering UnregisterServer()\r\n"));
#endif
CLSIDToTChar(clsid, szCLSID,CLSID_STRING_BUFFER_LENGTH);                //Convert the CLSID into a char.
//_tcscpy_s(szKey, _T("CLSID\\"));                                      //Build the key CLSID\\{...}
_tcscpy(szKey,_T("CLSID\\"));
//_tcscat_s(szKey, szCLSID) ;
_tcscat(szKey,szCLSID);
lResult=RecursiveDeleteKey(HKEY_CLASSES_ROOT, szKey);                   //Delete the CLSID Key - CLSID\{...}
lResult=RecursiveDeleteKey(HKEY_CLASSES_ROOT, szVerIndProgID);          //Delete the version-independent ProgID Key.
lResult=RecursiveDeleteKey(HKEY_CLASSES_ROOT, szProgID) ;               //Delete the ProgID key.
#if defined MYDEBUG
_ftprintf(fp,  _T("  Leaving UnregisterServer()\r\n"));
#endif

return S_OK ;
}


And Registry.h...


BOOL SetKeyAndValue(const TCHAR* szKey, const TCHAR* szSubkey, const TCHAR* szValue);
void CLSIDToChar(const CLSID& clsid, TCHAR* szCLSID, int length);
LONG RecursiveDeleteKey(HKEY hKeyParent, const TCHAR* lpszKeyChild);
HRESULT RegisterServer(TCHAR* szModule, const CLSID& clsid, const TCHAR* szFriendlyName, const TCHAR* szVerIndProgID, const TCHAR* szProgID);
HRESULT UnregisterServer(const CLSID& clsid, const TCHAR* szVerIndProgID, const TCHAR* szProgID);

Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 16, 2016, 07:16:13 PM
And GStrings.cpp which is a special stripped down version of my String Class just for the grid...


// cl GStrings.cpp /O1 /Os /GS- /c
//GStrings.cpp
#define   UNICODE
#define   _UNICODE
#include  "windows.h"
#include  "stdlib.h"
#include  "stdio.h"
#include  "tchar.h"
//#include  <math.h>
#include  "string.h"
#include  "GStrings.h"


String::String()
{
lpBuffer=new TCHAR[MINIMUM_ALLOCATION];
lpBuffer[0]=_T('\0');
this->iCapacity=MINIMUM_ALLOCATION-1;
this->iLen=0;
}


String& String::operator=(const TCHAR ch)                  // Assigns a TCHAR to a String
{
this->lpBuffer[0]=ch, this->lpBuffer[1]=_T('\0');
this->iLen=1;
return *this;
}


String& String::operator=(const TCHAR* pStr)               // Assigns a TCHAR* to a String
{
size_t iNewLen=_tcslen(pStr);
if(iNewLen>this->iCapacity)
{
    delete [] this->lpBuffer;
    size_t iNewSize=(iNewLen*EXPANSION_FACTOR/16+1)*16;
    this->lpBuffer=new TCHAR[iNewSize];
    this->iCapacity=iNewSize-1;
}
_tcscpy(this->lpBuffer,pStr);
this->iLen=iNewLen;

return *this;
}


String& String::operator=(const String& strAnother)        // Assigns another String to a String
{
if(this==&strAnother)
    return *this;
if(strAnother.iLen>this->iCapacity)
{
    delete [] this->lpBuffer;
    SIZE_T iNewSize=(strAnother.iLen*EXPANSION_FACTOR/16+1)*16;
    this->lpBuffer=new TCHAR[iNewSize];
    this->iCapacity=iNewSize-1;
}
_tcscpy(this->lpBuffer,strAnother.lpBuffer);
this->iLen=strAnother.iLen;

return *this;
}


bool String::operator==(const TCHAR* pStr)                 // Compares Two Strings For Equality
{
if(_tcscmp(this->lpStr(),pStr)==0)
    return true;
else
    return false;
}


int String::ParseCount(const TCHAR c)                      //returns one more than # of
{                                                          //delimiters so it accurately
int iCtr=0;                                               //reflects # of strings delimited
TCHAR* p;                                                 //by delimiter.

p=this->lpBuffer;
while(*p)
{
  if(*p==c)
     iCtr++;
  p++;
}

return ++iCtr;
}


void String::Parse(String* pStr, TCHAR delimiter)
{
unsigned int i=0;
TCHAR* pBuffer=0;
TCHAR* c;
TCHAR* p;

pBuffer=new TCHAR[this->iLen+1];
if(pBuffer)
{
    pBuffer[0]=0, p=pBuffer;
    c=this->lpBuffer;
    while(*c)
    {
       if(*c==delimiter)
       {
          pStr[i]=pBuffer,  p=pBuffer;
          i++,              pBuffer[0]=0;
       }
       else
       {
          *p=*c,  p++;
          *p=0;
       }
       c++;
    }
    pStr[i]=pBuffer;
    delete [] pBuffer;
}
}


void String::LTrim()
{
size_t iCt=0;

for(size_t i=0; i<this->iLen; i++)
{
     if(this->lpBuffer[i]==32 || this->lpBuffer[i]==9)
        iCt++;
     else
        break;
}
if(iCt)
{
    for(size_t i=iCt; i<=this->iLen; i++)
        this->lpBuffer[i-iCt]=this->lpBuffer[i];
}
this->iLen=this->iLen-iCt;
}


int String::iVal()
{
return _ttoi(this->lpBuffer);  //_ttoi
}


SIZE_T String::Len(void)
{
return this->iLen;
}


TCHAR* String::lpStr()
{
return lpBuffer;
}


String::~String()   //String Destructor
{
delete [] lpBuffer;
lpBuffer=0;
}


And Gstrings.h...


//GStrings.h                                   // Seperate build of string class for grid
#ifndef Grid_Strings_h
#define Grid_Strings_h

#define EXPANSION_FACTOR      2
#define MINIMUM_ALLOCATION   16

class String
{
public:
String();                                     // Uninitialized Constructor
String& operator=(const TCHAR ch);            // Assigns TCHAR To String
String& operator=(const TCHAR* pStr);         // Assigns TCHAR* To String
String& operator=(const String& strAnother);  // Assigns Another 2nd String To String
bool operator==(const TCHAR* pStr);           //
int ParseCount(const TCHAR c);                // Returns One More Than # Of TCHAR Delimiters As Represented By c
void Parse(String* pStr, TCHAR delimiter);    // Returns In Parameter List Array Of Strings As Pointed To By This And Seperated By delimiter
void LTrim();                                 // Trims Spaces Or Tabs From Front Of String
int iVal();                                   //
SIZE_T Len(void);                             // Returns Length Of String In Number Of TCHARs
TCHAR* lpStr();                               // Returns TCHAR* To Start Of String
~String();                                    // String Destructor

private:
TCHAR* lpBuffer;
SIZE_T iLen;
SIZE_T iCapacity;
};

#endif  //#ifndef Grid_Strings_h


I believe that should be it.  Here is the command line compilation output for a command line build followed by an invocation of UPX to compact the 28 k file down to 18 k...




C:\Code\VStudio\Grids\x64>cl @Grid.txt
Microsoft (R) C/C++ Optimizing Compiler Version 15.00.21022.08 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

cl Server.cpp
   Grid.cpp
   WinCode.cpp
   Registry.cpp
   GStrings.cpp
   FHGrid.def

   /O1 /Os /GS- /GR- /FeFHGrid.dll /LD
   /link /DLL /nodefaultlib:libcmt.lib

   TCLib.lib
   FHGridRes.obj
   Kernel32.lib
   comctl32.lib
   User32.lib
   Gdi32.lib
   UUID.lib
   Advapi32.lib
   Ole32.lib
   OleAut32.lib

Server.cpp
Grid.cpp
WinCode.cpp
Registry.cpp
GStrings.cpp
Generating Code...
Microsoft (R) Incremental Linker Version 9.00.21022.08
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:FHGrid.dll
/dll
/implib:FHGrid.lib
/DLL
/nodefaultlib:libcmt.lib
/def:FHGrid.def
Server.obj
Grid.obj
WinCode.obj
Registry.obj
GStrings.obj
TCLib.lib
FHGridRes.obj
Kernel32.lib
comctl32.lib
User32.lib
Gdi32.lib
UUID.lib
Advapi32.lib
Ole32.lib
OleAut32.lib
   Creating library FHGrid.lib and object FHGrid.exp

C:\Code\VStudio\Grids\x64>UPX FHGrid.dll
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2013
UPX 3.91w       Markus Oberhumer, Laszlo Molnar & John Reiser   Sep 30th 2013

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
     28160 ->     17920   63.64%    win64/pe     FHGrid.dll

Packed 1 file.

C:\Code\VStudio\Grids\x64>


The above output was from my Visual Studio 2008 compiler.  Using Visual Studio's 2015 compiler it works and produces a file of the same size, but with a bunch of aggravating warnings like so...


Grid.cpp(666): warning C4311: 'type cast': pointer truncation from 'HBRUSH' to 'int'
Grid.cpp(668): warning C4311: 'type cast': pointer truncation from 'HBRUSH' to 'int'
Grid.cpp(670): warning C4312: 'type cast': conversion from 'int' to 'HBRUSH' of greater size
Grid.cpp(690): warning C4311: 'type cast': pointer truncation from 'HBRUSH' to 'int'
Grid.cpp(695): warning C4311: 'type cast': pointer truncation from 'HBRUSH' to 'int'
WinCode.cpp
WinCode.cpp(310): warning C4312: 'type cast': conversion from 'int' to 'HMENU' of greater size
WinCode.cpp(341): warning C4312: 'type cast': conversion from 'unsigned int' to 'HMENU' of greater size
WinCode.cpp(368): warning C4312: 'type cast': conversion from 'int' to 'HMENU' of greater size
WinCode.cpp(918): warning C4311: 'type cast': pointer truncation from 'HBRUSH' to 'int'


And I've completely 'had it' with C++ over this.  I hate strong typing.  Its completely useless in my opinion.  That stuff above gave me fits when I was converting the PowerBASIC version of my grid to C++ several years ago.  Here's the deal. 

I wanted the user of my grid to be able to specify colors individually for grid cells.  One of my most important apps at work uses this.  I have an app entitled 'SaleAdministration'.  Its how the Pennsylvania Bureau of Forestry personnel keep track of critical information about our timber sales.  We have usually around a hundred anf fifty timber sales per year and the contract on a timber sale can run from one year to several years.  There's a lot of legal stuff and data involved concerning dates, money, volumes, etc. 

There's a database table entitled 'Blocks'.  What that is is data for each cutting block of each timber sale.  A sale can have anywhere from one to 30 – 40 blocks or so.  The loggers pay for the timber by block.  A block usually has to be paid for before it is cut, and our Blocks table has fields for the date we received the check for a block payment, the date the logger finished the block, and the date the forester finished his/her inspection of it and found everything in order.  I color coded those things in the grid using red, yellow, and green so as to easily show at a quick glance which blocks have been paid for, which haven't, and which have been finished and finalized.  Green all the way across the grid indicates the block has been fully paid for, harvested, and approved.  All red indicates a block hasn't been paid for yet or cut.  Green across to the date paid indicates that the logger paid for the block, and if yellow continues from there it means it hasn't been finished yet, i.e., the logger is still working on it.

To accommodate this in the constructor of the grid I allocate a memory block to be used as an array of HBRUSHs to be created when the user specifies a color for a cell.  At the outset there aren't any HBRUSHs created, but for each color the user wishes the code first checks the created colors to see if an HBRUSH has been created for that color.  If it has, the app re-uses that HBRUSH.  If not, it creates the HBRUSH, stores it in the HBRUSH buffer, and increments the count of created HBRUSHs stored at offset zero in the HBRUSH buffer.  So in other words, I'm storing a simple count such as 1, 2, 3, 5, etc., at offset zero in a buffer typed as containing HBRUSHs, and I'm storing the actual HBRUSHs in array elements 1 through however many slots I allocate, which is a user configurable number.  This is all very convenient, because in destroying the grid I get the count of HBRUSHs created at offset zero, and I call DestroyObject() on all the HBRUSHs in a for loop. 

PowerBASIC has no trouble with this because its not a strongly typed language.  An integral quantity is an integral quantity, is an integral quantity.  A DWORD, a HANDLE, a HWND, an HFONT – they're all the same to PowerBASIC, i.e., integral quantities.  But not so with C++!  It sees me trying to put an int or size_t into a memory location where HBRUSHs are supposed to go.  As far as C++ is concerned they are completely different things and can't be confounded together!  And its stubbornly determined to not let me do it, or at least complain to the high heavens.  And I'm just as stubbornly determined to do it that way because to me an integer is an integer is an integer!  When I wrote the C++ code I had to make it much more convoluted and involved than in the PowerBASIC code to even get it to compile.  But I finally got it to compile and link with no errors or warnings in Visual Studio 2008.  But on my Visual Studio 2015 compile its giving me all those warnings again.  And I say to h*** with it!  I almost always fix warnings but as far as I'm concerned these can stay.  I've had it with them!  I'm not going to use the VC19 grid anyway, but rather my VC15 one.

So it seems to work OK.  So far havn't found any differences between the way it works and the way the one works compiled against the C Runtime.  Next I'll provide an x64 host app which uses the grid.  Its coded to use LoadLibrary/GetProcAddress to load the grid, so you don't even have to RegSvr32 the grid dll if you don't wat to.  I'll also provide the binaries for this and the grid so you don't have to create your own if you don't want to.  I realize it is quite a project.  Here is a demo that uses the grid...
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 16, 2016, 07:17:16 PM

// cl Client7.cpp Strings.cpp /O1 /Os /GS- /GR- /Zc:sizedDealloc- /link TCLib.lib kernel32.lib user32.lib gdi32.lib UUID.lib ole32.lib oleaut32.lib
// 11,776 bytes; 17,920 bytes grid
#define     UNICODE             // COM uses wide character OLE Strings.
#define     _UNICODE            // This one is for the C Runtime includes
#include    <windows.h>         // This code requires three other files of mine, i.e., Client5.h, Strings.h,
#include    <objbase.h>         // and Strings.cpp.  All the other includes are SDK includes.
#include    <ocidl.h>
#include    "stdio.h"
#include    "tchar.h"
#include    "Client6.h"
#include    "Strings.h"
const CLSID CLSID_FHGrid        =  {0x30000000, 0x0000, 0x0000, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}};
const IID   IID_IFHGrid         =  {0x30000000, 0x0000, 0x0000, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01}};
const IID   IID_IFHGridEvents   =  {0x30000000, 0x0000, 0x0000, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02}};
const IID   LIBID_FHGrid        =  {0x30000000, 0x0000, 0x0000, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03}};
extern "C"  int _fltused        =  1;


int _cdecl _purecall(void)   // Won't link against TCLib.lib without this useless function!
{
return 0;
}


interface IGrid : IUnknown
{
virtual HRESULT STDMETHODCALLTYPE CreateGrid          (HWND hParent, BSTR strSetup, int x, int y, int cx, int cy, int iRows, int iCols, int iRowHt, int iSelBkClr, int iSelTxtClr, BSTR strFntNme, int iFntSz, int iFntWt) = 0;
virtual HRESULT STDMETHODCALLTYPE SetRowCount         (int iRowCount, int blnForce) = 0;
virtual HRESULT STDMETHODCALLTYPE GetRowCount         (int* pRowCount) = 0;
virtual HRESULT STDMETHODCALLTYPE SetData             (int iRow, int  iCol, BSTR strData) = 0;
virtual HRESULT STDMETHODCALLTYPE GetData             (int iRow, int iCol, BSTR* strData) = 0;
virtual HRESULT STDMETHODCALLTYPE FlushData           (void) = 0;
virtual HRESULT STDMETHODCALLTYPE Refresh             (void) = 0;
virtual HRESULT STDMETHODCALLTYPE GetVisibleRows      (int* pVisibleRows) = 0;
virtual HRESULT STDMETHODCALLTYPE GethGrid            (HWND* hWnd) = 0;
virtual HRESULT STDMETHODCALLTYPE GethCell            (int iRow, int iCol, HWND* hCell) = 0;
virtual HRESULT STDMETHODCALLTYPE GethComboBox        (int iCol, HWND* hCombo) = 0;
virtual HRESULT STDMETHODCALLTYPE SetCellAttributes   (int iRow, int iCol, int iBackColor, int iTextColor) = 0;
virtual HRESULT STDMETHODCALLTYPE DeleteRow           (int iRow) = 0;
};


interface IGridEvents : IUnknown
{
virtual HRESULT STDMETHODCALLTYPE Grid_OnKeyPress     (int iKeyCode, int iKeyData, int iRow, int iCol, int* blnCancel) = 0;
virtual HRESULT STDMETHODCALLTYPE Grid_OnKeyDown      (int KeyCode, int iKeyData, int iCellRow, int iGridRow, int iCol, int* blnCancel) = 0;
virtual HRESULT STDMETHODCALLTYPE Grid_OnLButtonDown  (int iCellRow, int iGridRow, int iCol) = 0;
virtual HRESULT STDMETHODCALLTYPE Grid_OnLButtonDblClk(int iCellRow, int iGridRow, int iCol) = 0;
virtual HRESULT STDMETHODCALLTYPE Grid_OnPaste        (int iCellRow, int iGridRow, int iCol) = 0;
virtual HRESULT STDMETHODCALLTYPE Grid_OnRowSelection (int iRow, int iAction) = 0;
virtual HRESULT STDMETHODCALLTYPE Grid_OnDelete       (int iRow) = 0;
};


class CSink : public IGridEvents                        //CSink
{
public:
CSink();
~CSink();
HRESULT STDMETHODCALLTYPE QueryInterface               (REFIID iid, void** ppv);
ULONG   STDMETHODCALLTYPE AddRef                       ();
ULONG   STDMETHODCALLTYPE Release                      ();
HRESULT STDMETHODCALLTYPE Grid_OnKeyPress              (int iKeyCode, int iKeyData, int iRow, int iCol, int* blnCancel);
HRESULT STDMETHODCALLTYPE Grid_OnKeyDown               (int KeyCode, int iKeyData, int iCellRow, int iGridRow, int iCol, int* blnCancel);
HRESULT STDMETHODCALLTYPE Grid_OnLButtonDown           (int CellRow, int GridRow, int Col);
HRESULT STDMETHODCALLTYPE Grid_OnLButtonDblClk         (int CellRow, int GridRow, int Col);
HRESULT STDMETHODCALLTYPE Grid_OnPaste                 (int CellRow, int GridRow, int Col);
HRESULT STDMETHODCALLTYPE Grid_OnRowSelection          (int iRow, int iAction);
HRESULT STDMETHODCALLTYPE Grid_OnDelete                (int iRow);

private:
HWND m_hWnd;
long m_cRef;
};


CSink::CSink() : m_cRef(0), m_hWnd(0)
{
this->m_hWnd = FindWindow(_T("64 Bit Grid"),_T("64 Bit Grid ActiveX Control"));
}


CSink::~CSink()
{
//_tprintf(_T("      Entering CSink Destructor!\n"));
}


ULONG CSink::AddRef()
{
++this->m_cRef;
return m_cRef;
}


ULONG CSink::Release()
{
if(--m_cRef != 0)
    return m_cRef;
else
    delete this;

return 0;
}


HRESULT CSink::QueryInterface(REFIID riid, void** ppv)
{
if(riid == IID_IUnknown)
    *ppv = (IUnknown*)this;
else if(riid == IID_IFHGridEvents)
    *ppv = (IGridEvents*)this;
else
{
    *ppv = NULL;
    return E_NOINTERFACE;
}
AddRef();

return S_OK;
}


HRESULT CSink::Grid_OnKeyPress(int KeyCode, int KeyData, int Row, int Col, int* blnCancel)
{
//_tprintf(_T("\nEntering CSink::Grid_OnKeyPress()\n"));
return S_OK;
}


HRESULT CSink::Grid_OnKeyDown(int KeyCode, int iKeyData, int iCellRow, int iGridRow, int iCol, int* blnCancel)
{
//_tprintf(_T("\nEntering CSink::Grid_OnKeyDown()\n"));
return S_OK;
}


HRESULT CSink::Grid_OnLButtonDown(int iCellRow, int iGridRow, int iCol)
{
//_tprintf(_T("\nEntering CSink::Grid_OnLButtonDown()\n"));
return S_OK;
}


HRESULT CSink::Grid_OnLButtonDblClk(int iCellRow, int iGridRow, int iCol)
{
return S_OK;
}


HRESULT CSink::Grid_OnPaste(int iCellRow, int iGridRow, int iCol)
{
return S_OK;
}


HRESULT CSink::Grid_OnRowSelection(int iRow, int iAction)
{
if(this->m_hWnd)
{
    if(iAction)
       SetWindowLongPtr(this->m_hWnd,2*sizeof_ptr,(LONG_PTR)iRow);
    else
       SetWindowLongPtr(this->m_hWnd,2*sizeof_ptr,(LONG_PTR)iAction);
}

return S_OK;
}


HRESULT CSink::Grid_OnDelete(int iRow)
{
IGrid* pGrid=(IGrid*)GetWindowLongPtr(this->m_hWnd,0*sizeof_ptr);
if(pGrid)
{
    pGrid->DeleteRow(iRow);
    SetWindowLongPtr(this->m_hWnd,2*sizeof_ptr,(LONG_PTR)0);
}

return S_OK;
}


long fnWndProc_OnCreate(WndEventArgs& Wea)
{
IConnectionPointContainer* pConnectionPointContainer=NULL;
BSTR strSetup=NULL,strFontName=NULL,strCoordinate=NULL;
IConnectionPoint* pConnectionPoint=NULL;
IClassFactory* pClassFactory=NULL;
double dpiX,dpiY,rxRatio,ryRatio;
CSink* pSink=NULL;
IGrid* pGrid=NULL;
DWORD dwCookie=0;
String s1,s2,s3;
HWND hCtl=NULL;
int iGridRows;
HDC hDC=NULL;
HRESULT hr;

Wea.hIns=((LPCREATESTRUCT)Wea.lParam)->hInstance;
hDC = GetDC(NULL);
dpiX=GetDeviceCaps(hDC, LOGPIXELSX);
dpiY=GetDeviceCaps(hDC, LOGPIXELSY);
rxRatio=(dpiX/96);
ryRatio=(dpiY/96);
MoveWindow(Wea.hWnd,SizX(200),SizY(100),SizX(840),SizY(340),TRUE);
hr=CoInitialize(NULL);
if(SUCCEEDED(hr))
{
    hCtl=CreateWindow(_T("button"),_T("Get Cell (3,2) Data"),WS_CHILD|WS_VISIBLE,SizX(10),SizY(10),SizX(200),SizY(30),Wea.hWnd,(HMENU)IDC_GET_DATA,Wea.hIns,NULL);
    hCtl=CreateWindow(_T("button"),_T("Get Selected Row"),WS_CHILD|WS_VISIBLE,SizX(10),SizY(50),SizX(200),SizY(30),Wea.hWnd,(HMENU)IDC_GET_SELECTED_ROW,Wea.hIns,NULL);
    hCtl=CreateWindow(_T("button"),_T("Get Row Count"),WS_CHILD|WS_VISIBLE,SizX(10),SizY(90),SizX(200),SizY(30),Wea.hWnd,(HMENU)IDC_GET_ROW_COUNT,Wea.hIns,NULL);
    hCtl=CreateWindow(_T("button"),_T("Set Row Count"),WS_CHILD|WS_VISIBLE,SizX(10),SizY(130),SizX(200),SizY(30),Wea.hWnd,(HMENU)IDC_SET_ROW_COUNT,Wea.hIns,NULL);
    hCtl=CreateWindow(_T("button"),_T("Get hCell"),WS_CHILD|WS_VISIBLE,SizX(10),SizY(170),SizX(200),SizY(30),Wea.hWnd,(HMENU)IDC_GET_HCELL,Wea.hIns,NULL);
    hCtl=CreateWindow(_T("button"),_T("Color Some Rows"),WS_CHILD|WS_VISIBLE,SizX(10),SizY(210),SizX(200),SizY(30),Wea.hWnd,(HMENU)IDC_COLOR_SOME_ROWS,Wea.hIns,NULL);
    hCtl=CreateWindow(_T("button"),_T("Destroy Grid"),WS_CHILD|WS_VISIBLE,SizX(10),SizY(250),SizX(200),SizY(30),Wea.hWnd,(HMENU)IDC_UNLOAD_GRID,Wea.hIns,NULL);
    HRESULT (__stdcall* ptrDllGetClassObject) (const CLSID&, const IID&, void**);  // code for registry free com
    HMODULE hDll=NULL;
    hDll=LoadLibrary(L"FHGrid.dll");
    if(hDll)
    {
       ptrDllGetClassObject=(HRESULT (__stdcall*)(REFCLSID, REFIID, void**))GetProcAddress(hDll,"DllGetClassObject");
       hr=ptrDllGetClassObject(CLSID_FHGrid,IID_IClassFactory,(void**)&pClassFactory); 
       if(SUCCEEDED(hr))
       {     
          hr=pClassFactory->CreateInstance(NULL,IID_IFHGrid,(void**)&pGrid);
          if(SUCCEEDED(hr))
          {
             SetWindowLongPtr(Wea.hWnd,0*sizeof_ptr,(LONG_PTR)pGrid);
             strSetup   = SysAllocString(L"110:Column 1:^:edit,110:Column 2:^:edit,110:Column 3:^:edit,110:Column 4:^:edit,110:Column 5:^:combo");
             strFontName= SysAllocString(L"Times New Roman");
             hr=pGrid->CreateGrid(Wea.hWnd,strSetup,SizX(250),SizY(10),SizX(570),SizY(273),NUMBER_OF_ROWS,NUMBER_OF_COLUMNS,SizY(28),0,0,strFontName,18,FW_DONTCARE);
             if(SUCCEEDED(hr))
             {
                hr=pGrid->QueryInterface(IID_IConnectionPointContainer,(void**)&pConnectionPointContainer);
                if(SUCCEEDED(hr))
                {
                   hr = pConnectionPointContainer->FindConnectionPoint(IID_IFHGridEvents, &pConnectionPoint);
                   if(SUCCEEDED(hr))
                   {
                      pSink=new CSink;
                      if(pSink)
                      {
                         hr=pConnectionPoint->Advise((IUnknown*)pSink, &dwCookie);
                         if(SUCCEEDED(hr))
                         {
                            SetWindowLongPtr(Wea.hWnd,1*sizeof_ptr,(long)dwCookie);
                            for(unsigned int i=1; i<=NUMBER_OF_ROWS; i++)
                            {
                                s1=i;
                                for(unsigned int j=1; j<=NUMBER_OF_COLUMNS; j++)
                                {
                                    s2=j;
                                    s3=s1+_T(',')+s2;
                                    strCoordinate=SysAllocString(s3.lpStr());
                                    pGrid->SetData(i,j,strCoordinate);
                                    SysFreeString(strCoordinate);
                                }
                            }
                            hCtl=0;
                            hr=pGrid->GethComboBox(5,&hCtl);
                            if(SUCCEEDED(hr))
                            {
                               s1=L"Frederick", SendMessage(hCtl,CB_INSERTSTRING,-1,(LPARAM)s1.lpStr());
                               s1=L"Elsie",     SendMessage(hCtl,CB_INSERTSTRING,-1,(LPARAM)s1.lpStr());
                               s1=L"Scott",     SendMessage(hCtl,CB_INSERTSTRING,-1,(LPARAM)s1.lpStr());
                               s1=L"Lorrie",    SendMessage(hCtl,CB_INSERTSTRING,-1,(LPARAM)s1.lpStr());
                               s1=L"Joseph",    SendMessage(hCtl,CB_INSERTSTRING,-1,(LPARAM)s1.lpStr());
                               s1=L"Frank",     SendMessage(hCtl,CB_INSERTSTRING,-1,(LPARAM)s1.lpStr());
                            }
                            pGrid->Refresh();
                         }
                      }
                      pConnectionPoint->Release();
                   }
                   pConnectionPointContainer->Release();
                }
             }
             SysFreeString(strSetup);
             SysFreeString(strFontName);
          }
       }
       pClassFactory->Release();
    }
}

return 0;
}


void DestroyGrid(HWND hHost)
{
IConnectionPoint* pConnectionPoint=NULL;
DWORD dwCookie=123456;

IGrid* pGrid=(IGrid*)GetWindowLongPtr(hHost,0);
if(pGrid)
{
    dwCookie=(DWORD)GetWindowLongPtr(hHost,1*sizeof_ptr);
    HRESULT hr=pGrid->QueryInterface(IID_IConnectionPoint,(void**)&pConnectionPoint);
    if(SUCCEEDED(hr))
    {
       hr=pConnectionPoint->Unadvise(dwCookie);
       pConnectionPoint->Release();
    }
    pGrid->Release();
    SetWindowLongPtr(hHost,0,NULL);
}
}


long fnWndProc_OnCommand(WndEventArgs& Wea)
{
switch(LOWORD(Wea.wParam))
{
    case IDC_GET_DATA:
      {
         IGrid* pGrid=NULL;
         BSTR strData=SysAllocString(L"");
         pGrid=(IGrid*)GetWindowLongPtr(Wea.hWnd,0);
         if(pGrid)
         {
            pGrid->FlushData();
            HRESULT hr=pGrid->GetData(3,2,&strData);
            if(SUCCEEDED(hr))
            {
               String s1=_T("strData = ");
               s1=s1+strData;
               MessageBox(Wea.hWnd,s1.lpStr(),_T("pGrid->GetData()"),MB_OK);
            }
            SysFreeString(strData);
         }
         break;
      }
    case IDC_GET_SELECTED_ROW:
      {
         int iRow=(int)GetWindowLongPtr(Wea.hWnd,2*sizeof_ptr);
         if(iRow)
         {
            TCHAR szNumber[16],szBuffer[64];
            _stprintf(szNumber,_T("%d"),iRow);
            _tcscpy(szBuffer,_T("Row # "));
            _tcscat(szBuffer,szNumber);
            _tcscat(szBuffer,_T(" Has Been Selected!"));
            MessageBox(Wea.hWnd,szBuffer,_T("Selection Results"),MB_OK);
         }
         else
            MessageBox(Wea.hWnd,_T("No Row Is Presently Selected!"),_T("Selection Results"),MB_OK);
         break;
      }
    case IDC_GET_ROW_COUNT:
      {
         IGrid* pGrid=NULL;
         pGrid=(IGrid*)GetWindowLongPtr(Wea.hWnd,0);
         if(pGrid)
         {
            int iRowCount=0;
            HRESULT hr=pGrid->GetRowCount(&iRowCount);
            if(SUCCEEDED(hr))
            {
               TCHAR szBuffer[64],szNumber[16];
               _stprintf(szNumber,_T("%d"),iRowCount);
               _tcscpy(szBuffer,_T("The Grid Has "));
               _tcscat(szBuffer,szNumber);
               _tcscat(szBuffer,_T(" Row In It."));
               MessageBox(Wea.hWnd,szBuffer,_T("Row Count Report"),MB_OK);
            }
         }
         break;
      }
    case IDC_SET_ROW_COUNT:
      {
         IGrid* pGrid=NULL;
         pGrid=(IGrid*)GetWindowLongPtr(Wea.hWnd,0);
         if(pGrid)
         {
            HRESULT hr=pGrid->SetRowCount(25,TRUE);
            if(SUCCEEDED(hr))
            {
               String s1,s2,s3;
               BSTR strCoordinate=NULL;
               for(unsigned int i=1; i<=25; i++)
               {
                   s1=i;
                   for(unsigned int j=1; j<=NUMBER_OF_COLUMNS; j++)
                   {
                       s2=j;
                       s3=s1+_T(',')+s2;
                       strCoordinate=SysAllocString(s3.lpStr());
                       pGrid->SetData(i,j,strCoordinate);
                       SysFreeString(strCoordinate);
                   }
               }
               pGrid->Refresh();
               MessageBox(Wea.hWnd,_T("pGrid->SetRowCount() Succeeded!"),_T("Report"),MB_OK);
            }
         }
         break;
      }
    case IDC_GET_HCELL:
      {
         IGrid* pGrid=NULL;
         pGrid=(IGrid*)GetWindowLongPtr(Wea.hWnd,0);
         if(pGrid)
         {
            HWND hCell=0;
            HRESULT hr=pGrid->GethCell(3,2,&hCell);
            if(SUCCEEDED(hr))
            {
               TCHAR szBuffer[32];
               TCHAR szNumber[16];
               _tcscpy(szBuffer,(TCHAR*)_T("hCell (3,2) = "));
               _stprintf(szNumber,_T("%p"),hCell);
               _tcscat(szBuffer,szNumber);
               MessageBox(Wea.hWnd,szBuffer,_T("pGrid->GethCell(3,2)"),MB_OK);
            }
         }
         break;
      }
    case IDC_COLOR_SOME_ROWS:
      {
         IGrid* pGrid=NULL;
         pGrid=(IGrid*)GetWindowLongPtr(Wea.hWnd,0);
         if(pGrid)
         {
            pGrid->FlushData();
            for(int i=1; i<=NUMBER_OF_COLUMNS; i++)
                pGrid->SetCellAttributes(3,i,0xFF,0xFFFFFF);
            for(int i=1; i<=NUMBER_OF_COLUMNS; i++)
                pGrid->SetCellAttributes(4,i,0x00FF00,0xFFFFFF);
            for(int i=1; i<=NUMBER_OF_COLUMNS; i++)
                pGrid->SetCellAttributes(5,i,0x00FF0000,0x00FFFFFF);
            for(int i=1; i<=NUMBER_OF_COLUMNS; i++)
                pGrid->SetCellAttributes(6,i,0x00FFFF,0x00);
            for(int i=1; i<=NUMBER_OF_COLUMNS; i++)
                pGrid->SetCellAttributes(7,i,0xFFFF00,0x01);
            pGrid->Refresh();
         }
         break;
      }
    case IDC_UNLOAD_GRID:
      {
         DestroyGrid(Wea.hWnd);
         InvalidateRect(Wea.hWnd,NULL,TRUE);
         EnableWindow(GetDlgItem(Wea.hWnd,IDC_UNLOAD_GRID),FALSE);
         EnableWindow(GetDlgItem(Wea.hWnd,IDC_GET_DATA),FALSE);
         EnableWindow(GetDlgItem(Wea.hWnd,IDC_GET_SELECTED_ROW),FALSE);
         EnableWindow(GetDlgItem(Wea.hWnd,IDC_GET_ROW_COUNT),FALSE);
         EnableWindow(GetDlgItem(Wea.hWnd,IDC_SET_ROW_COUNT),FALSE);
         EnableWindow(GetDlgItem(Wea.hWnd,IDC_COLOR_SOME_ROWS),FALSE);
         EnableWindow(GetDlgItem(Wea.hWnd,IDC_GET_HCELL),FALSE);
         break;
      }
}

return 0;
}


long fnWndProc_OnDestroy(WndEventArgs& Wea)
{
DestroyGrid(Wea.hWnd);
PostQuitMessage(0);
return 0;
}


LRESULT CALLBACK WndProc(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 hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
TCHAR szClass[]=_T("64 Bit Grid");
WNDCLASSEX wc;
HWND hWnd;
MSG Msg;

SetProcessDPIAware();
wc.lpszClassName=szClass,                 wc.lpfnWndProc=WndProc,                   wc.cbSize=sizeof(WNDCLASSEX);
wc.style=0,                               wc.cbClsExtra=0,                          wc.cbWndExtra=3*sizeof_ptr;
wc.hInstance=hInstance,                   wc.hIcon=LoadIcon(NULL,IDI_APPLICATION),  wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground=(HBRUSH)COLOR_BTNSHADOW, wc.lpszMenuName=NULL,                     wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION);
RegisterClassEx(&wc);
hWnd=CreateWindow(szClass,_T("64 Bit Grid ActiveX Control"),WS_OVERLAPPEDWINDOW,0,0,0,0,NULL,NULL,hInstance,NULL);
ShowWindow(hWnd,nCmdShow);
while(GetMessage(&Msg, NULL, 0, 0))
{
    TranslateMessage(&Msg);
    DispatchMessage(&Msg);

CoFreeUnusedLibraries();
CoUninitialize();

return (int)Msg.wParam;
}


Here's what the demo does.  If you click the top button ...

'Get Cell (3, 2) Data'

... the app returns in a MessageBox the contents of Cell (3, 2).  You can edit any cell and change what it contains.  Also, you can drag the column dividers to adjust the column widths.

The next button...

'Get Selected Row'

...returns in a MessageBox the number of the row which you've selected.  You select a row by clicking the narrow little buttons that form the left margin of the grid.  When you do that the cells are colored in a user configurable color, but the default if you don't set it is kind of dark bluish.  You unselect a row by clicking the button again, so it acts like a toggle.  If you have the row selected and you click the button 'Get Selected Row', that number will be returned in a MessageBox like I said.

The next utton...

'Get Row Count'

...just returns the number of rows the grid presently is maintaining.  In this app I just used only 12.

The next button...

'Set Row Count'

...increases the number of rows to 25, and then fills the grid with the row/column data.  Next....

'Get hCell'

...returns the HWND of the cell at row/col you specify.  This can be useful. 

'Color Some Rows'

...colors several rows of the grid some bright colors.  You can specify this by row/col.  You don't have to do a whole row.

'Destroy Grid' does just that.  The grid will disappear from the screen but the app will keep running.  It may not be obvious, but you can delete rows.  Just select a row as previously described, and hit the [Del] key.  The data above the row, if any, is bumped up into the row you just deleted.  You can delete all the rows if you want.  The row doesn't actually get eliminated – simply the data in it and the rest bumps up.

The host Client7.exe comes to 11,776 bytes, and the grid to 17,920 bytes.  So this whole app comes in less than 30 k.  This amount of functionality any other way (except for this grid in PowerBASIC) would have cost hundreds and hundreds of k.

I think I'm done.  Unless anyone has any questions this is a finished project.  Hope someone finds it useful.

Just a few words of advice if you attempt to use it on any of your existing projects.  The 1st thing to do is go through all the code cross checking CRT function calls against those I have implemented in TCLib.  You can look at the TCLib.mak file for a full listing of CRT functions provided.  For example, in the file printf.cpp you'll find printf and wprintf. In strlen.cpp you'll find strlen() and wcslen(), etc.  Or you can just attempt a compile/link and look at the linker errors.  Linker errors occur when the compiler either finds the function and the implementation, or just the function prototype, and passes on that.  Then it's the linker's turn to attempt to find the implementation on any function the compiler 'passed on' because it found a prototype but no implementation.  The linker will search the libs or object files provided in the command line looking for an implementation of the function.  If it can't find a usable implementation various things can happen.  It may attempt to load the CRT looking for an implementation there.  If it does that it will immediately find name collisions with various functions in TCLib.lib.  At that point it may throw errors unrelated to the actual CRT function it is missing which caused it to load the CRT.  That will happen if you are unlucky.  If you are lucky it'll just tell you which CRT function it is missing, and then you'll have to decide what you want to do about it. 

Fred
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 16, 2016, 07:32:16 PM
The attached zip contains Client7.exe, FHGrid.dll, TCLib.lib and various important files I've just described in my last posts.  Client7.exe and FHGrid.dll can be executed if they are in the same directory, as they use Reg-Free COM.
Title: Re: Moving TCLib Into Mainstream
Post by: James C. Fuller on April 17, 2016, 02:58:01 PM
Fred,
  I assume your code is for both 32 and 64 bit?
If not then the __cdecl and  STDMETHODCALLTYPE are moot aren't they?
Isn't there only one 64bit calling convention: __fastcall?
I know it states (somewhere?) they are ignored by the compiler, are they still required by the api ?

James
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 18, 2016, 03:23:44 PM
Yes, I hadn't gotten around to compiling with x86 w with the grid yet, but on 64 bit as you mentioned those calling conventions are defined away as nothing.  But for a code base compilable as 32/64 I'll be keeping them in.  It doesn't hurt.  Not sure about the calling convention for 64 bit.  It sounds wierd to me though what I've read about it.

That reminds me of a nasty couple hours I spent when I was converting the grid code to C++ from PowerBASIC.  I was using an enumerator function somewhere, and I copied the code from my PowerBAIC grid.  I forgot to add the __stdcall to the C++ code so it was compiling as __cdecl.  My 64 bit was working perfectly, but I was having instant crashes when I tried to run a 32 bit version!  It surprised me because I figured 32 bit would be easier.  But I had forgotten to add the __stdcall or STDMETHODCLLTYPE, or whatever it was.  After a couple hours lost I discovered it.! :'(
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 18, 2016, 05:19:44 PM
For clarification, when I originally translated the grid from PowerBASIC to C++ I tested extensively both ways, that is, 32 bit and 64 bit.  I just hadn't gotten around to testing 32 bit with TCLib yet.  There is a define that needs to be set though, which I believe is in Server.cpp.  I believe the name is x64_GRID or something like that.  There was only one very small place that was used and it involved registering and unregistering the grid's type library in the typelib key of the Registry.  I believe there was a different equate used in on of the parameters of the call.  Other than that its one code base for 32/64 bit.
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 18, 2016, 07:35:47 PM
Thought I'd check out a 32 bit compile of the grid and it doesn't work.  Here are the errors...


WinCode.obj : error LNK2019: unresolved external symbol __ftol2 referenced in function "long __cdecl fnGridProc_OnCreate(structWindowsEventArguments *)" (?fnGridProc_OnCreate@@YAJPAUWindowsEventArguments@@@Z)
WinCode.obj : error LNK2019: unresolved external symbol __ftol2_sse referenced in function "long __cdecl fnGridProc_OnCreate(struct WindowsEventArguments *)" (?fnGridProc_OnCreate@@YAJPAUWindowsEventArguments@@@Z)
FHGrid.dll : fatal error LNK1120: 2 unresolved externals


I've discussed that issue fairly extensively.  In summary, 32 bit C++ compilers use a bunch of asm code in the crt source subdirectory of the compiler's include directory.  Its for casting and other types of floating point transformations.  For TCLib its in that WinCode_crt_math.cpp file, which gets compiled for 32 bit builds (take a look at it and get ready to become appalled).  Apparently it was triggered by my High DPI Aware code I added to the grid sometime after I created it when I learned how to do that.  That code uses floating point math.  Not sure what I'm going to do about this.  While 64 bit is my priority, I had hoped this would work in 32 bit.  I might have to go to Martins Mozeiko for help on this.

What I had actually started working on today was my template code for multi-dimensional arrays, and particularly  that part you had turned me onto James about zeroing out memory using new and returning a NULL pointer on failed new calls.  Some issues (solvable) with regard to TCLib on that.
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on April 18, 2016, 09:31:45 PM
I've diagnosed the cause of the linker errors and it is the High DPI Aware code.  Remming about 6 or 7 lines out allows the grid dll to compile.  I was amazed to see how much smaller it was than the 64 bit version, which came in 28 k and compacted to 18 k.  In 32 bit without the DPI code it compiled to 22016 bytes! and compacted to 15 k!

In the course of diagnosing this I compiled my Demo25.cpp (it scrolls the Demo25.cpp file in a GUI Window), and in 32 bit that compiled to 5 k!  Here is that...


// cl Demo25.cpp /O1 /Os /GS- /link TCLib.lib kernel32.lib user32.lib gdi32.lib
// 7,680 Bytes x64 Wide Character
#ifndef UNICODE
   #define UNICODE
#endif
#ifndef _UNICODE
   #define _UNICODE
#endif
#include <windows.h>
#include "string.h"
#include "stdio.h"
#include "tchar.h"
#define  BUFFER_SIZE  256
#define  dim(x) (sizeof(x) / sizeof(x[0]))

struct WndEventArgs
{
HWND                         hWnd;
WPARAM                       wParam;
LPARAM                       lParam;
HINSTANCE                    hIns;
};

long fnWndProc_OnCreate       (WndEventArgs& Wea);
long fnWndProc_OnSize         (WndEventArgs& Wea);
long fnWndProc_OnVScroll      (WndEventArgs& Wea);
long fnWndProc_OnHScroll      (WndEventArgs& Wea);
long fnWndProc_OnMouseWheel   (WndEventArgs& Wea);
long fnWndProc_OnPaint        (WndEventArgs& Wea);
long fnWndProc_OnDestroy      (WndEventArgs& Wea);

struct EVENTHANDLER
{
unsigned int                 iMsg;
long                         (*fnPtr)(WndEventArgs&);
};

const EVENTHANDLER EventHandler[]=
{
{WM_CREATE,                  fnWndProc_OnCreate},
{WM_SIZE,                    fnWndProc_OnSize},
{WM_VSCROLL,                 fnWndProc_OnVScroll},
{WM_HSCROLL,                 fnWndProc_OnHScroll},
{WM_MOUSEWHEEL,              fnWndProc_OnMouseWheel},
{WM_PAINT,                   fnWndProc_OnPaint},
{WM_DESTROY,                 fnWndProc_OnDestroy}
};

struct ScrollData
{
TCHAR**                      pPtrs;
int                          iNumLines;
int                          cxChar;
int                          cxCaps;
int                          cyChar;
int                          cxClient;
int                          cyClient;
int                          iMaxWidth;
};


long fnWndProc_OnCreate(WndEventArgs& Wea)
{
TCHAR szBuffer[BUFFER_SIZE];
ScrollData* pScrDta=NULL;
int iLen=0,iLof=0,i;
HANDLE hHeap=NULL;
TCHAR* cRet=NULL;
HFONT hFont=NULL;
FILE* fp=NULL;
TEXTMETRIC tm;
HDC hdc;

hHeap=GetProcessHeap();
pScrDta=(ScrollData*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,sizeof(ScrollData));
if(!pScrDta)
    return -1;
SetWindowLongPtr(Wea.hWnd,0,(LONG_PTR)pScrDta);
hdc = GetDC(Wea.hWnd);
hFont=CreateFont
(
   -1*(11*GetDeviceCaps(hdc,LOGPIXELSY))/72,
   0,
   0,
   0,
   FW_SEMIBOLD,
   0,
   0,
   0,
   ANSI_CHARSET,
   0,
   0,
   DEFAULT_QUALITY,
   0,
   (TCHAR*)_T("Lucida Console")
);
if(!hFont)
    return -1;
HFONT hTmp=(HFONT)SelectObject(hdc,hFont);
GetTextMetrics(hdc, &tm);
pScrDta->cxChar = tm.tmAveCharWidth;
pScrDta->cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * pScrDta->cxChar / 2;
pScrDta->cyChar = tm.tmHeight + tm.tmExternalLeading;
DeleteObject(SelectObject(hdc,hTmp));
ReleaseDC(Wea.hWnd, hdc);
fp=_tfopen(_T("Demo25.cpp"),_T("r"));
if(fp)
{
    pScrDta->iNumLines=Lof(fp);
    if(pScrDta->iNumLines)
    {
       pScrDta->pPtrs=(TCHAR**)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, sizeof(TCHAR*) * pScrDta->iNumLines);
       if(pScrDta->pPtrs)
       {
          for(i=0; i<pScrDta->iNumLines; i++)
          {
              _fgetts(szBuffer,BUFFER_SIZE,fp);
              iLen=_tcslen(szBuffer);
              if(iLen>pScrDta->iMaxWidth)
                 pScrDta->iMaxWidth=iLen;
              pScrDta->pPtrs[i]=(TCHAR*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY, sizeof(TCHAR)*iLen+1*sizeof(TCHAR));
              _tcscpy(pScrDta->pPtrs[i],szBuffer);
          }
          pScrDta->iMaxWidth=pScrDta->iMaxWidth*pScrDta->cxChar;
       }
    }
    fclose(fp);
}

return 0;
}


long fnWndProc_OnSize(WndEventArgs& Wea)
{
ScrollData* pScrDta=NULL;
SCROLLINFO si;

pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
if(pScrDta)
{
    pScrDta->cxClient = LOWORD(Wea.lParam);
    pScrDta->cyClient = HIWORD(Wea.lParam);
    si.cbSize = sizeof(si) ;
    si.fMask  = SIF_RANGE | SIF_PAGE;
    si.nMin   = 0;
    si.nMax   = pScrDta->iNumLines - 1;
    si.nPage  = pScrDta->cyClient / pScrDta->cyChar;
    SetScrollInfo(Wea.hWnd, SB_VERT, &si, TRUE);
    si.cbSize = sizeof(si);
    si.fMask  = SIF_RANGE | SIF_PAGE;
    si.nMin   = 0;
    si.nMax   = pScrDta->iMaxWidth / pScrDta->cxChar;
    si.nPage  = pScrDta->cxClient / pScrDta->cxChar;
    SetScrollInfo(Wea.hWnd, SB_HORZ, &si, TRUE);
}

return 0;
}


long fnWndProc_OnVScroll(WndEventArgs& Wea)
{
ScrollData* pScrDta=NULL;
SCROLLINFO si;

pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
if(pScrDta)
{
    si.cbSize = sizeof(si);    // Get all the vertial scroll bar information
    si.fMask  = SIF_ALL;
    GetScrollInfo(Wea.hWnd, SB_VERT, &si);
    int iVertPos = si.nPos;    // Save the position for comparison later on
    switch (LOWORD(Wea.wParam))
    {
      case SB_TOP:
           si.nPos = si.nMin;
           break;
      case SB_BOTTOM:
           si.nPos = si.nMax;
           break;
      case SB_LINEUP:
           si.nPos -= 1;
           break;
      case SB_LINEDOWN:
           si.nPos += 1;
           break;
      case SB_PAGEUP:
           si.nPos -= si.nPage;
           break;
      case SB_PAGEDOWN:
           si.nPos += si.nPage;
           break;
      case SB_THUMBTRACK:
           si.nPos = si.nTrackPos;
           break;
      default:
           break;
    }
    si.fMask = SIF_POS;
    SetScrollInfo(Wea.hWnd, SB_VERT, &si, TRUE);
    GetScrollInfo(Wea.hWnd, SB_VERT, &si);
    if(si.nPos != iVertPos)
    {
       ScrollWindow(Wea.hWnd, 0, pScrDta->cyChar*(iVertPos-si.nPos), NULL, NULL);
       UpdateWindow(Wea.hWnd);
    }
}

return 0;
}


long fnWndProc_OnHScroll(WndEventArgs& Wea)
{
ScrollData* pScrDta=NULL;
SCROLLINFO si;

pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
if(pScrDta)
{
    si.cbSize = sizeof (si);                // Get all the horizontal scroll bar information
    si.fMask  = SIF_ALL;
    GetScrollInfo(Wea.hWnd, SB_HORZ, &si);  // Save the position for comparison later on
    int iHorzPos = si.nPos;
    switch (LOWORD(Wea.wParam))
    {
      case SB_LINELEFT:
           si.nPos -= 1;
           break;
      case SB_LINERIGHT:
           si.nPos += 1;
           break;
      case SB_PAGELEFT:
           si.nPos -= si.nPage;
           break;
      case SB_PAGERIGHT:
           si.nPos += si.nPage;
           break;
      case SB_THUMBTRACK:              // case SB_THUMBPOSITION:
           si.nPos = si.nTrackPos;
           break;
      default:
           break;
    }
    si.fMask = SIF_POS;
    SetScrollInfo(Wea.hWnd, SB_HORZ, &si, TRUE);
    GetScrollInfo(Wea.hWnd, SB_HORZ, &si);
    if(si.nPos != iHorzPos)
       ScrollWindow(Wea.hWnd, pScrDta->cxChar*(iHorzPos-si.nPos), 0, NULL, NULL);
}

return 0;
}


long fnWndProc_OnPaint(WndEventArgs& Wea)
{
int x,y,iPaintBeg,iPaintEnd,iVertPos,iHorzPos;
ScrollData* pScrDta=NULL;
HFONT hFont=NULL;
PAINTSTRUCT ps;
SCROLLINFO si;
HDC hdc;

hdc = BeginPaint(Wea.hWnd, &ps);
pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
if(pScrDta)
{
    hFont=CreateFont
    (
      -1*(11*GetDeviceCaps(hdc,LOGPIXELSY))/72,
      0,
      0,
      0,
      FW_SEMIBOLD,
      0,
      0,
      0,
      ANSI_CHARSET,
      0,
      0,
      DEFAULT_QUALITY,
      0,
      (TCHAR*)_T("Lucida Console")
    );
    HFONT hTmp=(HFONT)SelectObject(hdc,hFont);
    si.cbSize = sizeof (si);  // Get vertical scroll bar position
    si.fMask  = SIF_POS;
    GetScrollInfo(Wea.hWnd, SB_VERT, &si), iVertPos = si.nPos;
    GetScrollInfo(Wea.hWnd, SB_HORZ, &si), iHorzPos = si.nPos;
    if(iVertPos+ps.rcPaint.top/pScrDta->cyChar>0)
       iPaintBeg=iVertPos + ps.rcPaint.top / pScrDta->cyChar;
    else
       iPaintBeg=0;
    if(iVertPos + ps.rcPaint.bottom / pScrDta->cyChar < pScrDta->iNumLines - 1)
       iPaintEnd=iVertPos + ps.rcPaint.bottom / pScrDta->cyChar;
    else
       iPaintEnd=pScrDta->iNumLines-1;
    for(int i = iPaintBeg; i<= iPaintEnd; i++)
    {
        x = pScrDta->cxChar * (1 - iHorzPos);
        y = pScrDta->cyChar * (i - iVertPos);
        TabbedTextOut(hdc, x, y, pScrDta->pPtrs[i], _tcslen(pScrDta->pPtrs[i]),0,NULL,0);
    }
    DeleteObject(SelectObject(hdc,hTmp));
}
EndPaint(Wea.hWnd, &ps);

return 0;
}


long fnWndProc_OnMouseWheel(WndEventArgs& Wea)
{
int zdelta=GET_WHEEL_DELTA_WPARAM(Wea.wParam);
if(zdelta>0)
{
    for(int i=0; i<10; i++)
        SendMessage(Wea.hWnd,WM_VSCROLL,MAKEWPARAM(SB_LINEUP,0),0);
}
else
{
    for(int i=0; i<10; i++)
        SendMessage(Wea.hWnd,WM_VSCROLL,MAKEWPARAM(SB_LINEDOWN,0),0);
}

return 0;
}


long fnWndProc_OnDestroy(WndEventArgs& Wea)
{
ScrollData* pScrDta=NULL;
HANDLE hHeap=NULL;

hHeap=GetProcessHeap();
pScrDta=(ScrollData*)GetWindowLongPtr(Wea.hWnd,0);
if(pScrDta->pPtrs)
{
    for(int i=0; i<pScrDta->iNumLines; i++)
        HeapFree(hHeap,0,pScrDta->pPtrs[i]);
    HeapFree(hHeap,0,pScrDta->pPtrs);
}
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("Scroll Demo");
WNDCLASSEX wc;
MSG messages;
HWND hWnd;

wc.lpszClassName = szClassName,                          wc.lpfnWndProc = fnWndProc;
wc.cbSize        = sizeof(WNDCLASSEX),                   wc.style       = CS_HREDRAW | CS_VREDRAW;
wc.hIcon         = LoadIcon(NULL,IDI_APPLICATION),       wc.hIconSm     = NULL;
wc.hInstance     = hInstance,                            wc.hCursor     = LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH),  wc.cbWndExtra  = sizeof(void*);
wc.lpszMenuName  = NULL,                                 wc.cbClsExtra  = 0;
RegisterClassEx(&wc);
hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,25,25,1140,1035,HWND_DESKTOP,0,hInstance,0);
ShowWindow(hWnd,iShow);
while(GetMessage(&messages,NULL,0,0))
{
    TranslateMessage(&messages);
    DispatchMessage(&messages);
}

return (int)messages.wParam;
}


These are the changes I made in WinCode.cpp of the grid to allow it to compile...

at top...


#define SizX(x)  x //    x  rxRatio
#define SizY(y)  y //    x  ryRatio


...and in long fnGridProc_OnCreate(lpWndEventArgs Wea)...


int dpiY=96;

// DPI Handling
//double dpiX, dpiY;
//double rxRatio, ryRatio;
hDC = GetDC(NULL);
//dpiX=GetDeviceCaps(hDC, LOGPIXELSX);
//dpiY=GetDeviceCaps(hDC, LOGPIXELSY);
//rxRatio=(dpiX/96);
//ryRatio=(dpiY/96);
#ifdef MYDEBUG
//_ftprintf(fp,_T("    dpiX                       = %6.2f\n"),dpiX);
//_ftprintf(fp,_T("    dpiY                       = %6.2f\n"),dpiY);
//_ftprintf(fp,_T("    rxRatio                    = %6.2f\n"),rxRatio);
//_ftprintf(fp,_T("    ryRatio                    = %6.2f\n"),ryRatio);
#endif
// DPI Handling End


I guess I really need to work on this and get it working because several of my work apps use this grid in 32 bit.
Title: Re: Moving TCLib Into Mainstream
Post by: James C. Fuller on May 13, 2016, 01:10:46 PM
Fred,
  What is the status of TCLib?
Having released UbxBasic I decided to explore minimalization (not a word:)) again.

James
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on May 13, 2016, 07:28:43 PM
That was about all I worked on for like 3 months.  Haven't touched it for several weeks now as I had to finally move to other stuff I had to do.  I believe there were a few details I didn't post about though.  Was having some difficulties getting my 32 bit code for my grid to compile and work, but I found the solution to that at Martins Mozeiko's post over on www.handmadehero.com.  Its an addition that is needed. 

Where I finally stopped was my attempt to get one of my big projects at work going with TCLib.  What stopped me was C Runtime pow() from Math.h.  I knew in the back of my mind I'd need that eventually, but forgot I used it in that particular app.  Actually, I don't know how to code that.  So what I did was download the GCC C Standard Library Source code, and at some point I hope to develop my own versions of that in C or asm.  But that's my long term goal.  Its not going to happen for quite some time - maybe like 6 months or a year.  I'm not that accomplished at ASM and I've never gotten into all the floating point stuff with the FPU, SSE2, and everything else.  Then there's the 64 bit issue to deal with in all that.  Any of the recent Visual Studio downloads have ml64 in the \bin folder. 

Some of my other long term plans with it are to eventually go through it function by function and see if anything can be optimized by ASM or otherwise.  So I have long term plans for it.  It all turned out too good for me not to want to use it.  I'm amazed I got as far as I did with it, and I owe a lot of thanks to Martins Mozeiko for his postings about it and help he gave me.

I'll be retiring in under a year and I've got to finish up on some documentation I'm now working on.  That's kind of why I had to lay it aside for a bit.  Let me look for that final piece of code though that's absolutely needed.  Back in a bit...   
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on May 13, 2016, 08:33:50 PM
Attached are Examples_x86.zip and Examples_x64.zip.  There is a file in both of them named Win32_crt_Float.cpp.  It needs to be added to the last TCLib.lib that you have Jim - of course changed to *.obj.

In it are _ftol2() and _ftol2_sse().  Its C inlined assembler code in the form of naked functions.  Its only compiled in x86 builds as you'll see in the conditional compilation statements in the code.

The lack of it was preventing my Grid from being built in x86 form.

I was astounded to see how small my grid built with TCLib.  Actually, I was astounded it built at all.  I'm a pessimist by nature I suppose (as well as a minimalist) and always assume that nothin 'ell work.  That way I'm not disappointed very much, and I get to be pleasantly surprised when something does work.  But it built to 22 k in x64 and compacted to 18 K.  Did even a lot better in x86 and compacts there to 16 k. 

In the x86 package are Client1.exe and the x86 version of FHGrid.dll.  They ought to run without registering the grid as Client1 uses Registry Free COM.  The registration code works very well though.  Its been thourougly tested on 2000, XP, Win7 and Win10 in both x86 and x64 where applicable.

In the x64 package are Client2.exe and the 64 bit version of the grid.  Everything I said above applies there too.

I believe if you simply provide that Win32_crt_Float.cpp file and update the TCLib.mak file with that listing you'll have the latest stuff I have.  Like I said I'd like to do more work on it in the future, but for now I'd best put it aside for awhile and work on other stuff that needs doing.
Title: Re: Moving TCLib Into Mainstream
Post by: James C. Fuller on May 13, 2016, 09:00:40 PM
Fred,
  Thanks for the update.
  I found the latest of my development of TCLib for use with bc9Basic and it seems pretty complete. I intend to stay with the old fprintf and use the WinApi File IO and the memory mapped file concept for line input.
I have the luxury with bc9Basic to take:
   Raw As fstring sFile("TEST1.TXT")
   Local As HANDLE hFile
   OpenFile sFile For Input As hFile
and translate it to
    fstring  sFile(_T("TEST.TXT"));
    HANDLE   hFile = {0};
    hFile = CreateFile( sFile.lpStr(), GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);

I don't need the 32bit stuff as I don't write 32bit code any more.
While your grid is impressive, c++ COM is a bit more than my old brain can take at the moment :)

I will post some code in the bc9Basic section that uses TCLib.
I have one small gui that was reduced to 7k from 87k.

James
Title: Re: Moving TCLib Into Mainstream
Post by: James C. Fuller on May 16, 2016, 03:06:26 PM
Fred,
  I cannot seem to find either the Lof / LineCount function in any of my files.

Edit:
Never mind I found Lof and renamed it to LineCount

James
Title: Re: Moving TCLib Into Mainstream
Post by: James C. Fuller on May 18, 2016, 12:07:43 PM
Fred,
  The link: http://www.handmadehero.com/
appears to be gone?

James
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on May 18, 2016, 06:14:25 PM
Wow!  I see.  Don't know what to make of it Jim.  It seemed to be mostly a gaming site.  I had first found it in trying to solve the horrendous difficulties I was having trying to get any floating point functionality without linking against the C Std. Lib.  That Martins Mozeiko is kind of like Jose is with PowerBASIC and COM/OLE - if it has anything to do with C, C++, or assembler, he knows about everything there is to know about it.  He's the man with the answers.  I don't know what us lesser mortals would do without folks like that. 

The site was to further a game that the owner of the site was developing.  He was developing it piecemeal and documenting everything online.  Casey M - something was his name - an advanced game developer I'm to understand.  But another thing I came to understand through this is that game developers seem to have a lot in common with a lot of PowerBASIC developers such as myself who strive to produce fast, small code with a minimum of dependencies. 

There was an interesting thread there about the pros/cons of OOP.  It seems a lot of game developers prefer C to C++, or at least C isms to C++ isms, for developing games.  What I found most interesting about that thread was the complete disorientation new C++ coders were having upon learning that the most advanced game programmers - including the head man of that site who was developing the demo game for learning purposes, used C instead of C++. 

Anyway, that doesn't look good.  I wish it would come back.
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on May 18, 2016, 06:20:53 PM
https://www.youtube.com/user/handmadeheroarchive
Title: Re: Moving TCLib Into Mainstream
Post by: James C. Fuller on May 19, 2016, 03:56:45 PM
Fred,
  Here is an alternative line input.
I could not get your latest FILE based IO to work but decided the MMF approach was a bit weird and large.

File must be opened with CreateFile using GENERIC_READ.

The file must be asci. The lineinput routine converts to wide for your String (my fstring)

You need a:
typedef String fstring;
typedef char _char;


James


int LineInput (HANDLE hFile, fstring&  sData)
{
    _char*   ReadBuffer = {0};
    DWORD    dwBytesRead = {0};
    DWORD    dwPtr = {0};
    long     where = {0};
    int      j = {0};
    ReadBuffer = new _char[ 1024];
    dwPtr = SetFilePointer( hFile, NULL, NULL, FILE_CURRENT);
    if(FALSE == ReadFile(hFile, ReadBuffer, 1024, &dwBytesRead, NULL))
    {
        return -3;
    }
    if(dwBytesRead == 0 )
    {
        return -1;
    }
    {
        int      i;
        for(i = 0; i < dwBytesRead; i++)
        {
            if(ReadBuffer[i] == 10 )
            {
                where = i;
                j = where;
                if(ReadBuffer[i - 1] == 13 )
                {
                    where = i + 1;
                    j = i - 1;
                }
                break;
            }
        }
    }

    SetFilePointer(hFile, dwPtr + where, NULL, FILE_BEGIN);
    ReadBuffer[j]  = 0;
    wchar_t*  wszTo = new wchar_t[j + 1];
    wszTo[j] = L'\0';
    MultiByteToWideChar(CP_ACP, 0, ReadBuffer, -1, wszTo, (int)j);
    sData = wszTo;
    delete[] wszTo;
    delete []ReadBuffer;
    return 0;
}

Title: Re: Moving TCLib Into Mainstream
Post by: James C. Fuller on May 20, 2016, 04:22:58 PM
Fred,
  How do you work with binary file data?
Do you use your String class or just a void* buffer?

James
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on May 24, 2016, 04:49:21 AM
Hello, Jim!

https://hero.handmade.network/

https://hero.handmade.network/forums/code-discussion/t/1083

https://hero.handmade.network/forums/code-discussion/t/209

This FILE business has been something of an important nuisance!  Originally I hadn't paid it much mind, but then you got me thinking about it, and at that point I had everything else pretty much working, so I tried to work it out.

I've been at C a pretty long time, and have always used and liked the C Runtime functions for dealing with text files, and the functions involved there were fopen, fclose, fscanf, fgets, rewind, and feof.

Now the C Runtime has more general purpose functions for dealing with files of any type, i.e., binary, random access, and text files such as fread and fwrite, but I just never used them that much.

I simply don't deal with files as binary streams that much, but I do deal with the special case of random access binary files a real lot.  As I mentioned above, the C Runtime has functions for that, i.e., fread and fwrite, but I've always tended to use the Windows functions for dealing with binary/random access files, i.e., ReadFile(), WriteFile(), SetFilePointer(), etc.

So I don't have anything worked out whatsoever with regards to my String Class and binary/random access files.  I have wrappers around ReadFile/WriteFile/SetFilePointer which I believe you've already seen which I've named Get/Put after BASIC language versions of the same.

So when it came time to attempt to look at the issue with regard to my TCLib work, my real big realization was that in my general day to day programming work I use the C Runtime functions fopen, fprintf, and fclose on a daily basis for all my debugging work.  Unlike a lot of other coders I hardly use debuggers, and I couldn't code and any real apps without my trusty debug output to log files.

In fooling around with it I discovered to my amazement that somehow a stdio FILE object and a Windows HANDLE were interchangeable, at least in the context of the fopen and CreateFile functions.  I'd really like to know more about that at some point.

So in my early efforts I was able to get fopen and fprintf working and I posted some code on that that I later changed.

Then you got me to thinking about the whole Line Input thing.  I'd always used C Runtime fgets for that.  Its not exactly like BASIC Line Input, but real close.  And I realized I really needed to have that too.  At first I was a bit confused about how to implement it, but it soon dawned on me that the only reasonable implementation would involve ReadFile() with the bytes to read parameter set to 1 for one byte at a time.  And the function would have to run in a loop testing for end of line or number of bytes to read.  So that's what I worked out and posted I believe.  I didn't want to name it Line Input but rather fgets and fgetws.  These are I believe reasonably good implementations of those (I've posted them before)...


//=====================================================================================
//               Developed As An Addition To Matt Pietrek's LibCTiny.lib
//                            By Fred Harris, April 2016
//
//                   cl fgets.cpp /GS- /c /W3 /DWIN32_LEAN_AND_MEAN
//=====================================================================================
#include <windows.h>
#include "stdlib.h"
#include "stdio.h"


char* __cdecl fgets(char* pBuffer, int iSize, FILE* fp)
{
bool blnAddCharToBuffer=true;
DWORD lpBytesRead=0;
int iCtr=0;
char c=0;

if(fp!=INVALID_HANDLE_VALUE)
{
    pBuffer[0]=0;
    do
    {
       ReadFile((HANDLE)fp,&c,1,&lpBytesRead,NULL);
       if(lpBytesRead==0)
       {
          if(iCtr)
          {
             pBuffer[iCtr]=10;
             pBuffer[iCtr+1]=0;
          }
          if(pBuffer[0])
             return pBuffer;
          else
             return NULL;
       }
       if(iCtr==iSize-1)
       {
          blnAddCharToBuffer=false;
          pBuffer[iCtr+0] = 10;
          pBuffer[iCtr+1] = 0;
       }
       if(c==13)
       {
          if(blnAddCharToBuffer)
          {
             pBuffer[iCtr]=10;
             iCtr++;
             pBuffer[iCtr]=0;
             blnAddCharToBuffer=false;
          }
       }
       if(blnAddCharToBuffer)
          pBuffer[iCtr]=c;
       if(c==10)
          break;
       iCtr++;

    }while(true);
}

return pBuffer;
}


wchar_t* __cdecl fgetws(wchar_t* pBuffer, int iSize, FILE* fp)
{
char* pAsciBuffer=NULL;
char* pRet=NULL;

memset(pBuffer,0,iSize*2);
pAsciBuffer=(char*)malloc(iSize);
if(!pAsciBuffer)
    return NULL;
memset(pAsciBuffer,0,iSize);
pRet=fgets(pAsciBuffer,iSize,fp);
if(pRet)
    MultiByteToWideChar(CP_ACP,0,pAsciBuffer,(int)strlen(pAsciBuffer),pBuffer,iSize);
free(pAsciBuffer);
if(pRet)
    return pBuffer;
else
    return NULL;
}


size_t __cdecl Lof(FILE* fp)
{
DWORD lpNumberOfBytesRead=0;
size_t iReadSomething=0;
size_t iCtr=0;
char c=0;

if(fp!=INVALID_HANDLE_VALUE)
{
    do
    {
       ReadFile((HANDLE)fp,&c,1,&lpNumberOfBytesRead,NULL);
       if(lpNumberOfBytesRead)
       {
          if(c==10)
          {  iReadSomething=0;
             iCtr++;
          }
          else
             iReadSomething=1;
       }
       else
          break;
    }while(true);
}
rewind(fp);

return iCtr+iReadSomething;
}


void __cdecl rewind(FILE* fp)
{
SetFilePointer((HANDLE)fp,0,NULL,FILE_BEGIN);
}


The only other suspects left in the line up are fprintf and fopen...


//=============================================================
//   Developed As An Addition To Matt Pietrek's LibCTiny.lib
//                By Fred Harris, March 2016
//
//  cl fprintf.cpp /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN   
//=============================================================
// cl sprintf.cpp /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdarg.h>
#include "stdio.h"
#define EOF (-1)
#pragma comment(linker, "/defaultlib:user32.lib")

int __cdecl fprintf(FILE* fp, const char* format, ...)
{
char szBuff[1024];
DWORD cbWritten;
va_list argptr;
int retValue;

va_start(argptr, format);
retValue = wvsprintfA(szBuff, format, argptr);
va_end(argptr);
WriteFile((HANDLE)fp, szBuff, retValue, &cbWritten, 0);

return retValue;
}

int __cdecl fwprintf(FILE* fp, const wchar_t* format, ...)
{
wchar_t szBuff[1024];
DWORD cbWritten;
va_list argptr;
int retValue;

va_start(argptr, format);
retValue = wvsprintfW(szBuff, format, argptr);
va_end(argptr);
WriteFile((HANDLE)fp, szBuff, retValue*2, &cbWritten, 0);

return retValue;
}


The above were changed from my initial postings of those functions.  And here is fopen...


//=============================================================
//   Developed As An Addition To Matt Pietrek's LibCTiny.lib
//                By Fred Harris, April 2016
//
//   cl fopen.cpp /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN   
//=============================================================
#include <windows.h>
#include "stdio.h"


FILE* fopen(const char* pszFile, const char* pszMode)
{
DWORD dwDesiredAccess;
HANDLE hFile=NULL;

if(*pszMode=='w')
    dwDesiredAccess=GENERIC_WRITE;
if(*pszMode=='r')
    dwDesiredAccess=GENERIC_READ;
hFile=CreateFileA(pszFile,dwDesiredAccess,0,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
if(hFile==INVALID_HANDLE_VALUE)
    return NULL;

return (FILE*)hFile;
}


FILE* _wfopen(const wchar_t* pszFile, const wchar_t* pszMode)
{
DWORD dwDesiredAccess;
HANDLE hFile=NULL;

if(*pszMode==L'w')
    dwDesiredAccess=GENERIC_WRITE;
if(*pszMode==L'r')
    dwDesiredAccess=GENERIC_READ;
hFile=CreateFileW(pszFile,dwDesiredAccess,0,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
if(hFile==INVALID_HANDLE_VALUE)
    return NULL;

return (FILE*)hFile;
}


int fclose(FILE* fp)
{
return !CloseHandle((HANDLE)fp);
}


And they need stdio.h and tchar.h...


// stdio.h
#ifndef stdio_h
#define stdio_h

struct FILE
{
char*  _ptr;
int    _cnt;
char*  _base;
int    _flag;
int    _file;
int    _charbuf;
int    _bufsiz;
char*  _tmpfname;
};

extern "C" char     __cdecl getchar();

extern "C" FILE*    __cdecl fopen(const char* pszFile, const char* pszMode);
extern "C" FILE*    __cdecl _wfopen(const wchar_t* pszFile, const wchar_t* pszMode);

extern "C" char*    __cdecl fgets(char* pBuffer, int iSize, FILE* fp);
extern "C" wchar_t* __cdecl fgetws(wchar_t* pBuffer, int iSize, FILE* fp);

extern "C" int      __cdecl printf(const char* format, ...);
extern "C" int      __cdecl wprintf(const wchar_t* format, ...);

extern "C" int      __cdecl fprintf(FILE* fp, const char* format, ...);
extern "C" int      __cdecl fwprintf(FILE* fp, const wchar_t* format, ...);

extern "C" int      __cdecl sprintf(char* buffer, const char* format, ...);
extern "C" int      __cdecl swprintf(wchar_t* buffer, const wchar_t* format, ...);

extern "C" size_t   __cdecl FltToCh(char* p, double x, size_t iFldWthTChrs, int iDecPlaces, char cDecimalSeperator, bool blnRightJustified);
extern "C" size_t   __cdecl FltToWch(wchar_t* p, double x, size_t iFldWthTChrs, int iDecPlaces, wchar_t cDecimalSeperator, bool blnRightJustified);

extern "C" size_t   __cdecl Lof(FILE* fp);
extern "C" void     __cdecl rewind(FILE* fp);
extern "C" int      __cdecl fclose(FILE* fp);
#endif
   


// tchar.h
#ifndef tchar_h
   #define tchar_h
   #ifdef  _UNICODE
      typedef wchar_t     TCHAR;
      #define _T(x)       L## x
      #define _tmain      wmain
      #define _tWinMain   wWinMain
      #define _tfopen     _wfopen
      #define _fgetts     fgetws
      #define _tprintf    wprintf
      #define _ftprintf   fwprintf
      #define _stprintf   swprintf
      #define _tcslen     wcslen
      #define _tcscpy     wcscpy
      #define _tcscat     wcscat
      #define _tcsncpy    wcsncpy
      #define _tcscmp     wcscmp
      #define _tcsicmp    _wcsicmp
      #define _tcsncmp    wcsncmp
      #define _tcsnicmp   _wcsnicmp
      #define _tcsrev     _wcsrev
      #define FltToTch    FltToWch
      #define _ttol       _wtol
      #define _ttoi64     _wtoi64
   #else
      typedef char        TCHAR;
      #define _T(x)       x
      #define _tmain      main
      #define _tWinMain   WinMain
      #define _tfopen     fopen
      #define _fgetts     fgets
      #define _tprintf    printf
      #define _ftprintf   fprintf
      #define _stprintf   sprintf
      #define _tcslen     strlen
      #define _tcscpy     strcpy
      #define _tcscat     strcat
      #define _tcsncpy    strncpy
      #define _tcscmp     strcmp
      #define _tcsicmp    _stricmp
      #define _tcsncmp    strncmp
      #define _tcsnicmp   _strnicmp
      #define _tcsrev     _strrev
      #define FltToTch    FltToCh
      #define _ttol       atol
      #define _ttoi64     _atoi64
   #endif
#endif


The below three programs should be able to be compiled with TCLib or the C Standard Library.  They are Test1.cpp, Test2.cpp and Test3.cpp.  They work with this text file...

TestData.txt

Here Is Line #1
And Here Is Line #2
But Lets Not Forget Line #3
And Finally, Here Is Line #4


Below is Test1.cpp.  And in the above code I see I lapsed again into using Lof() for LineCount().  Try hard not to let that faul you up.  I'm sure its about as hard for BASIC programmers to deal with that name as it is for me to avoid using it because as far back as I can remember I've been writing Lof functions in C to count the number of lines in a text file, whereas in BASIC a function of that name counts the bytes in a file.  Anyway, in self-defense I put a rewind() call at the end of Lof() cuz I never learn and continue to waste piles of time by calling Lof to get the line count in a text file, then I forget to rewind the file cuz the file pointer is at EOF, then I try to read the file and I get NOTHING!  Then I spend an hour debugging fgets() trying to figure out why it won't read the file, only to finally figure out I forgot to set the file pointer back to the beginning of the file to read it!!!!  I don't know how many times I've done that.  Not in the same day or week of course, but months later one forgets details!  Anyway, this should read the file...


// cl Test1.cpp /O1 /Os /GS- /link TCLib.lib kernel32.lib
#include <windows.h>
#include "stdio.h"
#include "string.h"
#include "tchar.h"

int main()
{
TCHAR szBuffer[128];
TCHAR pCh=NULL;
FILE* fp=NULL;
size_t iLof=0;

fp=_tfopen(_T("TestData.txt"),_T("r"));
if(fp)
{
    _tprintf(_T("File Opened Successfully!\n"));
    iLof=Lof(fp);
    _tprintf(_T("iLof   = %Iu\n\n"),iLof);
    rewind(fp);
    for(size_t i=1; i<=iLof; i++)
    {
        _fgetts(szBuffer,128,fp);   
        _tprintf(_T("%s"),szBuffer);
    }
    fclose(fp);   
}
getchar();

return 0;
}

/*
File Opened Successfully!
iLof   = 4

Here Is Line #1
And Here Is Line #2
But Lets Not Forget Line #3
And Finally, Here Is Line #4
*/


Test2.cpp does the same thing but differently.  You can read and output the lines of text in the text file without knowing beforehand how many lines there are, because fgets returns NULL when it hits EOF...


// cl Test2.cpp /O1 /Os /GS- /link TCLib.lib kernel32.lib
#include <windows.h>
#include "stdio.h"
#include "string.h"
#include "tchar.h"

int main()
{
TCHAR szBuffer[128];
TCHAR* pCh=NULL;
FILE* fp=NULL;

fp=_tfopen(_T("TestData.txt"),_T("r"));
if(fp)
{
    _tprintf(_T("File Opened uccessfully!\n\n"));
    do
    {
      pCh=_fgetts(szBuffer,128,fp);
      if(!pCh)
         break;   
      _tprintf(_T("%s"),szBuffer);
    } while(true);
    fclose(fp);   
}
getchar();

return 0;
}


The C Standard Library fgets behaves similiarly.  As I said, any of these programs are standard C programs that are not dependent on TCLib.  Another aspect of fgets is that the middle parameter takes the number of characters to read from each line.  In Test3.cpp I have it set to only read 12 characters from each line...


// cl Test3.cpp /O1 /Os /GS- /link TCLib.lib kernel32.lib
#include <windows.h>
#include "stdio.h"
#include "string.h"
#include "tchar.h"

int main()
{
TCHAR szBuffer[128];
FILE* fp=NULL;
TCHAR pCh=NULL;
size_t iLof=0;

fp=_tfopen(_T("TestData.txt"),_T("r"));
if(fp)
{
    _tprintf(_T("File Opened uccessfully!\n"));
    iLof=Lof(fp);
    _tprintf(_T("iLof   = %Iu\n\n"),iLof);
    for(size_t i=1; i<=iLof; i++)
    {
        _fgetts(szBuffer,12,fp);   
        _tprintf(_T("%Iu\t%s"),_tcslen(szBuffer),szBuffer);
    }
    fclose(fp);   
}
getchar();

return 0;
}

/*
C:\Code\VStudio\VC++9\LibCTiny\fgets>test3
File Opened uccessfully!
iLof   = 4

12      Here Is Lin
12      And Here Is
12      But Lets No
12      And Finally
*/


So anyway, I believe that's the core of my TCLib interpretation of text file i/o.  I simply couldn't use your MMF work for this Jim.  I felt strongly that a byte by byte low level read of a file stream with ReadFile in a loop was the best interpretation of fgets, and I'd be surprised if that wasn't Bob Zale's underlying implementation of Line Input.  I do respect your work on MMF though, as that's something lacking in my background.  All I know about it is that MCM in the PowerBASIC forums loves it (MMF), and that its an important component of inter-process communication.  I don't usually study up on something until I see a need for it I guess.  I did try your MMF examples though, and was surprised it didn't add much overhead to the program.  And of course it did work.  Someday when I have time to tackle MMF I'll get back to it.

The only other possible implementation of Line Input would be scanf / fscanf looking for a character string, and most C or C++ coders including myself hold those functions in fairly low regard.  It could possibly be managed though.  But it wouldn't buy me anything with TCLib because I'd then have to implement scanf / fscanf, and that would bring me back to Win32 ReadFile().

After spending a few weeks on documentation I'm back at TCLib though.  At least temporarily.  I'm far from done with documentation.

What stopped me with TCLib though was, as I said, pow() from Math.h.  But I made a workaround for that.  I had a bunch of complicated regression equations for calculating the volume of pulpwood trees.  I was able to output a volume table though with the calculated volumes by diameter and height, and now I can read them into my program (using fgets(), by the way), and eliminate the need to raise doubles to exponents that are themselves doubles, thereby eliminating my need for pow().  In the fullness of time, as I said before, I want to figure out the code to do that, i.e., pow.  But for now I've gotten around the pow issue with my code.  After throwing the weekend plus today at it I'm down to these last remaing two unresolved externals, which are atof and wcstombs.

So I can deal with that.  I need to implement atof, and the 2nd one – wcstombs, can be removed with WideCharToMultiByte().  So I'm thinking in a couple days I might be able to get a major component of our Timber Sale System converted over to TCLib builds!

Sorry I took so long replying.  I didn't see your posts 'till today.  This past weekend I wasn't on the internet at all.  All I did was alternate working on code and practicing my 5 string banjo picking.  I practice a pretty lot, as one of my lifelong interests has always been bluegrass music.  I have yard work to do but all it ever does around here is rain so I can't seem to get that done.   I can't get my hedge cut no how.

 
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on May 24, 2016, 05:23:41 PM
If I wanted to implement Basic's Line Input in C/C++ I'd use fgets to do it.  The major difference between fgets and basic's Line Input is that basic's Line Input will return a string without a CR or LF at the end.  For example, this PowerBASIC Console Compiler program will return a length of 15 for the first string of my previously posted TestData.txt file...


#Compile Exe
#Dim All

Function PBMain() As Long
  Local strLine As String
  Local fp As Long

  fp=Freefile
  Open "TestData.txt" For Input As #fp
  Line Input #fp, strLine
  Console.Print strLine
  Console.Print "Len(strLine) = " Len(strLine)
  Close #fp
  Waitkey$

  PBMain=0
End Function

#if 0

Here Is Line #1
Len(strLine) =  15

#EndIf


Here's the same in C...


// cl CFGets.cpp /O1 /Os /GS- /link TCLib.lib kernel32.lib
#include <windows.h>
#include "stdio.h"
#include "string.h"

int main()
{
char szBuffer[128];
FILE* fp=NULL;

fp=fopen("TestData.txt","r");
if(fp)
{
    fgets(szBuffer,128,fp);   
    printf("%s",szBuffer);
    printf("strlen(szBuffer = %Iu\n",strlen(szBuffer));
    fclose(fp);   
}
getchar();

return 0;
}

#if 0

Here Is Line #1
strlen(szBuffer) = 16

#endif


...and as you can see, the output is the same but there is a newline at the end which gets counted in the string length making strlen return 16 instead of 15 as with PowerBASIC.  So it would be most desirable to not hack up C code by changing the implementation of fgets – which would screw up already existing and working C code, but to rather simply implement in C a LineInput function that preserves basic language behaviour, which in my humble opinion is better anyway.  Here would be one possible implementation of that which pulls in my String Class...


// cl LineInput.cpp Strings.cpp /O1 /Os /GS- /link TCLib.lib kernel32.lib
// 5120 bytes
#include <windows.h>
#include "stdio.h"
#include "string.h"
#include "Strings.h"

void LineInput(FILE* fp, String& strLn)
{
char szBuffer[2048];                 // Allocate Buffer

memset(szBuffer,0,2048);             // Zero Out Buffer
fgets(szBuffer,2048,fp);             // Read Line From fp, Which Will Be Terminated With CR Or LF + NULL
szBuffer[strlen(szBuffer)-1]=NULL;   // Replace CR Or LF With NULL To Shorten String By One Byte
strLn=szBuffer;                      // Assign To String
}

int main()
{
String strLine;
FILE* fp=NULL;

fp=fopen("TestData.txt","r");
if(fp)
{
    LineInput(fp,strLine);
    strLine.Print(true);
    printf("strLine.Len() = %Iu\n",strLine.Len());   
    fclose(fp);   
}
getchar();

return 0;
}


#if 0

Here Is Line #1
strLine.Len() = 15

#endif
 

So now we have it working like in PowerBASIC where the last character of the string, not counting the NULL, is a printable character rather than a Cr or Lf (I forget which).
Title: Re: Moving TCLib Into Mainstream
Post by: James C. Fuller on May 24, 2016, 06:15:38 PM
Fred,
  I've been busy on the File I/O front also.
As I mentioned I could not get your FILE approach to work with bc9 so I wrote my own.
I also have had a bad taste on the way BCX/bc9 handled Line Input. It translated:

OPEN a$ FOR INPUT AS fp1
WHILE NOT EOF(fp1)
  LINE INPUT fp1, a$
  ? a$
WEND

To This:


if((fp1=fopen(a,"r"))==0)
{
fprintf(stderr,"Can't open file %s\n",a);exit(1);
}
while(!EoF(fp1))
  {
    a[0]=0;
    fgets(a, 1048576,fp1);
    if(a[strlen(a)-1]==10)a[strlen(a)-1]=0;
    printf("%s\n",a);
  }


Notice the size of the buffer.

The bcx/bc9 translation of GET and PUT are macros which some compilers complain about because fread returns a value:

GET$   FP1,A$,6

#define GET(A,B,C)fread(B,1,C,A)
#define PUT(A,B,C)fwrite(B,1,C,A)

Your forray into TCLib and my subsequent investigations led me to rewrite File I/O for bc9.
I have the luxury to use anything I want in source form with bc9. I do not have to stuff it into a library.
The way I work UNICODE with the ULEX lexer I can write generic code and either ULEX it or not.

I wrote a specific LineInput routine for TCLib using your String (My fstring).
All file IO is for asci files only. There are no provisions for UNICODE files.
Input is converted to UNICODE for fstring.
fstring Output is converted to ansi before writing to file.
Files are opened with CreateFile
For now there is a 1024 byte ReadBuffer in the LineInput routine for TCLib.
Also note char is defined as _char so ULEX leaves it alone.

TCLib LineInput:

int LineInput (HANDLE hFile, fstring&  sData)
{
    _char*   ReadBuffer = {0};
    DWORD    dwBytesRead = {0};
    DWORD    dwPtr = {0};
    long     where = {0};
    int      j = {0};
    ReadBuffer = new _char[ 1024];
    dwPtr = SetFilePointer( hFile, NULL, NULL, FILE_CURRENT);
    if(FALSE == ReadFile(hFile, ReadBuffer, 1024, &dwBytesRead, NULL))
    {
        return -3;
    }
    if(dwBytesRead == 0 )
    {
        return -1;
    }
    {
        int      i;
        for(i = 0; i < dwBytesRead; i++)
        {
            if(ReadBuffer[i] == 10 )
            {
                where = i;
                j = where;
                if(ReadBuffer[i - 1] == 13 )
                {
                    where = i + 1;
                    j = i - 1;
                }
                break;
            }
        }
    }
    SetFilePointer(hFile, dwPtr + where, NULL, FILE_BEGIN);
    ReadBuffer[j]  = 0;
    wchar_t*  wszTo = new wchar_t[j + 1];
    wszTo[j] = L'\0';
    MultiByteToWideChar(CP_ACP, 0, ReadBuffer, -1, wszTo, (int)j);
    sData = wszTo;
    delete[] wszTo;
    delete [] ReadBuffer;
    return 0;
}


I hacked your original TCLib fprintf file to use HANDLE instead of FILE* but then
decided it was easier on my part to just use bc9 to spit out code so I added an fPrintS function for TCLib use:


int fPrintS(HANDLE hFile, fstring&  sBuffer)
{
    UINT     uLen = {0};
    DWORD    dwBytesWritten = {0};
    BOOL     bErrorFlag = {0};
    uLen = WideCharToMultiByte( CP_ACP, 0, sBuffer.lpStr(), - 1, 0, 0, 0, 0);
    _char*   szTo = new _char[uLen + 2];
    WideCharToMultiByte(CP_ACP, 0, sBuffer.lpStr(), -1, szTo, uLen, NULL, NULL);
    szTo[uLen - 1] = 13;
    szTo[uLen]  = 10;
    bErrorFlag = WriteFile( hFile, szTo, uLen + 1,  &dwBytesWritten, NULL);
    delete []szTo;
    return 0;
}



This is my replacement for GET$ -> FGET.
It uses _msize to determine the buffer size so the buffer must be allocated dynamically.
new,malloc, or calloc

DWORD FGet(HANDLE hFile, LPVOID  Buf, int bites)
{
    DWORD    dwBytesRead = {0};
    BOOL     retval = {0};
    if(_msize(Buf) < bites )
    {
        return 0;
    }
    retval = ReadFile( hFile, Buf, bites,  &dwBytesRead, NULL);
    return dwBytesRead;
}


And the PUT$ -> FPUT

BOOL FPut (HANDLE hFile, LPVOID  Buf, int bites)
{
    BOOL     reval = {0};
    DWORD    dwBytesWritten = {0};
    if(_msize(Buf) < bites )
    {
        return 0;
    }
    return WriteFile(hFile, Buf, bites, &dwBytesWritten, NULL);
}


I will post some bc9Basic examples and my updated TCLib along with a new version of bc9Basic in my area soon.


James
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on May 24, 2016, 08:54:16 PM
Ummm!  I see you are deep into it Jim! 

You know, I'm not sure about what to do about the size of the read buffer for things like fgets/Line Input.  If we're talking lines of text in a text file, which I imagine is the typical situation, we really don't need large numbers.  I'd think 256 or 512 would be plenty large enough.  Memory being what it is (practically infinite nowadays), larger numbers like 2048 or 4096 seem reasonable too.  But my TCLib is somewhat of a special circumstance I believe.  For that I'm more in favor of the smaller numbers.

I've worked on an implementation of atof/_wtof today.  Got it working.  Might tweak it some yet.  I just started out with my atol and added code to deal with the decimal point.  I'll post it in a bit.  I had more or less forgotten about that one.  I guess that's how its going to go.  Try to compile something different, and find out what's missing.  Then deal with it.
Title: Re: Moving TCLib Into Mainstream
Post by: James C. Fuller on May 27, 2016, 04:05:45 PM
Fred,
  I google atof source and it looked pretty daunting. I'm anxious to see your implementation.
Also, I have code for Unicode text file I/O but I'm not sure how useful it would be.
I'm waiting on feedback from Patrice http://www.jose.it-berater.org/smfforum/index.php?topic=5131.msg21909;topicseen#msg21909
but maybe you have some knowledge in this area?

James
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on May 27, 2016, 07:43:54 PM
atof was a piece of cake.  Don't have it handy, but I'll post it in a bit.  All I did was start with atol() and locate the decimal point, then keep deviding by 10 in a loop to get the decimal point where it should go. 

I finally succeeded in getting a major program at work called the TimberBeast to compile/link with TCLib.  However, the battle's far from won.  The program starts and runs OK, but I haven't tried to calculate any of our timber sales with it yet because it surely won't work.  The issue is again - floating point numbers.  This program has hundreds and hundreds of lines of printf statements all over the place with %f format specifiers of various complexity, such as %10.2f, etc.  That is perhaps the largest failing of my TCLib.  That's where the FltToCh.cpp file enters the picture, if you recall.

So the way I see it my options are two....

1) Fix printf and sprint to work exactly like the C Runtime;
2) Alter all my printf statements that use printf with %s format specifier to FltToCh usage.

I'll have to decide what to do.  Option #1 would be best but perhaps hardest mentally to figure out.  Option #2 is the drudgery no brainer option where you just doggedly keep at it till its done.

I don't know what to do about Unicode text files either.  Originally I coded my fgetws to read Unicode text files.  Then I read what you and Patrice were conversing about, and decided maybe best option was to only read ansi files with fgetws and convert to wide character.   Are many text files nowadays in Unicode?

On a somewhat related note I had quite a discussion with Martins Mozeiko over at www.handmadehero.com (when that address worked) about the whole Unicode thing and Microsoft's wchar_t and TCHARs.  He thinks its all a truck load of crap and everybody should be using UTF8. 

I can't really say I'm much up on character sets, but I did some little research on the issue and found out a few things that were very disconcerting to me.

For one thing, I thought this whole issue of a two byte character capable of handling all the world's languages and symbols was settled years ago.  I suppose I got that impression from reading Charles Petzold's last edition of "Programming Windows" - what was that  - 6th edition or something like that circa 1998 or something like that?  Anyway, he went on to say moving from the one byte character to the two byte character was the final answer.  Turns out that it isn't.  There still aren't enough  bytes in 65236 or whatever 2^16 is.  That came out a bit later than his book. 

So.... for Chinese, Japanese, and Korean versions of Windows Microsoft has some kind of special setup like with the old multi-byte character sets where a lead byte specifies what is contained in the next several bytes.  So I believe the original intent was to have Microsoft's wchar_t type (2 byte unsigned short int) be a FIXED WIDTH character encoding - but it didn't turn out that way.  In some areas of the world its still a VARIABLE WIDTH CHARACTER ENCODING.

Martins Mozeiko faulted my String Class on that basis.  All my algorithms in my String Class assume a FIXED WIDTH character; either ansi or two byte wchar_t.  In other words, it would fail on Chinese, Japanese, or Korean versions of Windows if a String was encountered where a character was not exactly one byte or two bytes.  At least that's my understanding of his argument.

His solution or recommendation to me was to exclusively use UTF-8 which is a variable width character encoding where some characters are one byte, other characters are two bytes, and yet others could be three or four bytes!!!!!!!!!!!!!!!!!

And just how exactly does one figure the count of characters in such strings?  Well, its not pretty.  I could spend the rest of my life I guess writing voluminous code to try to figure out how long a string is (he provided me links where folks are doing jnust that).  Maybe in my next life after a reincarnation I could spend that trying to figure out how to implement Mid$ on such a String where a character could be anywhere from one to four bytes long.

At least that's my understanding of the issue.  I could be wrong and if someone knows better perhaps they could straighten me out.     
Title: Re: Moving TCLib Into Mainstream
Post by: James C. Fuller on June 01, 2016, 02:56:16 PM
Fred,
Not bugging you (ok I am):)   I want to release an update to bc9Basic with the new TCLib support but I do need the atof to support some of the examples.

James
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on June 01, 2016, 05:52:32 PM
Sorry I took so long in posting this.  Its the original I got working, and was thinking I could improve on it by simply recopying the string representation of a number to another buffer without the decimal point, calling atoi on that, then dividing the result in a loop by ten till I got the right value.  Might save a couple lines from what I have now.  But here is what I have now...


//==============================================================================================
//               Developed As An Addition To Matt Pietrek's LibCTiny.lib
//                             By Fred Harris, May 2016
//
//        cl atof.cpp /D "_CRT_SECURE_NO_WARNINGS" /c /W3 /DWIN32_LEAN_AND_MEAN
//==============================================================================================
#include <windows.h>
#include "stdlib.h"
typedef SSIZE_T ssize_t;

double __cdecl atof(const char* pStr)
{
ssize_t lTotal   = 0;
char* pDecPt     = NULL;
char c,cNeg      = NULL;
double dblReturn;
size_t iDiff;

while(*pStr==32 || *pStr==8 || *pStr==48)
    pStr++;
if(*pStr=='-')
{
    cNeg='-';
    pStr++;
}
while(*pStr)
{
    if(*pStr=='.')
    {
       pDecPt=(char*)pStr;
       pStr++;
    }
    else
    {
       c=*pStr++;
       lTotal=10*lTotal+(c-48); // Add this digit to the total.
    }
}
if(pDecPt)
    iDiff=(int)(pStr-pDecPt-1);
else
    iDiff=0;
if(cNeg=='-')                  // If we have a negative sign, convert the value.
    lTotal=-lTotal;
dblReturn=(double)lTotal;
for(size_t i=0; i<iDiff; i++)
     dblReturn=dblReturn/10;

return dblReturn;
}

double __cdecl _wtof(const wchar_t* pStr)
{
ssize_t lTotal = 0;
wchar_t* pDecPt=NULL;
wchar_t c,cNeg=NULL;
double dblReturn;
size_t iDiff;

while(*pStr==32 || *pStr==8 || *pStr==48)
    pStr++;
if(*pStr==L'-')
{
    cNeg=L'-';
    pStr++;
}
while(*pStr)
{
    if(*pStr==L'.')
    {
       pDecPt=(wchar_t*)pStr;
       pStr++;
    }
    else
    {
       c=*pStr++;
       lTotal=10*lTotal+(c-48); // Add this digit to the total.
    }
}
if(pDecPt)
    iDiff=(int)(pStr-pDecPt-1);
else
    iDiff=0;
if(cNeg==L'-')                  // If we have a negative sign, convert the value.
    lTotal=-lTotal;
dblReturn=(double)lTotal;
for(size_t i=0; i<iDiff; i++)
     dblReturn=dblReturn/10;

return dblReturn;
}


Those functions need to be added to stdlib.h too...


// stdlib.h
#ifndef stdlib_h
#define stdlib_h
   #define NULL 0
   extern "C" void*   __cdecl malloc  (size_t          size);
   extern "C" void    __cdecl free    (void*           pMem);
   extern "C" long    __cdecl atol    (const char*     pStr);
   extern "C" int     __cdecl atoi    (const char*     pStr);
   extern "C" long    __cdecl _wtol   (const wchar_t*  pStr);
   extern "C" _int64  __cdecl _atoi64 (const char*     pStr);
   extern "C" _int64  __cdecl _wtoi64 (const wchar_t*  pStr);
   extern "C" double  __cdecl atof    (const char*     pStr);
   extern "C" double  __cdecl _wtof   (const wchar_t*  pStr);
   extern "C" int     __cdecl abs     (int             n   );
   extern "C" long    __cdecl labs    (long            n   );
   extern "C" _int64  __cdecl _abs64  (__int64         n   );
#endif


And maybe to tchar.h if you use those....


// tchar.h
#ifndef tchar_h
   #define tchar_h
   #ifdef  _UNICODE
      typedef wchar_t     TCHAR;
      #define _T(x)       L## x
      #define _tmain      wmain
      #define _tWinMain   wWinMain
      #define _tfopen     _wfopen
      #define _fgetts     fgetws
      #define _tprintf    wprintf
      #define _ftprintf   fwprintf
      #define _stprintf   swprintf
      #define _tcslen     wcslen
      #define _tcscpy     wcscpy
      #define _tcscat     wcscat
      #define _tcsncpy    wcsncpy
      #define _tcscmp     wcscmp
      #define _tcsicmp    _wcsicmp
      #define _tcsncmp    wcsncmp
      #define _tcsnicmp   _wcsnicmp
      #define _tcsrev     _wcsrev
      #define FltToTch    FltToWch
      #define _ttol       _wtol
      #define _ttoi64     _wtoi64
      #define _ttof       _wtof
   #else
      typedef char        TCHAR;
      #define _T(x)       x
      #define _tmain      main
      #define _tWinMain   WinMain
      #define _tfopen     fopen
      #define _fgetts     fgets
      #define _tprintf    printf
      #define _ftprintf   fprintf
      #define _stprintf   sprintf
      #define _tcslen     strlen
      #define _tcscpy     strcpy
      #define _tcscat     strcat
      #define _tcsncpy    strncpy
      #define _tcscmp     strcmp
      #define _tcsicmp    _stricmp
      #define _tcsncmp    strncmp
      #define _tcsnicmp   _strnicmp
      #define _tcsrev     _strrev
      #define FltToTch    FltToCh
      #define _ttol       atol
      #define _ttoi64     _atoi64
      #define _ttof       atof
   #endif
#endif


And in terms of my floating point delema with sprint, printf, and fprintf, I'm just having to leave it alone the way I have it where %f doesn't work, and one must either use my string class String::Format() or FltToTch().
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on June 01, 2016, 05:54:01 PM
Had it all along.  Just forgot how I named it (Demo of atof())...


// cl Demo27.cpp /O1 /Os /GS- /link TCLib.lib kernel32.lib
// 5,120 bytes VC15
// 6,144 bytes VC19
#define UNICODE
#define _UNICODE
#include <windows.h>
#include "stdio.h"
#include "stdlib.h"
#include "tchar.h"
extern "C" int _fltused=1.0;

int main()
{
TCHAR* pStrs[]={_T("  -12345.678987654"), _T("0.99"), _T("0"), _T("1"), _T("-0.009")};
TCHAR szConvertedDouble[24];
double dblNumber;

for(size_t i=0; i<sizeof(pStrs)/sizeof(pStrs[0]); i++)
{
     dblNumber=_ttof(pStrs[i]);
     FltToTch(szConvertedDouble,dblNumber,24,9,_T('.'),true);
     _tprintf(_T("szConvertedDouble=%s\n"),szConvertedDouble);
}
getchar();

return 0;
}
// Output:
// ==================
// C:\Code\VStudio\VC15\LibCTiny\x64\Test20>Demo27
// szConvertedDouble=       -12345.678987654
// szConvertedDouble=            0.990000000
// szConvertedDouble=            0.000000000
// szConvertedDouble=            1.000000000
// szConvertedDouble=           -0.009000000


And this is what my TCLib.mak file now looks like.  You don't need those last two if you aren't interested in x86...


PROJ       = TCLib

OBJS       = crt_con_a.obj crt_con_w.obj crt_win_a.obj crt_win_w.obj memset.obj newdel.obj printf.obj \
             sprintf.obj _strnicmp.obj strncpy.obj strncmp.obj _strrev.obj strcat.obj strcmp.obj \
             strcpy.obj strlen.obj getchar.obj alloc.obj alloc2.obj allocsup.obj FltToCh.obj atol.obj \
             _atoi64.obj abs.obj memcpy.obj strchr.obj fopen.obj fprintf.obj _stricmp.obj fgets.obj \
             atof.obj win32_crt_math.obj win32_crt_Float.obj
       
CC         = CL
CC_OPTIONS = /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN

$(PROJ).LIB: $(OBJS)
    LIB /NODEFAULTLIB /machine:x64 /OUT:$(PROJ).lib $(OBJS)

.CPP.OBJ:
    $(CC) $(CC_OPTIONS) $<
Title: Re: Moving TCLib Into Mainstream
Post by: Frederick J. Harris on June 03, 2016, 07:58:20 PM
Back to the issue of files and unicode again. 

As I suspected (just tested it out now to make absolutely sure), when one uses fwprintf() and there is a wide character parameter, the C Runtime writes the data to the file as asci!  Here's a program to prove it...


// cl Test1.cpp /O1 /Os /MT
// 125,952 ansi
// 128,512 wide
#define UNICODE
#define _UNICODE
#include <stdio.h>
#include <tchar.h>

int main()
{
FILE* fp=NULL;

fp=_tfopen(_T("Data.txt"),_T("w"));
if(fp)
{
    _ftprintf(fp,_T("Hello, World!\n"));
    _tprintf(_T("Hello, World!\n"));
    fclose(fp);
    getchar();
}

return 0;
}


Note the above program isn't using my TCLib.lib, but is rather linking in the typical manner with the C Runtime.  And it opens "Data.txt" and writes "Hello, World!" to the file.  If you check out the count of bytes in the file using Windows Explorer >> File Properties you'll see it is 15 bytes - 13 bytes for "Hello, World!" and two bytes for the CrLf.  This in spite of the fact that all wide character versions of the file opening and writing functions were used.  If the file had been written as UNICODE it should have been 30 bytes. 

And so I have something of a disjuncture with my TCLib and the C Runtime on this issue.  That same program above using my TCLib would have output the string with two bytes per character for a total file size of 30 bytes (I've tested it).  And if you open the file in Notepad and use Save As... it'll show up as a UNICODE file.

So what to do?

I just changed my fwprintf routine to work the way the C Runtime does.  I don't see much else I can do.  I want my TCLib to be as compatible with the C Runtime as possible.  So here is that file now...


//=============================================================
//   Developed As An Addition To Matt Pietrek's LibCTiny.lib
//                By Fred Harris, March 2016
//
//  cl fprintf.cpp /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN   
//=============================================================
// cl sprintf.cpp /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdarg.h>
#include "stdio.h"
#define EOF (-1)
#pragma comment(linker, "/defaultlib:user32.lib")

int __cdecl fprintf(FILE* fp, const char* format, ...)
{
char szBuff[1024];
DWORD cbWritten;
va_list argptr;
int retValue;

va_start(argptr, format);
retValue = wvsprintfA(szBuff, format, argptr);
va_end(argptr);
WriteFile((HANDLE)fp, szBuff, retValue, &cbWritten, 0);

return retValue;
}

int __cdecl fwprintf(FILE* fp, const wchar_t* pszFormat, ...)  // incomming pointers to character strings will be pointing to wide character strings
{                                                             
wchar_t szBuff[512];                                          // wide character strings will be written to here
char szAsci[512];                                             // this variable/buffer will contain converted string (from wide to narrow)
DWORD cbWritten;
va_list argptr;
int retValue;

va_start(argptr, pszFormat);
retValue = wvsprintfW(szBuff, pszFormat, argptr);
va_end(argptr);
WideCharToMultiByte(CP_ACP,0,szBuff,(int)wcslen(szBuff),szAsci,512,NULL,NULL);  // convert wide szBuff to narrow szAsci
WriteFile((HANDLE)fp, szAsci, retValue, &cbWritten, 0);                         // output retValue # of bytes

return retValue;
}


Another change too.  Not related to files though.  Its an improvement on my atol().  It really needs to test each character during the conversion to make sure the character is a digit, i.e., code between 48 and 57...


//==============================================================================================
//               Developed As An Addition To Matt Pietrek's LibCTiny.lib
//                             By Fred Harris, March 2016
//
//        cl atol.cpp /D "_CRT_SECURE_NO_WARNINGS" /c /W3 /DWIN32_LEAN_AND_MEAN
//==============================================================================================
#include "stdlib.h"

long __cdecl atol(const char* pStr)
{
char c,cNeg=NULL;           // c holds the char; cNeg holds the '-' sign.
long lTotal=0;              // The running total.

while(*pStr==32 || *pStr==8)
    pStr++; 
if(*pStr=='-')
{
    cNeg='-';
    pStr++;
}
while(*pStr)
{
    if(*pStr>=48 && *pStr<=57)
    {
       c=*pStr++;
       lTotal=10*lTotal+(c-48); // Add this digit to the total.
    }
    else
       pStr++;     
}
if(cNeg=='-')               // If we have a negative sign, convert the value.
    lTotal=-lTotal;

return lTotal;
}

int __cdecl atoi (const char* pStr)
{
return ((int)atol(pStr));
}

long __cdecl _wtol(const wchar_t* pStr)
{
wchar_t c,cNeg=NULL;        // c holds the char; cNeg holds the '-' sign.
long lTotal=0;              // The running total.

while(*pStr==32 || *pStr==8)
    pStr++; 
if(*pStr==L'-')
{
    cNeg=L'-';
    pStr++;
}
while(*pStr)
{
    if(*pStr>=48 && *pStr<=57)
    {
       c=*pStr++;
       lTotal=10*lTotal+(c-48); // Add this digit to the total.
    }
    else
       pStr++;     
}
if(cNeg==L'-')              // If we have a negative sign, convert the value.
    lTotal=-lTotal;

return lTotal;
}


int __cdecl _wtoi (const wchar_t* pStr)
{
return ((int)_wtol(pStr));
}


All this nastiness is coming out now that I'm actually using this on a major production app.