Author Topic: Win32 SDK Tutorial Using TCLib  (Read 6834 times)

Frederick Harris

  • Newbie
  • *
  • Posts: 47
Win32 SDK Tutorial Using TCLib
« on: October 14, 2021, 09:13:12 pm »
     To use TCLib to minimize the the sizes of your binaries you first have to obtain it.  The TCLib package is included here on Patrice's ObjReader site.  In the TCLib.rar package are all the *.cpp and *.h files necessary to build the library and use it, but also included there is the library itself ready for use by folks who don't care to build it.  I've never personally built the library from within an IDE such as Visual Studio, although I expect it could be done.  I simply execute Microsoft's nmake.exe utility from within the Visual Studio Command Prompt for x64 builds.  The shortcut to that functionality is found on the Start Menu's Visual Studio entries from the Visual Studio install.  Behind the scenes the shortcut executes a vcvars.bat file which sets up a command line session where paths are set such that the various components of Microsoft's 'Build Chain' become available.

     Skip the next few paragraphs if you don't care to build TCLib from the command line, or if you just want to use the included TCLib.lib.  If by some slim chance you care to build the library as I do from the command line (try it!  You'll have Fun!), simply open the x64 command prompt window as I described above, and change your directory/folder to where you've unzipped the TCLib package.  Or perhaps even better yet, make a second 'test' directory, copy all the source code files to there, and change your directory to that using the 'CD' and 'CD\' commands.  There should be a TCLib.mak file there, which looks like this....

Code: [Select]
PROJ       = TCLib

OBJS       = crt_con_a.obj crt_con_w.obj crt_win_a.obj crt_win_w.obj InitStdio.obj InitMath.obj\
             crt_dll.obj newdel.obj alloc.obj alloc2.obj allocsup.obj  strlen.obj memcpy.obj \
             strcpy.obj strncpy.obj strcmp.obj _stricmp.obj _strnicmp.obj _strrev.obj strncmp.obj \
             _atoi64.obj atof.obj abs.obj memset.obj strchr.obj strrchr.obj strcat.obj memcmp.obj \
             atol.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) $<

To build the library simply execute....

NMake TCLib.mak  [ENTER]

...at your command prompt. Here is the somewhat voluminous output from my system...

Code: [Select]
C:\Code\VStudio\VC19\TCLib>NMake TCLib.mak

Microsoft (R) Program Maintenance Utility Version 14.29.30133.0
Copyright (C) Microsoft Corporation.  All rights reserved.

        CL /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN crt_con_a.CPP
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30133 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

crt_con_a.CPP
        CL /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN crt_con_w.CPP
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30133 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

crt_con_w.CPP
        CL /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN crt_win_a.CPP
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30133 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

crt_win_a.CPP
        CL /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN crt_win_w.CPP
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30133 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

crt_win_w.CPP
        CL /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN InitStdio.CPP
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30133 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

InitStdio.CPP
        CL /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN InitMath.CPP
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30133 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

InitMath.CPP
        CL /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN crt_dll.CPP
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30133 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

crt_dll.CPP
        CL /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN newdel.CPP
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30133 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

newdel.CPP
        CL /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN alloc.CPP
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30133 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

alloc.CPP
        CL /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN alloc2.CPP
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30133 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

alloc2.CPP
        CL /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN allocsup.CPP
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30133 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

allocsup.CPP
        CL /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN strlen.CPP
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30133 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

strlen.CPP
        CL /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN memcpy.CPP
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30133 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

memcpy.CPP
        CL /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN strcpy.CPP
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30133 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

strcpy.CPP
        CL /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN strncpy.CPP
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30133 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

strncpy.CPP
        CL /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN strcmp.CPP
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30133 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

strcmp.CPP
        CL /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN _stricmp.CPP
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30133 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

_stricmp.CPP
        CL /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN _strnicmp.CPP
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30133 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

_strnicmp.CPP
        CL /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN _strrev.CPP
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30133 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

_strrev.CPP
        CL /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN strncmp.CPP
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30133 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

strncmp.CPP
        CL /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN _atoi64.CPP
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30133 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

