• Welcome to Jose's Read Only Forum 2023.
 

Moving TCLib Into Mainstream

Started by Frederick J. Harris, April 02, 2016, 12:03:13 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

James C. Fuller

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

Frederick J. Harris

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. 

Frederick J. Harris

     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!

Frederick J. Harris

#48
     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...

Frederick J. Harris


// 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;
}


Frederick J. Harris

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


Frederick J. Harris

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


Frederick J. Harris

#52
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...

Frederick J. Harris

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));
}


Frederick J. Harris

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);


Frederick J. Harris

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...

Frederick J. Harris


// 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

Frederick J. Harris

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.

James C. Fuller

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

Frederick J. Harris

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.! :'(