_atoi64.CPP
        CL /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN atof.CPP
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30133 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

atof.CPP
        CL /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN abs.CPP
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30133 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

abs.CPP
        CL /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN memset.CPP
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30133 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

memset.CPP
        CL /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN strchr.CPP
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30133 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

strchr.CPP
        CL /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN strrchr.CPP
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30133 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

strrchr.CPP
        CL /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN strcat.CPP
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30133 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

strcat.CPP
        CL /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN memcmp.CPP
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30133 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

memcmp.CPP
        CL /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN atol.CPP
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30133 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

atol.CPP
        LIB /NODEFAULTLIB /machine:x64 /OUT:TCLib.lib

crt_con_a.obj
crt_con_w.obj
crt_win_a.obj
crt_win_w.obj
InitStdio.obj
InitMath.obj
crt_dll.obj
newdel.obj
alloc.obj
alloc2.obj
allocsup.obj 
strlen.obj
memcpy.obj 
strcpy.obj
strncpy.obj
strcmp.obj
_stricmp.obj
_strnicmp.obj
_strrev.obj
strncmp.obj 
_atoi64.obj
atof.obj
abs.obj
memset.obj
strchr.obj
strrchr.obj
strcat.obj
memcmp.obj 
atol.obj

Microsoft (R) Library Manager Version 14.29.30133.0
Copyright (C) Microsoft Corporation.  All rights reserved.

C:\Code\VStudio\VC19\TCLib>

     If you don't care to build the library yourself, simply ignore all that.  But following are the things you need to know to use the library.  First, I don't have a working x86 version at this time.  For the first year or so I worked on TCLib, I developed the x86 and x64 versions in tandem.  But I kept running into excrutiating difficulties with the x86 version - having to write all kinds of assembler code involving casting between 64 bit doubles and 32 bit integral numbers, for example, among other things, and it just wore me down and I temporarily abandoned the x86 code I had.  Maybe I'll pick it up again in the fullness of time, but for now I only have an x64 version.  The above make file won't build if you attempt to execute it in an x86 command prompt window.  Note the /machine:x64 linker switch towards the end of TCLib.mak above.

     Second, you'll need to use my TCLib specific versions of the C Runtime headers stdio.h stdlib.h, math.h, memory.h, malloc.h, string.h, and tchar.h - if your code uses any of the functions in those headers.  You only need to include headers of functions your code references.  Regarding that, I usually test build my programs using both my TCLib linkage, and the standard Microsoft linkage to theit C Runtime.  So what I do is define a symbol like so at the top of my code....

#define TCLib

If I want TCLib linkage I'll build the code with that symbol as is.  If I want to build against Microsoft's default libraries, I'll comment that out...

//#define TCLib

Where it has an effect - and this is IMPORTANT, is in my #includes.  To illustrate this, here is Demo1.cpp from the TCLib download, which is basically pretty close to Dennis Ritchie's famous "Hello, World!" program....

Code: [Select]
// Dennis Ritchie Classic "Hello, World!"
// cl Demo1.cpp /O1 /Os /GS- /Gy TCLib.lib
// cl Demo1.cpp /O1 /Os /GS- /Gy
//   2,048 Bytes TCLib,  x86, Ascii,   VC19
//   2,048 Bytes TCLib,  x86, UNICODE, VC15
//   2,560 Bytes TCLib,  x64  UNICODE, VC15
//   3,584 Bytes TCLib,  x64, Ascii,   VC19
//   3,584 Bytes TCLib,  x64, UNICODE, VC19
// 127,488 Bytes LibCmt, x64, Ascii,   VC19
// 123,904 Bytes LibCmt, x64, UNICODE, VC19
//#define TCLib
#ifndef UNICODE
   #define  UNICODE
#endif
#ifndef _UNICODE
   #define  _UNICODE
#endif
#include <windows.h>
#ifdef TCLib
   #include "stdio.h"
   #include "tchar.h"
#else
   #include <stdio.h>
   #include <tchar.h>
#endif

int _tmain()
{
 _tprintf(_T("Hello, World!\n"));
 getchar();

 return 0;
}

// Output:
// =============
// Hello, World!

Carefully note this above...

Code: [Select]
#ifdef TCLib
   #include "stdio.h"
   #include "tchar.h"
#else
   #include <stdio.h>
   #include <tchar.h>
#endif

     If the symbol TCLib is defined, the names in the header files are enclosed in double quotes, which causes the compiler to look for the file in the program's directory/folder.  If TCLib isn't defined, i.e., we want to build with Microsoft's default libraries - not my TCLib, then the compiler uses the includes referenced in the build environment for the compiler.  So it will be seeing different includes in each case.  Many times you'll be wanting to test run a build with Microsoft's libraries, and you'll forget to comment out the TCLib symbol, and you'll get screens full of nasty linker errors (the worst kind) full of nasty gibberish.  If that happens - and it will I guarantee you, you'll now know where to look!

     The other difference in the above code, which is different from Dennis Ritchie's code, is the inclusion of <Windows.h>.  His program only included stdio.h, naturally, because he was working with his new Unix operating system.  But Windows.h is necessary when using my TCLib because Windows substitutes for C Runtime functions (there are many) is one of TCLib's methodologies for eliminating C Runtime functions.   

     Next, is TCLib only for C programs?  No, absolutely not!  While I started with C and coded only in C for many years before I taught myself C++, lately, like for at least ten years, I haven't really coded in C, and I always end my source code files in *.cpp for C++ compilation.  One effect the development of TCLib had on me was to crystallize in my mind the absolutely sharp distinction between the C and C++ languages themselves, and their standard libraries.  TCLib has nothing to do with the definition and capabilities of the C or C++ languages; it is simply an alternate source of library code.  For an example of this, let's take a quick look at something that is a bit tricky to do in C - parse strings.  Of course in C we had the strtok family of functions, and casting about for some way to describe my feelings for those functions the words ghastly, grisly, and gruesome all come to mind.  With C++ and it's Standard Library it became a bit easier.  The canonical way of parsing a string such as....

"Zero, One, two, three, four, five, six"

...in C++ would be something like so....

Code: [Select]
// cl  StdLibParse.cpp /O1 /Os /MT /EHsc
// g++ StdLibParse.cpp -oStdLibParse.exe -mconsole -s -Os
//   225,792 Bytes, VC19, x64, ascii, LibCmt, C++ Std. Lib.
// 1,007,616 Bytes, TDM_GCC, x64, Ascii
#include <iostream>
#include <sstream>

int main()
{
 std::string input = "Zero, One, Two, Three, Four, Five, Six";
 std::istringstream ss(input);
 std::string token;
 while(std::getline(ss, token, ','))
 {
    std::cout << token << '\n';
 }
 
 return 0;
}


#if 0

...and here would be the output from that...

Output:
=======
Zero
 One
 Two
 Three
 Four
 Five
 Six

 #endif

Basically 15 lines of code and it builds to 225,792 bytes with VC19 from Visual Studio 2019.  Even worse, Mingw GCC 9 series compilers build it to 1,007,616 bytes!  And that's with optimizing for size -Os, and stripping debug symbols from executable (-s).  Here is another C++ program (Demo32.cpp) using my String Class which is exactly 15 lines long - same as StdLibParse.cpp above, and building it against the Microsoft C/C++ Standard Library, it builds to 138,752 Bytes, or 87,040 bytes less than using the iostream and sstream libraries.... 

Code: [Select]
// Demo32.cpp
//   5,120 Bytes VC19              TCLib  x64 Ascii
// 138,752 Bytes VC19 VStudio 2019 LibCmt x64 Ascii
// 158,720 Bytes TDM-GCC-64 9.2    LibCmt x64 Ascii
#include <windows.h>
#include "Strings.h"

int main()
{
 String s1="Zero, One, Two, Three, Four, Five";
 int iParseCount=s1.ParseCount(',');
 String* pStrs=new String[iParseCount];
 s1.Parse(pStrs, ',', iParseCount);
 for(int i=0; i<iParseCount; i++)
     pStrs[i].Print(true);
 delete [] pStrs;
 
 return 0;
}

     So that knocks off about 40% size wise.  However, building it against TCLib produces a 5,120 byte executable!  That's a size reduction of about 98%. And that's absolutely a C++ program.  If you examine that code closely, and you are familiar with PowerBASIC, you might come to the conclusion that it looks suspiciously like PowerBASIC code, but in C++.  You would certainly be right.  Here is the exact code in PowerBASIC....

Code: [Select]
'13,064 Bytes
#Compile Exe                                    ' No headers needed for this program.  All the dynamic memory allocation,
#Dim All                                        ' array handling, and string functionality are part of the core language.

Function PBMain() As Long
  Local iParseCount As Long
  Local strLine As String
  Local pStrs() As String
  Register i As Long

  strLine = "Zero, One, Two, Three, Four, Five" ' Assign CSV string to dynamic OLE string variable
  iParseCount = ParseCount(strLine,",")         ' Determine  number of comma separated values
  Redim pStrs(iParseCount) As String            ' Dynamic memory allocation of array string variable
  Parse strLine, pStrs(), ","                   ' Call PowerBASIC Parse command to Parse String based on comma
  For i=0 To UBound(pStrs, 1)-1                 ' Loop through pStrs array outputting parsed ‘tokens’.
    Console.Print pStrs(i)
  Next i
  Erase pStrs()                                 ' Free memory.  This is optional.  PowerBASIC automatically does it
  Waitkey$

  PBMain=0
End Function

     Took 21 lines of code instead of 15, and that's mostly because I don't believe PowerBASIC allows variable declarations and initializations on the same line.  Size wise it comes in 13,064 bytes, which seems pretty good to me - certainly much more compact than C++ versions not built with TCLib.

     One final note I should provide is that in using TCLib to build really large and complex applications, it is sometines necessary to take charge of stack memory allocations through additional compiler/linker switches.  You may wish to read up on this.  Here is an example of the command line string I use for a rather large (maybe 50,000 to 60,000 lines of code) application at work...

Code: [Select]
TimberBeast.cpp
Strings.cpp
CSql.cpp
frmTally.cpp
frmCruise.cpp
frmProcess.cpp

/O1 /Os /GS- /Gs9999999 /GR- /Zc:sizedDealloc- /FeTbrBst.exe
/link /STACK:0x100000,0x100000

TCLib.lib
Kernel32.lib
User32.lib
Gdi32.lib
UUID.lib
shell32.lib
Ole32.lib
OleAut32.lib
odbc32.lib
comdlg32.lib

     The above is in a text file named TbrBst.txt and it is how I build an application named TimberBeast.  Using command line compiling one can then do this at the command prompt...

cl @TbrBst.txt

The '@' symbol tells the compiler 'cl' to read in the command line compilation string from a text file in the project directory named TbrBst.txt.  Take note of the /Gs and /STACK:0x100000,0x100000 switches.  You may need to play with those numbers if you are getting 'out of stack space' errors in very large programs which may have procedures with a lot of local variables that are consumming a lot of stack space. 

     One last thing that comes to mind is that in applications with pure virtual functions it is necessary to implement an obscure function named _purecall().  Here is my implementation of it from my ActiveX Grid Control...

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

     The above code samples illustrate my intention of using the C++ language in a rather eclectic way to create an application development framework with a syntax more similiar to basic family languages than common C/C++ usages where the C++ specific part of the Standard Library are used.  So in a way my development of TCLib in conjunction with my String Class and Multi-Dimensional Array Class provided me a somewhat adequate replacement of what I felt I had lost with the unfortunate passing of Bob Zale.  Of course, there are plusses and minuses to everything.  In some ways my framework isn't quite as good as PowerBASIC, but in other ways it is better.  Maybe not as easy to use, and sometimes a bit cranky, but in other ways better because it builds even smaller than PowerBASIC - which is quite a feat, and it is tied to Microsoft's world class compiler, which will always be up to date, and allows even finer control of the build and linking process than is available with PowerBASIC - and likely all or most other programming languages.