• Welcome to Powerbasic Museum 2020-B.
 

Reducing Windows Executable File Size With Matt Pietrek's LIBCTINY.LIB Revisited

Started by Frederick J. Harris, February 12, 2016, 01:47:02 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Frederick J. Harris

     This is a continuation of my previous work on  Matt Pietrek's LibCTiny.lib, which can be used to minimize the ever increasing size of binaries produced by Microsoft compilers.  My previous post on this from 2014 is here...

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

     I have updated my work to include the latest Microsoft compiler (from Visual Studio 2015), and have pushed the limits to see how sophisticated of an app can be made by linking with Matt's LibCTiny (and quite a few extra functions I added to it).

     To review, the techniques involved here are basically twofold.  First, specify some rather esoteric linker switches that force the linker to merge all code and data sections in the PE file into one section.  Second, to convince the linker to look elsewhere for functions other than in the C Runtime Libraries and to provide simpler replacements for the functions it is looking for, thereby eliminating the need to pull large object code sections into the executable.

     As I write this now in February of 2016 there are some events taking place at Microsoft that have the potential to eliminate the necessity to do what I am doing here in order to reduce the size of executables.  That movement is known as the UCRT or Universal C Runtime.  Here is a link from one of Microsoft's blogs on it ...

The Great C Runtime (CRT) Refactoring

https://blogs.msdn.microsoft.com/vcblog/2014/06/10/the-great-c-runtime-crt-refactoring/

...and here...

https://blogs.msdn.microsoft.com/vcblog/2015/03/03/introducing-the-universal-crt/

     The reader responses after the latter article are particularly revealing of the developer community angst over this matter.   The benefits from that work may not become evident for many years however.

     I'll provide a brief synopsis on the issue before getting into the details of code and results.  Ever since after Visual Studio 6 - VC6 circa 1998 or so, Microsoft has been putting out versioned C and C++ Runtimes of the C and C++ Standard Libraries.  From VC6 we simply had...

msvcrt.dll and msvcrt.lib

But starting with Visual Studio 2002 (2002, 2003, 2005, 2008, 2010, 2012, and 2013) the files Microsoft provided to developers were like so...

MSVCRT70.DLL Visual Studio .NET
MSVCRT71.DLL Visual Studio 2003
MSVCRT80.DLL Visual Studio 2005
MSVCRT90.DLL Visual Studio 2008
MSVCRT100.DLL Visual Studio 2010

     But Microsoft didn't use these 'developer' versions themselves.  They used their own version of msvcrt.dll and msvcrt.lib to create Windows itself, all its device drivers, and their own Windows Components and programs. 

     Developers have been stuck with having either to provide the several megabyte 'Redistributable Package' alongside of their programs to be installed by an installer, or doing a static link ( /MT ) against the versioned library files, which resulted in hundred kilobyte sized "Hello, World!" type programs, i.e., a pretty lot of bloat.

     The Windows C++ coding world we live in now doesn't just include Microsoft compilers.  In the early 2000s the MinGW project successfully ported the GCC compiler collection originally started by Richard Stallman over to Windows.  Their early compilers were a favorite of mine simply because they attacked the problem of the C Standard Library implementation differently than Windows.  Since msvcrt.dll is now a 'protected' system dll in \system32, they use that implementation as their 'runtime' instead of the versioned files Microsoft puts out.  It's a defensible strategy because it's as system file, as is, for example, kernel32.dll or user32.dll.  Therefore, executables produced are much, much smaller than comparable VC compiled ones.  At least for a long while.  But now that's over, and has been for some time.  With the release of post C99 changes to the C++ Standards, they've incorporated various threading libraries into the GCC produced binaries, and their binaries are now much larger than even Microsoft's.  It's a sorry situation for those of us who remember and value small, efficient code with no bloat.  With that background, let's get into the details now.

     If you do any application development work within the C/C++ ecosystem you just about can't live with C.  What you need is a good string class, because, a large part of much application development work involves manipulating strings.  So you need C++.  So lets take a look at the code size effect of adding various string classes to various C++ and other compilers for the production of a simple console mode "Hello, World!" program, i.e., this...


#include <stdio.h>
#include <string>

int main()
{
std::wstring s(L"Hello, World!");
wprintf(L"%s\n",s.c_str());
getchar();

return 0;
}


     In the above program I'm using the std::wstring class from the C++ Standard Library.  Note I'm not using <iostream> to output text to the console, because that would further bloat the executable.  Also note I didn't include <cstdio> but rather stdio.h because, again, that makes for even more useless bloat (about 20 k!).  So we're trying to do everything possible to eliminate code bloat short of eliminating our string class, which we deem as an absolute necessity.  Here are the resulting executable program sizes for two different versions of the MinGW GCC compiler using two different string classes, i.e., mine and the one from the C++ Standard Library, and also for VC9 C++ from Visual Studio 2008 and VC15 from Visual Studio 2015 with both string classes, with and without optimization from LibCTiny.lib, and for comparison purposes the PowerBASIC compiler result...


Compiler                                    Architecture         String Class              C Std. Lib.   Linkage   Memory Image
====================================================================================================================================
MinGW GCC g++ 4.4.1 circa 2009                 32 Bit     My String Class (Minimum Build)     Yes       msvcrt.dll      23552
MinGW GCC g++ 4.4.1 circa 2009                 32 Bit     My String Class (Maximum Build)     Yes       msvcrt.dll      29184

VC++ From VS 2008 Version 15.00.21022.08       64 Bit     My String Class (Minimum Build)     Yes       /MT LIBCMT      61440
VC++ From VS 2008 Version 15.00.21022.08       64 Bit     My String Class (Maximum Build)     Yes       /MT LIBCMT      76800

MinGW GCC g++ 4.8.1 circa 2013                 64 Bit     My String Class (Minimum Build)     Yes       msvcrt.dll     189952
MinGW GCC g++ 4.4.1 circa 2013                 64 Bit     My String Class (Maximum Build)     Yes       msvcrt.dll     196608

MinGW GCC g++ 4.4.1 circa 2009                 32 Bit     C++ Std. Lib. String Class          Yes       msvcrt.dll      58880
VC++ From VS 2008 Version 15.00.21022.08       64 Bit     C++ Std. Lib. String Class          Yes       /MT LIBCMT      92160
MinGW GCC g++ 4.8.1 circa 2013                 64 Bit     C++ Std. Lib. String Class          Yes       msvcrt.dll     234496

PowerBASIC Console Compiler 6.02 circa 2011    32 Bit     Built In WString/String Type        N.A.         N.A.         10752

VC++ From VS 2008 Version 15.00.21022.08       64 Bit     My String Class (Minimum Build)     No      LibCTiny.lib       5632
Vc++ from VS 2015 Version 19.00.23506          64 Bit     My String Class (Minimum Build)     No      LibCTiny.lib       8192


     A lot of data, I know!  But I'll explain it.  As you can see I'm testing both 32 bit and 64 bit compiles, where applicable.  By 'where applicable' I mean that the older GCC MinGW compilers can't produce 64 bit executables, and neither can the PowerBASIC Console Compiler.  Also, with my string class I have conditional compilation structures within it to remove certain member functions if they aren't needed, to aid in reducing un-needed code.  That's where the terms 'Minimum Build' and 'Maximum Build' come into play in the above data when using my string class, as opposed to the C++ Standard Library's string class.

     Further, all the compiles were linked as stated in the table, i.e., /MT for Microsoft compilers, and msvcrt.dll for the MinGW builds.  So all the results generate stand alone executables which will run on any 32 bit Windows operating system without the need to furnish additional runtime modules (64 bit exes won't run on 32 bit systems, obviously).

     Starting at the top, the older MinGW 32 bit compilers are just about my favorite.  With my string class I'm getting anywhere from 23 k to 29 k executables.  I don't consider that too bad considering my string class has about 1400 lines of code in it to compile into the executable, and that adds a lot of functionality to it.

     Next in the table comes the Microsoft compiler VC 9 from Visual Studio 2008.  I can create either 32 bit or 64 bit code with that.  Now we're starting to bloat!  We're up to 61 k to 77 k depending on which version of my string class I use.

     But things get worse.  Much worse.  The TDM GCC MinGW w64 branch is giving me nearly 200 k executables from a simple "Hello, World!" program!  Did I mention all these numbers are optimized as release builds for small code, i.e., O1, Os?  The reason for this is because as stated in the release notes for the TDM branches the winpthreads library gets statically linked into every executable, whether it needs it or not!  I guess the new and much waited for and loved C++ standards have seen to that!

The next three lines in the table...


MinGW GCC g++ 4.4.1 circa 2009                 32 Bit     C++ Std. Lib. String Class          Yes       msvcrt.dll      58880
VC++ From VS 2008 Version 15.00.21022.08       64 Bit     C++ Std. Lib. String Class          Yes       /MT LIBCMT      92160
MinGW GCC g++ 4.8.1 circa 2013                 64 Bit     C++ Std. Lib. String Class          Yes       msvcrt.dll     234496


...show the three compilers compared together but using not my string class but rather the C++ Standard Library's string class.  If you compare these numbers to the numbers using my string class you'll see I'm saving somewhere around 40 k using my string class.  That's partly why I have my own.  Actually, the savings are a bit more than shown because my string class has a lot of functionality lacking from the C++ Standard Library's, the addition of which to a use of the latter would bloat the results even further.

     The next entry...


Compiler                                    Architecture         String Class              C Std. Lib.   Linkage   Memory Image
====================================================================================================================================
PowerBASIC Console Compiler 6.02 circa 2011    32 Bit     Built In WString/String Type        N.A.         N.A.         10752


...gives a little perspective by showing results for another programming language compiler, i.e., PowerBASIC's Console Compiler.  That particular program looks like this...


#Compile  Exe
#Dim      All
#Optimize Size

Function PBMain() As Long
  Local s As WString

  s="Hello, World!"
  Console.Print s
  WaitKey$

  Function=0
End Function


     And of course that's for 32 bit compiled code that runs just as fast as C code.  In the case of PowerBASIC string handling is built right into the compiler and language; its not an 'add on' through libraries as with C++.

     Now comes the interesting part.  We'll use my string class with Matt Pietrek's LibCTiny.lib and see what we get.  Here is that line...


Compiler                                    Architecture         String Class              C Std. Lib.   Linkage   Memory Image
====================================================================================================================================
VC++ From VS 2008 Version 15.00.21022.08       64 Bit     My String Class (Minimum Build)     No      LibCTiny.lib       5632
Vc++ from VS 2015 Version 19.00.23506          64 Bit     My String Class (Minimum Build)     No      LibCTiny.lib       8192


Wow!!!!  5632 bytes!  More than 10 times smaller than VC9 x64 linked statically with the LIBCMT.LIB!  And even VC19 from Visual Studio 2015 is only coming in 8192 bytes.  And realize that isn't a C "Hello, World!" program.  Its a C++ program using a large String Class.  Here's the actual program exactly as it is in VC15 and VC19... 


// cl Hello.cpp Strings.cpp libctiny.lib Kernel32.lib /O1 /Os
#include <windows.h>
#include "stdio.h"
#include "Strings.h"             // TAKE NOTE <<< not C++ Std. Lib's <string>, rather my string class

int main()
{
String s1("Hello, World!");     // Literal String Instantiated In Constructor!
s1.Print(true);                 // String::Print() Is A Method Of My String Class
getchar();

return 0;
}


     But I haven't given you the full story yet.  Its not exactly with Matt Pietrek's original LibCTiny.lib, for I had to add my versions of about a dozen CRT functions to it.  That's how I was able to compile my string class into the above program. 

     Backing up a bit, when I originally looked into this business of minimizing program sizes with LibCTiny.lib over a year ago, I was successfully able to get it working in both 32 bit and 64 bit with quite minimal programs such as that "Hello, World!" program above without a String Class, i.e., just Dennis Ritchie's original C program, or with a blank Win32 GUI Window, i.e., something like this ...


#include <windows.h>

LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
if(msg==WM_DESTROY)
{
    PostQuitMessage(0);
    return 0;
}

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

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
WNDCLASSEX wc={};
MSG messages;
HWND hWnd;

wc.lpszClassName = "Form1";
wc.lpfnWndProc   = fnWndProc;
wc.cbSize        = sizeof(WNDCLASSEX);
wc.hInstance     = hInstance;
RegisterClassEx(&wc);
hWnd=CreateWindowEx(0,"Form1","Form1",WS_OVERLAPPEDWINDOW|WS_VISIBLE,200,100,325,300,HWND_DESKTOP,0,hInstance,0);
while(GetMessage(&messages,NULL,0,0))
{
    TranslateMessage(&messages);
    DispatchMessage(&messages);
}

return messages.wParam;
}


     And while I found that impressive (especially the latter, i.e., a 4 k Windows GUI program), I didn't pursue it any further and had serious questions as to the extensibility of the concept and system.  I suspected something would break down somewhere in attempting to add more functionality.  But I hoped to investigate further at some point.  Well, that brings us to now.  I've investigated it pretty well and nothing so far has broken down.  In terms of my first attempt to extend my work of over a year ago where I had a 4k Win64 GUI blank window, I tried adding a couple buttons and text boxes to it.  That program wrote "The World Needs More Love" to one text box when you clicked one button, and "The World Needs More Hate" to another text box when you clicked the other button.  That compiles and runs perfectly and is giving me a 5632 byte executable in x64 with VC15 (VC9 from VS 2008)...


//Form2.cpp
// cl Form2.cpp libctiny.lib Kernel32.lib User32.lib Gdi32.lib /O1 /Os /FeForm2.exe
#include <windows.h>
#define BTN_LOVE  1500
#define BTN_HATE  1505
#define TXT_LOVE  1510
#define TXT_HATE  1515


LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
   case WM_CREATE:
    {
       HINSTANCE hIns=((LPCREATESTRUCT)lParam)->hInstance;
       CreateWindowEx(0,"button","Love",WS_CHILD|WS_VISIBLE,125,15,80,30,hwnd,(HMENU)BTN_LOVE,hIns,0);
       CreateWindowEx(WS_EX_CLIENTEDGE,"edit","",WS_CHILD|WS_VISIBLE,25,60,285,22,hwnd,(HMENU)TXT_LOVE,hIns,0);
       CreateWindowEx(0,"button","Hate",WS_CHILD|WS_VISIBLE,125,105,80,30,hwnd,(HMENU)BTN_HATE,hIns,0);
       CreateWindowEx(WS_EX_CLIENTEDGE,"edit","",WS_CHILD|WS_VISIBLE,25,150,285,22,hwnd,(HMENU)TXT_HATE,hIns,0);
       return 0;
    }
   case WM_COMMAND:
    {
        switch(LOWORD(wParam))
        {
          case BTN_LOVE:
            SetWindowText(GetDlgItem(hwnd,TXT_LOVE),"The World Needs More Love!");
            break;
          case BTN_HATE:
            SetWindowText(GetDlgItem(hwnd,TXT_HATE),"The World Needs More Hate!");
            break;
        }
        return 0;
    }
   case WM_DESTROY:
    {
       PostQuitMessage(0);
       return 0;
    }
}

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


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
char szClassName[]="Form2";
WNDCLASSEX wc={};
MSG messages;
HWND hWnd;

wc.lpszClassName = szClassName;
wc.lpfnWndProc   = fnWndProc;
wc.cbSize        = sizeof(WNDCLASSEX);
wc.hbrBackground = (HBRUSH)COLOR_BTNSHADOW;
wc.hInstance     = hInstance;
RegisterClassEx(&wc);
hWnd=CreateWindowEx(0,szClassName,"What Does The World Need More Of?",WS_OVERLAPPEDWINDOW,150,150,350,250,HWND_DESKTOP,0,hInstance,0);
ShowWindow(hWnd,iShow);
while(GetMessage(&messages,NULL,0,0))
{
    TranslateMessage(&messages);
    DispatchMessage(&messages);
}

return messages.wParam;
}
 

     Well, Matt's system will do more than a blank window or a console "Hello, World!" program, apparently.  But how much more is the big question?  Should we 'go for broke' and see if my String Class could possibly be compiled/linked?  If it can that opens up BIG possibilities for size optimizations of C++ code.  It takes Matt Pietrek's concept out of the realm of being a curiosity and into the realm of practical day to day application development!  So that's the next BIG step!

     In terms of getting my String Class to compile, I shed a lot of blood and lost a fair amount of hide.  Been working on this for weeks!  At one point I thought I'd hit a brick wall and would have to give up.  But I got through it.  So if you are interested in pursuing this for yourself and perhaps reduce the size of your C++ executables drastically I'll provide detailed information on how exactly to go about this.  But a word of warning so that you've been forewarned.  This technique won't work apparently with any of the C++ Standard Library functionality.  At least not easily it won't.  I've only tried it with the C++ Standard Library's std::string class, and it won't link.  It won't link because no doubt the implementation is making calls into the C Standard Library that I have not implemented in Matt's LibCTiny.lib.  I further doubt that my work here will be useful to many (if any at all) C++ coders because I sense among them a reluctance to abandon the C++ Standard Library, which I perceive they somehow view erroneously as inseparable from the C++ language itself.  But for my part I never 'bought into' the C++ Standard Library, always preferring to use C idioms instead, so not using it was never any loss to me.  And I always lamented the fact that my C++ programs couldn't be as small as PowerBASIC ones due to this String Class issue.  So if you feel you can proceed, let's start.   

     First, you'll want to download Matt Pietrek's libctiny.lib and associated *.cpp files.  You'll also have to read his Microsoft Systems Journal article on this.  There were actually several of them, and other resources related to this widely regarded work of his can be found on the internet.  Here is the link to Matt's website where you can download the lib and source...

http://www.wheaty.net/

     All right, now for the details and there are a lot of them!  The first thing you ought to do is build his library yourself.  He provides the built library in the download, but we'll be having to use Lib.exe to add object code to it.  He provides a make file in the download that looks like so...

continued....

Frederick J. Harris


#==================================================
# LIBCTINY - Matt Pietrek 1996
# Microsoft Systems Journal, October 1996
# FILE: LIBCTINY.MAK - Makefile for Microsoft version
#==================================================
PROJ = LIBCTINY

OBJS =  CRT0TCON.OBJ CRT0TWIN.OBJ DLLCRT0.OBJ ARGCARGV.OBJ PRINTF.OBJ\
        SPRINTF.OBJ PUTS.OBJ ALLOC.OBJ ALLOC2.OBJ ALLOCSUP.OBJ STRUPLWR.OBJ \
        ISCTYPE.OBJ ATOL.OBJ STRICMP.OBJ NEWDEL.OBJ INITTERM.OBJ

CC = CL
CC_OPTIONS = /c /W3 /DWIN32_LEAN_AND_MEAN

!ifdef DEBUG
CC_OPTIONS = $(CC_OPTIONS) /Zi
!else
CC_OPTIONS = $(CC_OPTIONS) /O1
!endif

$(PROJ).LIB: $(OBJS)
    LIB /OUT:$(PROJ).LIB $(OBJS)

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


     You use NMAKE with Microsoft products to run make files.  The syntax to invoke nmake on a *.mak file is simply...

C:\directories\....\Working>nmake makefile.mak  [ENTER]

     If you execute libctiny.mak on Matt's *.cpp files you'll likely get an error - especially if running 64 bit.  The culpret is in NewDel.cpp where he has an unsigned int parameter for new.  In NewDel.cpp change the unsigned int param of new to size_t.  It was this...


void * __cdecl operator new(unsigned int s)
{
    return HeapAlloc( GetProcessHeap(), 0, s );
}


So change to this...


void * __cdecl operator new(size_t s)
{
    return HeapAlloc( GetProcessHeap(), 0, s );
}


     Then it should 'make' without errors.  If you want both a 32 bit version and a 64 bit version as I do you'll of course have to do it twice in different directories.  But the thing is, when I provide you with my String Class, you won't be able to compile it with just the functions Matt has provided.  You'll need more. 

      With just his files you can create a really small C type "Hello, World!" program a la Dennis Ritchie, or even a GUI program, but you'll essentially be using mostly C idioms to do it.  So we're back to needing a String Class, like I said.

     I knew this was going to be a long difficult undertaking with no guarantee of ultimate success and I would have to go very slow taking one very small step at a time, so the first thing I did was go through my String Class with a fine tooth comb locating every C Standard Library function call I made.  Then I compared this list against the list of functions in libctiny.lib.  While they are all listed in the make file above, you can run lib.exe on the library and obtain a 'dump'.  The directory I'm using for this exposition is...

C:\Code\VStudio\VC++9\test

And top line below shows the lib.exe command to list the *.obj files in Matt's unmodified libctiny.lib...


C:\Code\VStudio\VC++9\Test>lib libctiny.lib /list /verbose
Microsoft (R) Library Manager Version 9.00.21022.08
Copyright (C) Microsoft Corporation.  All rights reserved.

crt0twin.obj
crt0tcon.obj
DLLCRT0.OBJ
ARGCARGV.OBJ
PRINTF.OBJ
SPRINTF.OBJ
PUTS.OBJ
ALLOC.OBJ
ALLOC2.OBJ
ALLOCSUP.OBJ
STRUPLWR.OBJ
ISCTYPE.OBJ
ATOL.OBJ
STRICMP.OBJ
NEWDEL.OBJ
INITTERM.OBJ

C:\Code\VStudio\VC++9\Test>


     And here is the list I made after going through my String Class to identify all the C Runtime function calls...


TCHAR         char          wchar_t        Win32 String Functions   status
==========================================================================
new           HeapAlloc     HeapAlloc      Pietrek                    ck
_tcslen       strlen        wcslen         Fred                       ck
_tcscpy       strcpy        wcscpy         Fred                       ck
_tcsncpy      strncpy       wcsncpy        Fred                       ck
_stprintf     sprintf       swprintf       Pietrek                    ck
_tcscat       strcat        wcscat         Fred                       ck
_tcscmp       strcmp        wcscmp         Fred                       ck
_tcsncmp      strncmp       wcsncmp        Fred                       ck
_tcsnicmp     _strnicmp     _wcsnicmp      Pietrek                    ck
_tcsrev       _strrev       _wcsrev        Fred                       ck
memset                                                                ck
_abs64
abs
_tprintf
_ftprintf


     Note that my String Class uses the TCHAR macros so that I can control easily whether I want to use it with an asci build or a wide character build.  If I was smarter I'd have kept that breakdown perhaps, but alas I'm not smart enough for that.  I figured I'd be doing very well just to succeed at an asci version first.  Have I mentioned yet I don't have a wide character version of this ready yet?  Sorry, I haven't gotten to that yet.  Give me time!  But I do have x86 and x64 in asci.

     The first column above lists the functions as I found them in my string class.  The second column I listed the associated asci equivalent.  Third column shows the wide character version.  Fourth column shows the source of the function needed to implement what I found in my String Class.  Possible sources are Matt Pietrek or I.  In other words, if you see a 'Pietrek' in that column, Matt had already implemented that function in his libctiny.lib.  If you see a 'Fred' in that column, it's a function Matt didn't implement, but which is needed by my String Class, and Fred, i.e., me, is going to have to write it.  Last column I put a check mark after I made a successful resolution of that function call, i.e., it was either in the library, or I wrote it, compiled it, and added it myself.  You can see I didn't fill out a few at the bottom.  Anyway, you can see I'm proceeding in a pretty methodical manner to try to bring this project - which isn't an easy one, at least for me,  to a successful conclusion.

     Next step is to implement the functions not provided by Matt that I need, and add them to the library.  I did that individually, but let me make a recommendation for you if you want to do this.  I was able to add the functions I needed to Matt's make file, and I can provide that altered make file for you.  It actually looks like so...


#==================================================
# LIBCTINY - Matt Pietrek 1996
# Microsoft Systems Journal, October 1996
# FILE: LIBCTINY.MAK - Makefile for Microsoft version
#==================================================
PROJ = LIBCTINY

OBJS =  CRT0TCON.OBJ CRT0TWIN.OBJ DLLCRT0.OBJ ARGCARGV.OBJ PRINTF.OBJ\
        SPRINTF.OBJ PUTS.OBJ ALLOC.OBJ ALLOC2.OBJ ALLOCSUP.OBJ STRUPLWR.OBJ \
        ISCTYPE.OBJ atol.obj STRICMP.OBJ NEWDEL.OBJ INITTERM.OBJ fprintf.obj \
        _strrev.obj strcmp.obj getchar.obj strcpy.obj strlen.obj strncpy.obj \
        strcat.obj strncmp.obj _strnicmp.obj memset.obj

CC = CL
CC_OPTIONS = /c /W3 /DWIN32_LEAN_AND_MEAN

!ifdef DEBUG
CC_OPTIONS = $(CC_OPTIONS) /Zi
!else
CC_OPTIONS = $(CC_OPTIONS) /O1
!endif

$(PROJ).LIB: $(OBJS)
    LIB /OUT:$(PROJ).LIB $(OBJS)

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



     You can see the ones I added - they are all in lower case.  You can name that file whatever you want but with a .mak extension.  It'll produce a libctiny.lib once you have the required extra *.cpp files which I'll now provide. 

     While we're talking make files and dealing with libs, let me take a moment to provide some of the more useful commands for dealing with libraries and the library manager – Lib.exe.  This will dump the names of object files already in an existing library...


C:\Code\VStudio\VC15\LibCTiny\x64\Test1>Lib LibCTiny.lib /list /verbose
Microsoft (R) Library Manager Version 14.00.23506.0
Copyright (C) Microsoft Corporation.  All rights reserved.

CRT0TCON.OBJ
CRT0TWIN.OBJ
DLLCRT0.OBJ
ARGCARGV.OBJ
PRINTF.OBJ
SPRINTF.OBJ
PUTS.OBJ
ALLOC.OBJ
ALLOC2.OBJ
ALLOCSUP.OBJ
STRUPLWR.OBJ
ISCTYPE.OBJ
ATOL.OBJ
STRICMP.OBJ
NEWDEL.OBJ
INITTERM.OBJ
fprintf.obj
_strrev.obj
strcmp.obj
getchar.obj
strcpy.obj
strlen.obj
strncpy.obj
strcat.obj
strncmp.obj
_strnicmp.obj
memset.obj

C:\Code\VStudio\VC15\LibCTiny\x64\Test1>


     To create a library a syntax like this works if the library doesn't already exist...


Lib /OUT:LibCTiny.lib _strnicmp.obj _strrev.obj alloc.obj


     That command would create a new LIBCTINY.LIB and put those three obj files in it.  Of course, the three obj files need to exist in the path.  To remove an obj file from a lib use this...


Lib LibCTiny.lib /remove:alloc.obj /verbose


     That will remove alloc.obj from the lib and dump to the console screen the remaining obj files in the lib.  To replace an ob j file back in the lib do this...


Lib LibCTiny.lib alloc.obj /verbose


That will add the file to the library and dump the existing contents.

     Getting back to the project, let me provide the 1st of very many 'words of warning'.  In fprintf() below note I'm including "stdio.h" and not <stdio.h> and absolutely not <cstdio>.   Using <stdio.h> works for VC15 from VS 2008 but not with VC19 from VS2015.  The reason for that is the 'refactoring' mentioned above with regard to the UCRT (Universal C Runtime) work at Microsoft.  They've really done a number on the crt.  They've got files and dependencies scattered in so many places it's a Herculean task to figure out the paths to all of them.  So what I did was make a simpler stdio.h you can use instead.  Here it is...


// stdio.h
#ifndef stdio_h
#define stdio_h

extern "C" int    __cdecl sprintf(char* buffer, const char* format, ...);
extern "C" int    __cdecl printf(const char * format, ...);
extern "C" int    __cdecl puts(const char* s);
extern "C" char   __cdecl getchar();

#endif
 

continued...

Frederick J. Harris

How's that for simple?  Like I've said before, I'm a minimalist.  Minimalists do things like that.  Having gotten that out of the way here's fprintf.cpp...


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

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

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

return retValue;
}



Next is _strrev.cpp  (needed for some output formatting members)...



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

extern "C" char* __cdecl _strrev(char* pStr)
{
size_t iLen,iHalf;
char a,b;

iLen=strlen(pStr), iHalf=iLen/2;
for(size_t i=0; i<iHalf; i++)
{
     a=pStr[i], b=pStr[iLen-i-1];
     pStr[i]=b, pStr[iLen-i-1]=a;
}

return pStr;
}



Now strcmp.cpp ...


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

extern "C" int __cdecl strcmp(const char* string1, const char* string2)   
{
return lstrcmp(string1,string2);
}


Next getchar.cpp ...  (this is C's Waitkey$)



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

extern "C" char __cdecl getchar()
{
DWORD nRead,dwConsoleMode;
INPUT_RECORD ir[8];
bool blnLoop=true;
HANDLE hStdIn;
char c=0;

hStdIn=GetStdHandle(STD_INPUT_HANDLE);
GetConsoleMode(hStdIn,&dwConsoleMode);
SetConsoleMode(hStdIn,0);
FlushConsoleInputBuffer(hStdIn);
do
{
    WaitForSingleObject(hStdIn,INFINITE);
    ReadConsoleInput(hStdIn,ir,8,&nRead);
    for(unsigned i=0;i<nRead;i++)
    {
        if(ir[i].EventType==KEY_EVENT && ir[i].Event.KeyEvent.bKeyDown==TRUE)
        {
           c=ir[i].Event.KeyEvent.uChar.AsciiChar;
           blnLoop=false;
        }
    }
}while(blnLoop==true);
SetConsoleMode(hStdIn,dwConsoleMode);

return c;
}


Now good 'ol strcpy.cpp ...



// cl strcpy.cpp /c /W3 /DWIN32_LEAN_AND_MEAN
#include <windows.h>
#include <string.h>

extern "C" char* __cdecl strcpy(char* strDestination, const char* strSource)
{
return lstrcpy(strDestination, strSource);
}



What would we ever do without that old work horse strlen.cpp ...



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

extern "C" size_t __cdecl strlen(const char* strSource)
{
return lstrlen(strSource);
}



Now strncpy.cpp ...



//=====================================================================================
//               Developed As An Addition To Matt Pietrek's LibCTiny.lib
//                            By Fred Harris, January 2016
//
//                     cl strncpy.cpp /c /W3 /DWIN32_LEAN_AND_MEAN
//=====================================================================================
#include <windows.h>
extern "C" size_t __cdecl strlen(const char* strSource);

extern "C" char* __cdecl strncpy(char* strDest, const char* strSource, size_t iCount)   
{                                           // 3rd param is size_t
size_t iLen=strlen(strSource);             // strlen returns size_t                                           
lstrcpyn(strDest,strSource,(int)iCount);   // lstrcpyn wants an int here for 3rd param                                           
strDest[iCount-1]=strSource[iCount-1];     // so try cast
if(iCount>iLen)
{
    for(size_t i=iLen; i<iCount; i++)
        strDest[i]=0;
}

return strDest;
}



Another real work horse strcat.cpp ...



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

extern "C" char* __cdecl strcat(char* strDest, const char* strSource)   
{                                           // 3rd param is size_t
return lstrcat(strDest, strSource);
}




Here's a duzzy - strncmp.cpp ...



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

extern "C" int __cdecl strncmp(const char* str1, const char* str2, size_t count)
{
size_t iLen1=strlen(str1);
size_t iLen2=strlen(str2);
if(count>iLen1)
    return -1;
if(count>iLen2)
    return 1;
char* pStr1=(char*)malloc(count+1);
char* pStr2=(char*)malloc(count+1);
strncpy(pStr1,str1,count);
strncpy(pStr2,str2,count);
pStr1[count]=0;
pStr2[count]=0;
int iReturn=strcmp(pStr1,pStr2);
free(pStr1);
free(pStr2);

return iReturn;
}



_strnicmp.cpp - try to pronounce this one with your mouth full...



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

extern "C" int __cdecl _strnicmp(const char* str1, const char* str2, size_t count)
{
size_t iLen1=strlen(str1);
size_t iLen2=strlen(str2);
if(count>iLen1)
    return -1;
if(count>iLen2)
    return 1;
char* pStr1=(char*)malloc(count+1);
char* pStr2=(char*)malloc(count+1);
strncpy(pStr1,str1,count);
strncpy(pStr2,str2,count);
pStr1[count]=0;
pStr2[count]=0;
int iReturn=lstrcmpi(pStr1,pStr2);
free(pStr1);
free(pStr2);

return iReturn;
}


And memset, which caused me a lot of grief with VS 2015 (another couple hours lost!)...



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

extern "C" void* __cdecl memset(void* p, int c, size_t count)
{
char* pCh=(char*)p;
for(size_t i=0; i<count; i++)
     pCh[i]=(char)c;
return p;
}


Finally, here's atol.cpp.  Lost several hours on this one too.  When one decides to leave the beaten path one exposes oneself to demons and monsters just foaming at the mouth for an easy victim...


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

extern "C" long __cdecl atol ( const char * pstr )
{
int  cCurr;                       // The current character.
long lTotal;                      // The running total.
int  iIsNeg;                      // Holds the '-' sign.

while(*pstr==32)
{
    ++pstr;
}
cCurr = *pstr++;                  // Get the current character.
iIsNeg = cCurr;                   // Save the negative sign.
if(('-'==cCurr)||('+'==cCurr))
     cCurr = *pstr++ ;             // We have a sign, so skip it.
lTotal = 0 ;                      // Initialize the total.
while(cCurr>=48 && cCurr<=57)     // While we have digits, addem up.
{
     lTotal=10*lTotal+(cCurr-'0'); // Add this digit to the total.
     cCurr=*pstr++;                // Do the next character.
}
if('-'==iIsNeg)                   // If we have a negative sign, convert the value.
    return(-lTotal);
else
    return ( lTotal ) ;
}

extern "C" int __cdecl atoi ( const char * pstr )
{
return ((int)atol(pstr)) ;
}



     Well, there you have it.  If you examine the logic behind some of those functions you'll see the modus operandi is to simply hand off the parameters to equivalent Win32 functions and return their result.  Not possible in all cases.  Sometimes one must write code.  And in some cases it can get very bad.

     So if you make little *.cpp files for all those and invoke the make file as above you should end up with a libctiny.lib which can be used to create some pretty serious programs that will be seriously small.  And by small I'm not talking shaving a few k off your best results so far but rather cutting your program sizes down 25% to 75%!

     Continuing with the saga.  Once I got that far I started building up my String Class one method at a time.  I figured there was no chance of the whole thing just compiling/linking  'as is', so a piecemeal approach would allow me to more easily determine the source and resolution of the inevitable errors.  But we need not go through that brutality here.  I'll just give you the whole thing which took a lot of work but does work.  Here is my String.h class header as it now stands.  In the comments after each member is enough information to allow you to know how to use it...


Continued...

Frederick J. Harris


// Strings.h
#ifndef Strings_h
#define Strings_h

#ifndef ssize_t
typedef SSIZE_T ssize_t;             // ssize_t is defined in GCC, but isn't defined in VC9-15, but rather SSIZE_T.  For symetry we'll define it.
#endif
#include "stdio.h"

#define MINIMUM_ALLOCATION     16    // allocate at least this for every String
#define EXPANSION_FACTOR        2    // repeated concatenations will keep doubling buffer
#define NUMERIC_CONVERSIONS          // allows direct assignment of numeric values to Strings, e.g., String s(3.14159) or s=3.14159;
#define FORMAT_MONEY                 // put commas every three places, rounding, left/right justification, specify field sizes (padding), etc.
#define CONSOLE_OUTPUT               // support console output, i.e., enable String::Print()
#define                       x64

class String
{
public:                                        // Constructors (8)
String();                                      // Uninitialized Constructor
String(const int iSize, bool blnNullOut);      // Constructor creates String of size iSize and optionally nulls out
String(const char ch);                         // Constructor creates a String initialized with a char, e.g., String c('A');
String(const char* pStr);                      // Constructor: Initializes with char*, e.g. s1 = "PowerBASIC! Compile Without Compromise!"
String(const String& strAnother);              // Constructor creates String initialized with another already existing String, e.g., String s2(s1);
#ifdef NUMERIC_CONVERSIONS
    String(int iNumber);                        // Constructor creates String initialized with an int, e.g., String s1(2468); kind of Str$(2468) in PowerBASIC
    String(unsigned int uNumber);               // Constructor creates String initialized with an unsigned int, e.g., String s1(2468); kind of Str$(2468) in PowerBASIC
    #ifdef x64
       String(size_t  uiNumber);                // Constructor creates String from 64 bit unsigned number, e.g., String strBigNum(12345678987654);
       String(ssize_t iNumber);                 // Constructor creates String from 64 bit signed number, e.g., String strBigNum(-12345678987654);
    #endif
    String(double dblNumber);                   // Constructor creates String from floating point number, e.g., String s(3.14159);
#endif
String& operator=(const char c);               // Assign a char to a String, e.g., String s='A';
String& operator=(const char* pChar);          // Assign a character string to a String Object, e.g.,  String s1 = "Compile Without Compromise";
String& operator=(const String& strAnother);   // Assign an already existing String to this one, e.g., String s2 = s1;
#ifdef NUMERIC_CONVERSIONS
    String& operator=(int iNumber);             // Assign an          int converted to a String to this, e.g., String s1 = -123456789;
    String& operator=(unsigned int uNumber);    // Assign an unsigned int converted to a String to this, e.g., String s1 =  123456789;
    #ifdef x64
       String& operator=(size_t  uNumber);      // Assign a 64 bit unsigned quantity converted to a String to this, e.g., String s2 =  12345678987654;
       String& operator=(ssize_t iNumber);      // Assign a 64 bit   signed quantity converted to a String to this, e.g., String s2 = -12345678987654;
    #endif
    String& operator=(double dblNumber);        // Assign a double converted to a String to this, e.g., String strDouble = 3.14159;
#endif
String operator+(const TCHAR ch);              // Concatenates or adds a character to an already existing String, e.g., s1 = s1 + 'A';
String operator+(const TCHAR* pChar);          // Concatenates or adds a character array (char*) to an already existing String, e.g., s1 = s1 + lpText;
String operator+(String& s);                   // Concatenates or adds another String to this one, e.g., s1 = s1 + s2;
bool operator==(String& s);                    // Compares two Strings for case sensitive equality
bool operator==(const char* pChar);            // Compares a String against a char* for case sensitive equality
bool operator!=(char* pChar);                  // Compares a String against a char* for case sensitive inequality
void LTrim();                                  // Removes leading white space by modifying existing String
void RTrim();                                  // Removes trailing white space by modifying existing String
void Trim();                                   // Removes leading or trailing white space from existing String
String Left(size_t iCntChars);                 // Returns a String consisting of left-most iCntChars of this
String Right(size_t iCntChars);                // Returns a String consisting of right-most iCntChars of this
String Mid(size_t iStart, size_t iCount);      // Returns a String consisting of iCount characters of this starting at one based iStart
int InStr(const char* pStr, bool blnCaseSensitive, bool blnStartLeft);   // Returns one based position of pStr in this by case sensitivity and left/right start
int InStr(const String& str, bool blnCaseSensitive, bool blnStartLeft);  // Returns one based position of String in this by case sensitivity and left/right start
int ParseCount(const char ch);                                           // Returns count of delimited fields as specified by char delimiter, i.e., comma, space, tab, etc.
void Parse(String* pStrs, char cDelimiterh, size_t iParseCount);         // Parses this based on delimiter.  Must pass in 1st parameter String* to sufficient number of Strings
String Replace(char* pToRevove, char* pReplacement);                     // Replaces pToRemove with pReplacement in new String.  Replacement can cause String to grow
String Remove(const char* pCharsToRemove);                               // Returns A String With All The chars In A char* Removed (Individual char removal)
String Remove(const char* pStrToRemove, bool bCaseSensitive);            //Returns a String with 1st parameter removed.  2nd is bool for case sensitivity.
#ifdef FORMAT_MONEY
    void Format(double dblNumber, size_t iFldLen, size_t iDecPlaces);                   // this contains dblNum converted to String, in iFldLen field width, with iDecPlaces to right of dec.
    void Format(double dblNum, int iFldWth, int iDecimal, bool blnRightJustified);      // same as above but allows right/left field justification
    void Format(size_t iNumber, int iFldLen, char cSeperator, bool blnRightJustified);  // For integral numbers; can specify justification, field width, and seperator for thousands place
    void Format(ssize_t iNumber, int iFldLen, char cSeperator, bool blnRightJustified); // Same as above for signed numbers
    void Money(double dblNum, size_t iFldLen, size_t iOffsetDollarSign);                // Specifically for money, can specify where the dollar sign goes in field
    void Money(double dblNum, size_t iFldLen, bool blnDollarSign);                      // Specifically for money, can specify whether a dollar sign is put before each amount
#endif
#ifdef CONSOLE_OUTPUT
    void Print(bool blnCrLf);
    void Print(const char* pStr, bool blnCrLf);
#endif
char* lpStr();
int Len();                                                                             // accesor for String::iLen member
int Capacity();                                                                        // Will be one less than underlying memory allocation
~String();   //String Destructor

private:
char*  lpBuffer;
size_t iLen;
size_t iCapacity;
};

String operator+(char* lhs, String& rhs);
#endif


     If you know anything about String Classes, you'll immediately see it is considerably different from std::string.  It is based on simple null terminated strings, and manages quite transparently the underlying char* buffer.  Unlike basic style dynamic strings allocated with the OLE String Engine, or the C++ Standard Library string class, nulls within the String aren't supported.  A null terminates the String.  Also, as I've previously intimated, it contains functionality not present natively in the C Std. Lib. string class, i.e., formatting, replaces where the underlying buffer can grow (String::Replace(...)), etc.  But I'd say it is nonetheless pretty minimalist, which suits my temperament.  Its underlying purpose is to allow me to code in C++ much like in C but without the bloat, and with basic style string handling.

     Not much good without the Strings.cpp implementation file, so here is that (its big!) ...


// Strings.cpp
#include  <windows.h>
#include  <string.h>
#include  "stdio.h"
#include  "Strings.h"

#ifdef NUMERIC_CONVERSIONS
   extern HINSTANCE hLib;
   extern int (*pSPrtf)(char*, const char*, ... );
   #ifdef x64
      extern ssize_t (*pAbs64)(ssize_t);
      extern ssize_t (*pAtoi64)(const char*);
   #else
      extern ssize_t (*pAbs)(ssize_t);
   #endif
#else
   #ifdef FORMAT_MONEY
      extern HINSTANCE hLib;
      extern int (*pSPrtf)(char*, const char*, ... );
      #ifdef x64
         extern ssize_t (*pAbs64)(ssize_t);
         extern ssize_t (*pAtoi64)(const char*);
      #else
         extern ssize_t (*pAbs)(ssize_t);
      #endif
   #endif
#endif


String operator+(char* lhs, String& rhs)              //global function
{
String sr=lhs;
sr=sr+rhs;

return sr;
}


String::String()                                      // Uninitialized Constructor
{
lpBuffer=(char*)operator new(MINIMUM_ALLOCATION);
this->lpBuffer=new char[MINIMUM_ALLOCATION];
lpBuffer[0]='\0';
this->iCapacity=MINIMUM_ALLOCATION-1;
this->iLen=0;
}


String::String(const int iSize, bool blnFillNulls)  //Constructor Creates String With Custom Sized
{                                                   //Buffer (rounded up to paragraph boundary)
int iNewSize=(iSize/16+1)*16;
this->lpBuffer=new char[iNewSize];
this->iCapacity=iNewSize-1;
this->iLen=0;
this->lpBuffer[0]='\0';
if(blnFillNulls)
{
    for(size_t i=0; i<this->iCapacity; i++)
        this->lpBuffer[i]=0;
}
}


String::String(const char ch)  //Constructor: Initializes with char
{
this->iLen=1;
int iNewSize=MINIMUM_ALLOCATION;
this->lpBuffer=new char[iNewSize];
this->iCapacity=iNewSize-1;
this->lpBuffer[0]=ch, this->lpBuffer[1]='\0';
}


String::String(const char* pStr)  //Constructor: Initializes with char*
{
this->iLen=(int)strlen(pStr);
int iNewSize=(this->iLen/16+1)*16;
this->lpBuffer=new char[iNewSize];
this->iCapacity=iNewSize-1;
strcpy(lpBuffer,pStr);
}


String::String(const String& s)  //Constructor Initializes With Another String, i.e., Copy Constructor
{
int iNewSize=(s.iLen/16+1)*16;
this->iLen=s.iLen;
this->lpBuffer=new char[iNewSize];
this->iCapacity=iNewSize-1;
strcpy(this->lpBuffer,s.lpBuffer);
}


#ifdef NUMERIC_CONVERSIONS
   #ifdef x64
      String::String(size_t iNum)
      {
       this->lpBuffer=new char[32];
       this->iCapacity=31;
       this->iLen=sprintf(this->lpBuffer,"%Iu",iNum);
      }


      String::String(ssize_t iNum)
      {
       this->lpBuffer=new char[32];
       this->iCapacity=31;
       this->iLen=sprintf(this->lpBuffer,"%Id",iNum);
      }
   #endif


   String::String(int iNum)
   {
    this->lpBuffer=new char[32];
    this->iCapacity=15;
    this->iLen=sprintf(this->lpBuffer,"%d",iNum);
   }


   String::String(unsigned int iNum)
   {
    this->lpBuffer=new char[16];
    this->iCapacity=15;
    this->iLen=sprintf(this->lpBuffer,"%u",iNum);
   }


   String::String(double dblNumber)
   {
    char szBuffer[48];

    this->iLen=pSPrtf(szBuffer,"%16.8f",dblNumber);
    int iNewSize=(this->iLen/16+1)*16;
    this->lpBuffer=new char[iNewSize];
    this->iCapacity=iNewSize-1;
    strcpy(this->lpBuffer,szBuffer);
   }
#endif


String& String::operator=(const char ch)
{
this->lpBuffer[0]=ch, this->lpBuffer[1]='\0';
this->iLen=1;
return *this;
}


String& String::operator=(const char* pStr)  // Assign TCHAR* to String
{
size_t iNewLen=strlen(pStr);
if(iNewLen>this->iCapacity)
{
    printf("  Got in Where iNewLen > this->iCapacity\n");
    delete [] this->lpBuffer;
    int iNewSize=(iNewLen*EXPANSION_FACTOR/16+1)*16;
    this->lpBuffer=new char[iNewSize];
    this->iCapacity=iNewSize-1;
}
strcpy(this->lpBuffer,pStr);
this->iLen=iNewLen;

return *this;
}


String& String::operator=(const String& strAnother)
{
if(this==&strAnother)
    return *this;
if(strAnother.iLen>this->iCapacity)
{
    delete [] this->lpBuffer;
    int iNewSize=(strAnother.iLen*EXPANSION_FACTOR/16+1)*16;
    this->lpBuffer=new char[iNewSize];
    this->iCapacity=iNewSize-1;
}
strcpy(this->lpBuffer,strAnother.lpBuffer);
this->iLen=strAnother.iLen;

return *this;
}


#ifdef NUMERIC_CONVERSIONS
   #ifdef x64
      String& String::operator=(size_t iNum)
      {
       if(this->iCapacity>=32)
          this->iLen=sprintf(this->lpBuffer,"%Iu",iNum);
       else
       {
          delete [] this->lpBuffer;
          this->lpBuffer=new char[32];
          this->iCapacity=31;
          this->iLen=sprintf(this->lpBuffer,"%Iu",iNum);
       }

       return *this;
      }


      String& String::operator=(ssize_t iNum)
      {
       if(this->iCapacity>=32)
          this->iLen=sprintf(this->lpBuffer,"%Id",iNum);
       else
       {
          delete [] this->lpBuffer;
          this->lpBuffer=new char[32];
          this->iCapacity=31;
          this->iLen=sprintf(this->lpBuffer,"%Id",iNum);
       }

       return *this;
      }
   #endif


   String& String::operator=(int iNum)
   {
    if(this->iCapacity>=15)
       this->iLen=sprintf(this->lpBuffer,"%d",iNum);
    else
    {
       delete [] this->lpBuffer;
       this->lpBuffer=new char[16];
       this->iCapacity=15;
       this->iLen=sprintf(this->lpBuffer,"%d",iNum);
    }

    return *this;
   }


   String& String::operator=(unsigned int iNum)
   {
    if(this->iCapacity>=15)
       this->iLen=sprintf(this->lpBuffer,"%u",iNum);
    else
    {
       delete [] this->lpBuffer;
       this->lpBuffer=new char[16];
       this->iCapacity=15;
       this->iLen=sprintf(this->lpBuffer,"%u",iNum);
    }

    return *this;
   }


   String& String::operator=(double dblNumber)
   {
    if(this->iCapacity>=31)
       this->iLen=pSPrtf(this->lpBuffer,"%16.8f",dblNumber);
    else
    {
       delete [] this->lpBuffer;
       this->lpBuffer=new char[32];
       this->iLen=pSPrtf(this->lpBuffer,"%16.8f",dblNumber);
       this->iCapacity=31;
    }

    return *this;
   }
#endif


String String::operator+(const char ch)
{
int iNewLen=this->iLen+1;

String s(iNewLen,false);
strcpy(s.lpBuffer,this->lpBuffer);
s.lpBuffer[iNewLen-1]=ch;
s.lpBuffer[iNewLen]='\0';
s.iLen=iNewLen;

return s;
}


String String::operator+(const char* pStr)
{
int iNewLen=strlen(pStr)+this->iLen;
String s(iNewLen,false);
strcpy(s.lpBuffer,this->lpBuffer);
strcat(s.lpBuffer,pStr);
s.iLen=iNewLen;

return s;
}


String String::operator+(String& strRef)
{
int iNewLen=strRef.iLen+this->iLen;
String s(iNewLen,false);
strcpy(s.lpBuffer,this->lpBuffer);
strcat(s.lpBuffer,strRef.lpBuffer);
s.iLen=iNewLen;

return s;
}


bool String::operator==(String& strRef)
{
if(strcmp(this->lpStr(),strRef.lpStr())==0)
    return true;
else
    return false;
}


bool String::operator==(const char* pStr)
{
if(strcmp(this->lpStr(),pStr)==0)
    return true;
else
    return false;
}


bool String::operator!=(char* pStr)
{
if(strcmp(this->lpStr(),(char*)pStr)==0)
    return false;
else
    return true;
}


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

for(size_t i=0; i<this->iLen; i++)
{
     if(this->lpBuffer[i]==9||this->lpBuffer[i]==10||this->lpBuffer[i]==13||this->lpBuffer[i]==32)
        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;
}


void String::RTrim()
{
int iCt=0;

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


void String::Trim()
{
this->LTrim();
this->RTrim();
}


String String::Left(size_t iNum)   //  strncpy = _tcsncpy
{
if(iNum<this->iLen)
{
    size_t iNewSize=(iNum*EXPANSION_FACTOR/16+1)*16;
    String sr(iNewSize,false);
    strncpy(sr.lpBuffer,this->lpBuffer,iNum);
    sr.lpBuffer[iNum]=0;
    sr.iLen=iNum;
    return sr;
}
else
{
    String sr=*this;
    sr.iLen=this->iLen;
    return sr;
}
}


String String::Right(size_t iNum)  //Returns Right$(strMain,iNum)
{
if(iNum<this->iLen)
{
    size_t iNewSize=(iNum*EXPANSION_FACTOR/16+1)*16;
    String sr(iNewSize,false);
    strncpy(sr.lpBuffer,this->lpBuffer+this->iLen-iNum,iNum);
    sr.lpBuffer[iNum]='\0';
    sr.iLen=iNum;
    return sr;
}
else
{
    String sr=*this;
    sr.iLen=this->iLen;
    return sr;
}
}


String String::Mid(size_t iStart, size_t iCount)
{
if(iStart<1)
{
    String sr;
    return sr;
}
if(iCount+iStart>this->iLen)
    iCount=this->iLen-iStart+1;
String sr(iCount,false);
strncpy(sr.lpBuffer,this->lpBuffer+iStart-1,iCount);
sr.lpBuffer[iCount]='\0';
sr.iLen=iCount;

return sr;
}


int iMatch(char* pThis, const char* pStr, bool blnCaseSensitive, bool blnStartBeginning, int i, int iParamLen)
{
if(blnCaseSensitive)
{
    if(strncmp(pThis+i,pStr,iParamLen)==0)   //_tcsncmp
       return i+1;
    else
       return 0;
}
else
{
    if(_strnicmp(pThis+i,pStr,iParamLen)==0)  //__tcsnicmp
       return i+1;
    else
       return 0;
}
}


int String::InStr(const char* pStr, bool blnCaseSensitive, bool blnStartBeginning)
{
int i,iParamLen,iRange,iReturn;

if(*pStr==0)
    return 0;
iParamLen=strlen(pStr);
iRange=this->iLen-iParamLen;
if(blnStartBeginning)
{
    if(iRange>=0)
    {
       for(i=0; i<=iRange; i++)
       {
           iReturn=iMatch(this->lpBuffer,pStr,blnCaseSensitive,blnStartBeginning,i,iParamLen);
           if(iReturn)
              return iReturn;
       }
    }
}
else
{
    if(iRange>=0)
    {
       for(i=iRange; i>=0; i--)
       {
           iReturn=iMatch(this->lpBuffer,pStr,blnCaseSensitive,blnStartBeginning,i,iParamLen);
           if(iReturn)
              return iReturn;
       }
    }
}

return 0;
}


int String::InStr(const String& s, bool blnCaseSensitive, bool blnStartBeginning)
{
int i,iParamLen,iRange,iReturn;

if(s.iLen==0)
    return 0;
iParamLen=s.iLen;
iRange=this->iLen-iParamLen;
if(blnStartBeginning)
{
    if(iRange>=0)
    {
       for(i=0; i<=iRange; i++)
       {
           iReturn=iMatch(this->lpBuffer,s.lpBuffer,blnCaseSensitive,blnStartBeginning,i,iParamLen);
           if(iReturn)
              return iReturn;
       }
    }
}
else
{
    if(iRange>=0)
    {
       for(i=iRange; i>=0; i--)
       {
           iReturn=iMatch(this->lpBuffer,s.lpBuffer,blnCaseSensitive,blnStartBeginning,i,iParamLen);
           if(iReturn)
              return iReturn;
       }
    }
}

return 0;
}


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

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

return ++iCtr;
}


void String::Parse(String* pStr, char delimiter, size_t iParseCount)
{
char* pBuffer=(char*)operator new(this->iLen+1);
if(pBuffer)
{
    char* p=pBuffer;
    char* c=this->lpBuffer;
    while(*c)
    {
       if(*c==delimiter)
          *p=0;
       else
          *p=*c;
       p++, c++;
    }
    *p=0, p=pBuffer;
    for(size_t i=0; i<iParseCount; i++)
    {
        pStr[i]=p;
        p=p+pStr[i].iLen+1;
    }
    operator delete(pBuffer);
}
}


String String::Replace(char* pMatch, char* pNew)  //strncmp = _tcsncmp
{
size_t i,iLenMatch,iLenNew,iCountMatches,iExtra,iExtraLengthNeeded,iAllocation,iCtr;
iLenMatch=strlen(pMatch);
iCountMatches=0, iAllocation=0, iCtr=0;
iLenNew=strlen(pNew);
if(iLenNew==0)
{
    String sr=this->Remove(pMatch,true); //return
    return sr;
}
else
{
    iExtra=iLenNew-iLenMatch;
    for(i=0; i<this->iLen; i++)
    {
        if(strncmp(lpBuffer+i,pMatch,iLenMatch)==0)
           iCountMatches++;  //Count how many match strings
    }
    iExtraLengthNeeded=iCountMatches*iExtra;
    iAllocation=this->iLen+iExtraLengthNeeded;
    String sr(iAllocation,false);
    for(i=0; i<this->iLen; i++)
    {
        if(strncmp(this->lpBuffer+i,pMatch,iLenMatch)==0)
        {
           strcpy(sr.lpBuffer+iCtr,pNew);
           iCtr+=iLenNew;
           i+=iLenMatch-1;
        }
        else
        {
           sr.lpBuffer[iCtr]=this->lpBuffer[i];
           iCtr++;
        }
        sr.lpBuffer[iCtr]='\0';
    }
    sr.iLen=iCtr;
    return sr;
}
}


String String::Remove(const char* pStr)    // Individual character removal, i.e., if this
{                                          // was passed in as pStr...
unsigned int i,j,iStrLen,iParamLen;       //
char *pThis, *pThat, *p;                  // abcdefg
bool blnFoundBadTCHAR;                    //
                                           // ...then this would be returned in another
iStrLen=this->iLen;                       // string
String sr((int)iStrLen,false);            //
iParamLen=strlen(pStr);                   // aceg
pThis=this->lpBuffer;                     //
p=sr.lpStr();
for(i=0; i<iStrLen; i++)
{
     pThat=(char*)pStr;
     blnFoundBadTCHAR=false;
     for(j=0; j<iParamLen; j++)
     {
         if(*pThis==*pThat)
         {
            blnFoundBadTCHAR=true;
            break;
         }
         pThat++;
     }
     if(!blnFoundBadTCHAR)
     {
        *p=*pThis;
        p++;
        *p='\0';
     }
     pThis++;
}
sr.iLen=strlen(sr.lpStr());

return sr;
}


String String::Remove(const char* pMatch, bool blnCaseSensitive)
{
size_t i,iCountMatches=0,iCtr=0;

size_t iLenMatch=strlen(pMatch);
for(i=0; i<this->iLen; i++)
{
     if(blnCaseSensitive)
     {
        if(strncmp(lpBuffer+i,pMatch,iLenMatch)==0)  //_tcsncmp
           iCountMatches++;
     }
     else
     {
        if(_strnicmp(lpBuffer+i,pMatch,iLenMatch)==0) //__tcsnicmp
           iCountMatches++;
     }
}
int iAllocation=this->iLen-(iCountMatches*iLenMatch);
String sr(iAllocation,false);
for(i=0; i<this->iLen; i++)
{
     if(blnCaseSensitive)
     {
        if(strncmp(this->lpBuffer+i,pMatch,iLenMatch)==0)
           i+=iLenMatch-1;
        else
        {
           sr.lpBuffer[iCtr]=this->lpBuffer[i];
           iCtr++;
        }
        sr.lpBuffer[iCtr]='\0';
     }
     else
     {
        if(_strnicmp(this->lpBuffer+i,pMatch,iLenMatch)==0)
           i+=iLenMatch-1;
        else
        {
           sr.lpBuffer[iCtr]=this->lpBuffer[i];
           iCtr++;
        }
        sr.lpBuffer[iCtr]='\0';
     }
}
sr.iLen=iCtr;
return sr;
}


#ifdef FORMAT_MONEY
   void String::Format(double dblNumber, size_t iFldLen, size_t iDecPlaces)
   {
    char szNumber[24], szFormat[8], szDecPlaces[4], szDecimal[16];
    size_t iLen=0,iDot=0;
    SSIZE_T iNumber=0;
    SSIZE_T iDiff=0;
    String strTmp;

    memset(szNumber,0,24*sizeof(char));
    memset(szFormat,0,8*sizeof(char));
    memset(szDecPlaces,0,4*sizeof(char));
    memset(szDecimal,0,16*sizeof(char));
    #ifdef x64
       pSPrtf(szDecPlaces,"%llu",iDecPlaces);
    #else
       pSPrtf(szDecPlaces,"%u",iDecPlaces);
    #endif
    strcpy(szFormat,(char*)"%0.");
    strcat(szFormat,szDecPlaces);
    strcat(szFormat,(char*)"f");
    iLen=pSPrtf(szNumber,szFormat,dblNumber);
    if(iDecPlaces)  // At Least 1 Digit To Right Of Decimal Point
    {
       iNumber=(SSIZE_T)dblNumber;
       for(size_t i=0; i<iLen; i++)
       {
           if(szNumber[i]=='.')
           {
              iDot=i;
              break;
           }
       }
       strncpy(szDecimal,szNumber+iDot+1,iDecPlaces);
       if(dblNumber>=0)
          strTmp.Format(iNumber,0,',',true);
       else
       {
          if(iNumber==0)
          {
             strTmp.Format(iNumber,0,',',true);
             strTmp=(char*)"-"+strTmp;
          }
          else
             strTmp.Format(iNumber,0,',',true);
       }
       strTmp=strTmp+(char*)".";
       strTmp=strTmp+szDecimal;
       iLen=strTmp.Len();
       if(this->iCapacity<iLen || this->iCapacity<iFldLen)  // Code To Reallocate If this->lpBuffer Isn't Big Enough
       {
          size_t iNewSize,iNewLen;
          delete [] this->lpBuffer;
          iNewLen=iLen+iFldLen;
          iNewSize = (iNewLen/16 + 1) * 16;
          this->lpBuffer=new char[iNewSize];
          this->iCapacity=iNewSize-1;
       }
       iDiff=iFldLen-iLen;
       if(iDiff>0)
       {
          for(size_t i=0; i<(size_t)iDiff; i++)
              this->lpBuffer[i]=' ';
          this->lpBuffer[iDiff]=NULL;
       }
       else
          this->lpBuffer[0]=NULL;
       strcat(this->lpBuffer,strTmp.lpStr());
       this->iLen=strlen(this->lpBuffer);
    }
    else // No Decimal Places
    {    // pAtoi64=(ssize_t (*)(const char*))GetProcAddress(hLib,"_atoi64");
       #ifdef x64
          strTmp.Format((SSIZE_T)pAtoi64(szNumber),0,',',true);
       #else
          strTmp.Format((SSIZE_T)atoi(szNumber),0,',',true);
       #endif
       iLen=strTmp.Len();
       if(this->iCapacity<iLen || this->iCapacity<iFldLen)  // Code To Reallocate If this->lpBuffer Isn't Big Enough
       {
          size_t iNewSize,iNewLen;
          delete [] this->lpBuffer;
          iNewLen=iLen+iFldLen;
          iNewSize = (iNewLen/16 + 1) * 16;
          this->lpBuffer=new char[iNewSize];
          this->iCapacity=iNewSize-1;
       }
       iDiff=iFldLen-iLen;
       if(iDiff>0)
       {
          for(size_t i=0; i<(size_t)iDiff; i++)
              this->lpBuffer[i]=' ';
          this->lpBuffer[iDiff]=NULL;
       }
       else
          this->lpBuffer[0]=NULL;
       strcat(this->lpBuffer,strTmp.lpStr());
    }
    this->iLen=strlen(this->lpBuffer);
   }


   void String::Format(double dblNum, int iFldWth, int iDecimal, bool blnRightJustified)  // This one doesn't insert commas
   {                                                                                      // every three digits
    if(iFldWth>0 && iFldWth<32)
    {
       if(iDecimal>=0 && iDecimal<19)
       {
          if(this->iCapacity<32)
          {
             delete [] this->lpBuffer;
             lpBuffer=new char[32];
             this->iCapacity=31;
          }
          String sFormat;
          if(blnRightJustified)
             sFormat=(char*)"%";
          else
             sFormat=(char*)"%-";
          String strFldWth(iFldWth);
          String strDecimalPoints(iDecimal);
          sFormat=sFormat+strFldWth+(char*)"."+strDecimalPoints+(char*)"f";
          this->iLen=pSPrtf(this->lpBuffer,sFormat.lpStr(),dblNum);  // either of these work
       }
    }
   }


   void String::Format(size_t iNumber, int iFldLen, char cSeperator, bool blnRightJustified)
   {
    char szBuf1[24];
    char szBuf2[24];
    size_t iDigit=0;
    size_t iLen=0;
    size_t iCtr=1;
    size_t j=0;

    memset(szBuf1,0,24*sizeof(char));
    memset(szBuf2,0,24*sizeof(char));
    #ifdef x64
       pSPrtf(szBuf1,"%Iu",iNumber);
    #else
       pSPrtf(szBuf1,"%Iu",iNumber);
    #endif
    _strrev(szBuf1);
    iLen=strlen(szBuf1);
    for(size_t i=0; i<iLen; i++)
    {
        if(iCtr==3)
        {
           iDigit++;
           szBuf2[j]=szBuf1[i];
           if(iDigit<iLen)
           {
              j++;
              szBuf2[j]=cSeperator;
           }
           j++, iCtr=1;
        }
        else
        {
           iDigit++;
           szBuf2[j]=szBuf1[i];
           j++, iCtr++;
        }
    }
    _strrev(szBuf2);                    // Done With Creating String With Commas
    memset(szBuf1,0,24*sizeof(char));   // Reuse szBuf1
    strcpy(szBuf1,szBuf2);              // szBuf1 Now Contains Comma Delimited Positive Value, but No Field Paddings Or R/L Justification
    size_t iRequiredBytes;              // Find out which of two is larger - length of string necessary
    iLen=strlen(szBuf1);                // or iFldLen
    if(iFldLen>(int)iLen)
       iRequiredBytes=iFldLen;
    else
       iRequiredBytes=iLen;
    if(iRequiredBytes>(size_t)this->iCapacity)
    {
       delete [] this->lpBuffer;
       int iNewSize=(iRequiredBytes*EXPANSION_FACTOR/16+1)*16;
       this->lpBuffer=new char[iNewSize];
       this->iCapacity=iNewSize-1;
    }
    memset(this->lpBuffer,0,this->iCapacity*sizeof(char));  // Now, at this point, szBuf1 Is Holding reformatted value, whether plus or minus, But no leading or trailing spaces.  And this->lpBuffer is large enough.
    SSIZE_T iDifference=iFldLen-iLen;                       // space = 32;  $ = 36;  , = 44
    if(blnRightJustified)
    {
       if(iDifference > 0)
       {
          for(size_t i=0; i<(size_t)iDifference; i++)
              this->lpBuffer[i]=' ';  // 32
       }
       strcat(this->lpBuffer,szBuf1);
    }
    else
    {
       strcpy(this->lpBuffer,szBuf1);
       if(iDifference>0)
       {
          for(size_t i=iLen; i<iDifference+iLen; i++)
              this->lpBuffer[i]=' ';  // 32
       }
    }
    this->iLen=strlen(this->lpBuffer);
   }


   void String::Format(ssize_t iNumber, int iFldLen, char cSeperator, bool blnRightJustified)
   {
    bool blnPositive;
    char szBuf1[24];
    char szBuf2[24];
    size_t iDigit=0;
    size_t iLen=0;
    size_t iCtr=1;
    size_t j=0;

    memset(szBuf1,0,24*sizeof(char));
    memset(szBuf2,0,24*sizeof(char));
    if(iNumber<0)
       blnPositive=false;
    else
       blnPositive=true;
    #ifdef x64
       iNumber=pAbs64(iNumber);
    #else
       iNumber=pAbs(iNumber);
    #endif
    #ifdef x64
       pSPrtf(szBuf1,"%Iu",(size_t)iNumber);
    #else
       pSPrtf(szBuf1,"%u",(size_t)iNumber);
    #endif
    _strrev(szBuf1);
    iLen=strlen(szBuf1);
    for(size_t i=0; i<iLen; i++)
    {
        if(iCtr==3)
        {
           iDigit++;
           szBuf2[j]=szBuf1[i];
           if(iDigit<iLen)
           {
              j++;
              szBuf2[j]=cSeperator;
           }
           j++, iCtr=1;
        }
        else
        {
           iDigit++;
           szBuf2[j]=szBuf1[i];
           j++, iCtr++;
        }
    }
    _strrev(szBuf2); // Done With Creating String With Commas

    memset(szBuf1,0,24*sizeof(char));  // Reuse szBuf1
    if(blnPositive)
       strcpy(szBuf1,szBuf2);
    else
    {
       szBuf1[0]='-';
       strcat(szBuf1,szBuf2);
    }   // szBuf1 Now Contains Comma Delimited Positive Or Negative Value, but No Field Paddings Or R/L Justification

    size_t iRequiredBytes;         // Find out which of two is larger - length of string necessary
    iLen=strlen(szBuf1);           // or iFldLen
    if(iFldLen>(int)iLen)
       iRequiredBytes=iFldLen;
    else
       iRequiredBytes=iLen;

    if(iRequiredBytes>(size_t)this->iCapacity)
    {
       delete [] this->lpBuffer;
       int iNewSize=(iRequiredBytes*EXPANSION_FACTOR/16+1)*16;
       this->lpBuffer=new char[iNewSize];
       this->iCapacity=iNewSize-1;
    }
    memset(this->lpBuffer,0,this->iCapacity*sizeof(char));   // Now, at this point, szBuf1 Is Holding reformatted value, whether plus or minus,
    SSIZE_T iDifference=iFldLen-iLen; // space = 32;  $ = 36;  , = 44   But no leading or trailing spaces.  And this->lpBuffer is large enough.
    if(blnRightJustified)
    {
       if(iDifference > 0)
       {
          for(size_t i=0; i<(size_t)iDifference; i++)
              this->lpBuffer[i]=' ';  // 32
       }
       strcat(this->lpBuffer,szBuf1);
    }
    else
    {
       strcpy(this->lpBuffer,szBuf1);
       if(iDifference>0)
       {
          for(size_t i=iLen; i<iDifference+iLen; i++)
              this->lpBuffer[i]=' ';  // 32
       }
    }
    this->iLen=strlen(this->lpBuffer);
   }


   void String::Money(double dblAmount, size_t iFldLen, bool blnDollarSign)
   {
    size_t iLen,iCharactersNeeded,iDifference;
    String strAmount;

    strAmount.Format(dblAmount,0,2);
    if(blnDollarSign)
       strAmount=(char*)"$"+strAmount;
    iLen=strAmount.iLen;
    if(iLen>iFldLen)
       iCharactersNeeded=iLen;
    else
       iCharactersNeeded=iFldLen;
    if(this->iCapacity<iCharactersNeeded)
    {
       delete [] this->lpBuffer;
       size_t iNewSize = (iCharactersNeeded/16 + 1) * 16;
       this->lpBuffer=new char[iNewSize];
       this->iCapacity=iNewSize-1;
    }
    if(iLen>iFldLen)
       iDifference=0;
    else
       iDifference=iFldLen-iLen;
    for(size_t i=0; i<iDifference; i++)
        this->lpBuffer[i]=32;
    this->lpBuffer[iDifference]='\0';
    strcat(this->lpBuffer,strAmount.lpStr());
    this->iLen=strlen(lpBuffer);
   }


   void String::Money(double dblAmount, size_t iFldLen, size_t iOneBasedOffsetDollarSign)
   {
    size_t iLen,iCharactersNeeded,iDifference;
    String strAmount;

    strAmount.Money(dblAmount,0,false);
    iLen=strAmount.iLen;
    if(iLen>iFldLen)
       iCharactersNeeded=iLen;
    else
       iCharactersNeeded=iFldLen;
    if(this->iCapacity<iCharactersNeeded)
    {
       delete [] this->lpBuffer;
       size_t iNewSize = (iCharactersNeeded/16 + 1) * 16;
       this->lpBuffer=new char[iNewSize];
       this->iCapacity=iNewSize-1;
    }
    if(iLen>iFldLen)
       iDifference=0;
    else
       iDifference=iFldLen-iLen;
    for(size_t i=0; i<iDifference; i++)
        this->lpBuffer[i]=32;
    this->lpBuffer[iDifference]='\0';
    strcat(this->lpBuffer,strAmount.lpStr());
    if(iOneBasedOffsetDollarSign && iOneBasedOffsetDollarSign<=iFldLen)
       this->lpBuffer[iOneBasedOffsetDollarSign-1]='$';
    this->iLen=strlen(lpBuffer);
   }
#endif


void String::Print(bool blnCrLf)
{
printf("%s",lpBuffer);
if(blnCrLf)
    printf("\n");
}


void String::Print(const char* pStr, bool blnCrLf)
{
printf("%s%s",pStr,lpBuffer);
if(blnCrLf)
    printf("\n");
}


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


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


int String::Capacity(void)
{
return this->iCapacity;
}


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


     Well, there it is.  All that got compiled into our 5632 byte C++ "Hello, World!" program!  But its time to start airing out the 'dirty linen' (English colloquialism, means coming clean or confessing to nasty deeds one has performed, i.e., telling the truth).  There were problems in doing this, to put it mildly.  Pretty big problems!  Although not at first.  Fact is, I almost had the whole thing working before I hit THE BIG PROBLEM.  And here it is - the problem is floating point numbers.  That's the Achilles heel of this whole thing.  To describe the problem lets first look at Matt Pietrek's printf function...

continued...

Frederick J. Harris


//==========================================
// LIBCTINY - Matt Pietrek 2001
// MSDN Magazine, January 2001
//==========================================
#include <windows.h>
#include <stdio.h>
#include <stdarg.h>

// Force the linker to include USER32.LIB
#pragma comment(linker, "/defaultlib:user32.lib")

extern "C" int __cdecl printf(const char* format, ...)
{
    char szBuff[1024];
    int retValue;
    DWORD cbWritten;
    va_list argptr;
         
    va_start(argptr, format);
    retValue = wvsprintf(szBuff, format, argptr);
    va_end( argptr );
    WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), szBuff, retValue, &cbWritten, 0);

    return retValue;



     Very, very nice implementation that simply hands off processing to wvsprintf() in user32.lib.  Its a printf family function (there are hundreds of them scattered all over various libraries) that writes parameters to an output string buffer as per the format specifiers in the format string (the 2nd parameter of wvsprintf() and the 1st parameter of printf passed in through the latter's parameter list.  This format string now contains entities known as format specifiers that specify what type of variable will be in the variable argument list.  For example, this...


int    iNumber    = 5;
double dblNumber  = 3.14159;
char   szBuffer[] = "Hello, World!";

printf("%d\t%s\t%f\n", iNumber, szBuffer, dblNumber);


...is a call where an int, a null terminated char string, and a double floating point number are to be output.  The expected console output should look like so...

5   Hello, World!   3.141590

However, and truly unfortunately, it doesn't work with Matt Pietrek's printf.  The integer and the character string come out all right, but not the double.  Investigating further and looking up the format specifiers for wvsprintf() one finds this list (annotated) from the MSDN docs...




hc
hd
hs
hu
i
lc
ld
li
ls
lu
lx
s
S
u
x

     Notably absent from the list is 'f' which is used for floating point numbers.  So we can't output floating point numbers.  But that isn't the worst of it.  I've actually gotten ahead of myself.  As soon as I tried to add a floating point double to my String Class, I couldn't link anymore.  Linker errors exploded at me like a bomb which left me in an utter state of confusion.  Eventually I abandoned trying to get my String Class to work and reverted to just this in an attempt to isolate and understand the problem...


// cl Double.cpp libctiny.lib Kernel32.lib /O1 /Os
#include <windows.h>
#include <stdio.h>

int main()
{
double dblNumber;

dblNumber = 3.14159;
printf("dblNumber = %f\n", dblNumber);
getchar();

return 0;
}


     All I'm trying to do there is declare a floating point variable and assign a floating point literal to it.  Note we're not talking String Classes or anything that large or complicated.  All we want to do is assign pi to...

double dblNumber; 

It won't work!!!!!  Here is the command line output....


C:\Code\VStudio\VC++9\64_Bit\msvcrt>cl Double.cpp libctiny.lib Kernel32.lib /O1 /Os

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

/out:Double.exe
Double.obj
libctiny.lib
Kernel32.lib
LIBCMT.lib(mbctype.obj) : warning LNK4078: multiple '.CRT' sections found with different attributes (40400040)
LIBCMT.lib(crt0.obj) : error LNK2005: mainCRTStartup already defined in libctiny.lib(CRT0TCON.OBJ)
Double.exe : fatal error LNK1169: one or more multiply defined symbols found


     I don't think I have to tell you that that is definitely not good!  Pure poison bad!  At this point my spirits sank to an all time low.  I realized and accepted that I had finally hit a real limit to this technique which might not be possible to overcome.  And things kept getting worse. 

     Let me explain a bit about what you are seeing above and the reasons for it, which involve how the linker works.  The linker will have what's termed a 'default lib', which can be such libs as LIBC.LIB, LIBCMT.LIB, etc., depending on various types of files upon which it is operating.  But note this layout of my command line compilation string...


cl Double.cpp libctiny.lib Kernel32.lib /O1 /Os


     What will happen there since the only information being fed to the linker are the two lib files libctiny.lib and kernel32.lib is that it will attempt to locate necessary symbols that it needs to create the executable from those two libraries explicitly listed.  But if it encounters a symbol which it needs to reconcile and it doesn't find it within those two libs explicitly listed, it will load its default lib and start looking for it there.  And that's where disaster strikes!  For as soon as it loads its default lib it immediately discoverers name collisions  with Matt Pietrek's various implementations of various pre-start up code Windows' loader uses such as mainCRTStartup() as listed in the errors.  If you look at the code in Matt's CRT0TCON.CPP file (its in the download – I didn't provide it), you'll find that function there.  Here it is...


//==========================================
// LIBCTINY - Matt Pietrek 2001
// MSDN Magazine, January 2001
// FILE: CRT0TCON.CPP
// cl CRT0TCON.cpp /c /W3 /DWIN32_LEAN_AND_MEAN
//==========================================
#include <windows.h>
#include "argcargv.h"
#include "initterm.h"

// Force the linker to include KERNEL32.LIB
#pragma comment(linker, "/defaultlib:kernel32.lib")

//// Force 512 byte section alignment in the PE file   // <<< I commented this out cuz its outdated
//#pragma comment(linker, "/OPT:NOWIN98")

//#pragma comment(linker, "/nodefaultlib:libc.lib")    // <<< here's how Matt is getting rid of bloated
//#pragma comment(linker, "/nodefaultlib:libcmt.lib")  // <<< start up code!!!

extern "C" int __cdecl main(int, char **, char **);    // In user's code

//
// Modified version of the Visual C++ startup code.  Simplified to
// make it easier to read.  Only supports ANSI programs.
//
extern "C" void __cdecl mainCRTStartup( void )
{
    int mainret, argc;

    argc = _ConvertCommandLineToArgcArgv( );

    // set up our minimal cheezy atexit table
    _atexit_init();

    // Call C++ constructors
    _initterm( __xc_a, __xc_z );

    mainret = main( argc, _ppszArgv, 0 );

    _DoExit();

    ExitProcess(mainret);
}


     So those are the nature of the errors one gets constantly in attempting to do this kind of work.  Those above are the most feared ones.  When you add something to your code and you get those kinds of linker errors you know you are in big trouble and hours if not days are going to be lost!

     But moving on I started compiling and linking in separate steps rather than all on one command line string; also experimented with myriads of linker switches to try to stop the linker from loading and looking at LIBC or LIBCMT and started seeing stuff like this...


Double.obj : error LNK2001: unresolved external symbol _fltused
libctiny.lib(PRINTF.obj) : error LNK2001: unresolved external symbol __GSHandlerCheck
libctiny.lib(PRINTF.obj) : error LNK2019: unresolved external symbol __imp_wvsprintfA referenced in function printf
libctiny.lib(PRINTF.obj) : error LNK2019: unresolved external symbol __security_cookie referenced in function printf
libctiny.lib(PRINTF.obj) : error LNK2019: unresolved external symbol __security_check_cookie referenced in function printf
Double.exe : fatal error LNK1120: 5 unresolved externals


     Ah Ha!  At least the barest hint at what might be going on!  Note the top line...


error LNK2001: unresolved external symbol _fltused


     And everything seems to work perfectly until I try to declare and assign a floating point value!  And there is an error message that looks something like a Boolean switch telling me that a floating point number was used, i.e., _fltused!  I lost about a day and a half on this!

      My immediate thought was that my fundamental assumptions about the architecture of C were wrong.  My study of the matter had revealed to me that C was created mostly by Dennis Ritchie and Ken Thompson at Bell Labs during the late 60s as an attempt to create a operating system agnostic systems programming language where the core structure of the language would be separated from input/output capabilities, which were operating system specific.  In that sense a set of fundamental data types would be created along with the logical and syntactic constructs to operate on these fundamental data types.  And here I was having an error message which seemed to strongly indicate that I couldn't assign a floating point value to one of the fundamental data types, which is a C double.  I do not consider an assignment operation to be an input/output activity requiring the intervention of external support libraries.  And in any case, I could assign values to ints and char pointers.  But not doubles.  It wasn't making sense.  Does this mean that some of the fundamental capabilities of C/C++ are actually in external libraries and not within the core language?

     I fiddled around with all the various arcane linker switches for about a day trying to get it to link before I did an internet search on _fltused.  I didn't expect to come up with much being as I figured this path I was following was a pretty lightly tread one.  But lo and behold I did which led me to some fairly seedy places.  At this link...

https://hero.handmadedev.org/forum/code-discussion/79-guide-how-to-avoid-c-c-runtime-on-windows

... I found this...

Quote
If you are using floating points in your code, then you'll get following linker error:

Quote
C:\handmade>cl.exe -nologo -Gm- -GR- -EHa- -Oi -GS- -Gs9999999 win32_handmade.cpp -link -subsystem:windows -nodefaultlib kernel32.lib -stack:0x100000,0x100000 win32_handmade.cpp win32_handmade.obj :

error LNK2001: unresolved external symbol __fltused

win32_handmade.exe : fatal error LNK1120: 1 unresolved externals

In this case linker wants to see _fltused symbol. It needs just the symbol, it doesn't care about its value. So let's provide it in win32_crt_float.cpp file:

extern "C" int _fltused;

And include this file in our win32_handmade.cpp:


#include <windows.h>
#include "win32_crt_float.cpp"

void __stdcall WinMainCRTStartup()
{
    float f;
    f = 0.0f;

    ExitProcess(0);
}


Quote
Let's run compiler and we'll see that everything works:

C:\handmade>cl.exe -nologo -Gm- -GR- -EHa- -Oi -GS- -Gs9999999 win32_handmade.cpp -link -subsystem:windows -nodefaultlib kernel32.lib -stack:0x100000,0x100000
win32_handmade.cpp

     So its looks like I'm not dead yet if I can only get that to work, and I did after about an hour sweating blood over that and fooling around with some of those bizarre linker switches, none of which were needed I later found.  Just the extern "C" int _fltused was needed, and I eventually put that in my Strings.h header.

     So at that point I finally had a program which would compile and link, even with doubles, and that was a massive step foreword!  But it was really only the first of many, many more woes to come!  For while I could now use floating point values in my program, I couldn't output them!  As it turns out there actually is no capability whatsoever in the Windows system files to convert a floating point number to a character representation.  Amazing but seemingly true.  Lots of ways to do it in the C Standard Library but not in Win32.  So it looks like defeat again.

     I might mention that in doing all this work I was periodically switching between working in x86 and x64.  I wanted a common solution for both.  But in agonizing over this I realized that my options for solving this floating point issue were fairly plain.  Here are some of them that occurred to me...

1) I could study up on the IEEE standards for floating point numbers and write my own routines to translate them.  Of course there's the mantissa, exponent, sign bit, and all the rest.  No doubt somehow one could loop through the bits using bit flags and decipher the mess. Uggh!  Not really looking foreword to that!;

2) I could look for already existing FPU (Floating Point Unit) code which I could beg, borrow, or steal;

3)  I could do a real cop out and dynamically load the C Runtime and sneak in a few calls to sprintf() when nobody was looking.

Finally I settled on #3 above, but not before sinking substantial effort into #2, i.e., looking for some FPU code I could use.  As it turns out, over in Hutch's masm32 forum ...

www.masm32.com

I found that Raymond Filiatreault had provided exactly what I needed, i.e., an asm based set of functions operating on the floating point unit, and they were in the form of a lib file containing COFF formatted object files callable either directly from C/C++ code or inline assembler.  Actually, Raymond's Fpu.lib contains about 32 functions, but the only one I needed was FpuFLtoA, i.e., float to asci.   The asm function is like so...


FpuFLtoA
(
  lpSrc1    // pointer to an 80-bit REAL number
  lpSrc2    // pointer to, or value of, 2nd parameter
  lpszDest  // pointer to destination of result
  uID       // ID flags for sources and format
)


...with a masm prototype in Raymond's Fpu.inc file such as this...


FpuFLtoA    PROTO :DWORD,:DWORD,:DWORD,:DWORD


I wrote a little asm code to test it like so...


; C:\Code\MASM\Projects\FPU>ml /c /coff /Cp Fpu2.asm
; C:\Code\MASM\Projects\FPU>link /SUBSYSTEM:CONSOLE Fpu2.obj
include \masm32\include\masm32rt.inc
include \masm32\include\Fpu.inc
includelib \masm32\lib\Fpu.lib

.data
dblNumber  dt 123.456           ; this is the number we want to convert to a char string
dwSrc2     dd 0                 ; this memory variable will store in al the decimal places; ah padding tp right
pszDbl     db 32  dup(0)        ; buffer for storing converted number to asci string
CrLf       db 13, 10, 0         ; carriage return / line feed bytes

.code
start:
  mov al, 2                     ; two decimal places to right of decimal point is all we want
  mov ah, 4                     ; four spaces padding to left of decimal we want
  mov dwSrc2, eax               ; mov to dwSrc2 our desired padding and decimal places in ax
  invoke FpuFLtoA,              ; call FpuFLtoA (Floating Point To Asci)
  ADDR dblNumber,               ; 1st parameter is address of floating point number
  ADDR dwSrc2,                  ; 2nd parameter is where left padding and number of decimal places is stored in ax
  ADDR pszDbl,                  ; 3rd parameter is address of buffer to output Z String
  SRC1_REAL or SRC2_DMEM        ; 4th parameter are equate flags to inform Fpu lib of nature of parameters 1, 2, and 3
  printf("eax    = %u\n",eax);
  printf("pszDbl = %s\n",ADDR pszDbl);
  invoke crt_getchar
  exit
end start

Console Output:
================
eax    = 1
pszDbl =  123.46
 

     One has to set up some equates in the 4th parameter above to tell the function what's in the first three parameters as various combinations are possible such as telling FpuFLtoA that the floating point number to be converted has already been put on the FPU with a fld instruction, or the address of the number to convert to a string is in a memory location such as above where I have the 80 bit dt (Ten Byte) directive allocating ten bytes in the data segment which I'm referring to as dblNumber.  In AL one specifies the number of decimal places in the conversion and in AH goes the padding to the left of the decimal point.  Above I'm attempting to convert 123.456 to a character string in a seven byte buffer with two decimal places to the right of the decimal point.  As you can see in my output above, the program is working perfectly, which shows I can do at least a little assembler yet!

     But it didn't work well (actually not at all) when I tried to transfer that over to C++.  I could provide a C++ prototype, compile and link the program, get it to run without crashing, get a success return code from Raymond's function, and even get it to write to my output buffer, but only zeros showed up.  Not good.  The issue was that Raymond has the input buffer where the floating point is to go set up as an 80 bit ten byte floating point data type.  C/C++ has no way of mapping to such a data type.  PowerBASIC does of course, but floating point data type type primitives in C/C++ are 64 bit 8 byte doubles.  It wouldn't translate to put it simply.

     So I tried to put the float right on the FPU with the fld FPU instruction as in this asm code...


; C:\Code\MASM\Projects\FPU>ml /c /coff /Cp Fpu3.asm
; C:\Code\MASM\Projects\FPU>link /SUBSYSTEM:CONSOLE Fpu3.obj
include \masm32\include\masm32rt.inc
include \masm32\include\Fpu.inc
includelib \masm32\lib\Fpu.lib

.data
dblNumber  dt 123.456     
dwSrc2     dd 0           
pszDbl     db 32  dup(0)
dblFloat   dd 0 
CrLf       db 13, 10, 0   

.code
start:
  mov al, 2                     
  mov ah, 4                     
  mov dwSrc2, eax
  fld dblNumber                           ; << execute fld to put dblNumber on FPU
  invoke FpuFLtoA,             
  0,                                      ; << leave 1st parameter blank             
  ADDR dwSrc2,                 
  ADDR pszDbl,                 
  SRC1_FPU or SRC2_DMEM       
  printf("eax    = %u\n",eax);
  printf("pszDbl = %s\n",ADDR pszDbl);
  mov dblFloat, 234567
  invoke crt_getchar
  exit
end start

eax    = 1
pszDbl =  123.46


     And the good news is that translated to C++ and I was able to use inline assembler to execute the fld instruction.  Here is that functioning C++ code in a compilable example...


// cl Test3.cpp fpu.lib /MT /O1 /Os
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef unsigned int UINT;

#define  SRC1_FPU        1         // SRC1_FPU     Src1 is already on the FPU
#define  SRC1_REAL       2         // SRC1_REAL    Src1 is a pointer to an 80-bit REAL number
#define  SRC2_DMEM    1024         // SRC2_DMEM    Src2 is a pointer to a 32-bit signed integer

extern "C" int __stdcall FpuFLtoA  // FpuFLtoA PROTO lpSrc1:DWORD, lpSrc2:DWORD, lpszDest:DWORD, uID:DWORD
(
  double*       lpFltSrc1,         // This points to an 80-bit REAL number.
  unsigned int* lpSrc2,            // pointer to 2nd parameter
  char*         lpszDest,          // pointer to destination of result
  int           uID                // ID flags for sources and format
);

int main()
{
unsigned short LoByteDecPlaces = 0;       // how many decimal places in result?
unsigned short HiBytePadding   = 0;       // padding spaces to left of decimal point
unsigned int   iFormat         = 0;       // 32 bit entity; AL=Number Decimal Places; AH=Padding Left of Decimal
int            iReturn         = 0;       // Return Value From FpuFLtoA() Function Call
char           szBuffer[64];              // Buffer For Returned Asci Z String.  Make It Plenty Big!
double         dblNumber       = 123.456;

memset(szBuffer,0,64);                    // Zero Out Output Buffer
HiBytePadding   = 4;                      // characters before the decimal point, spaces being used for padding.
LoByteDecPlaces = 2;                   
HiBytePadding=HiBytePadding<<8;       
iFormat=HiBytePadding|LoByteDecPlaces;
__asm fld dblNumber
iReturn=FpuFLtoA(0,&iFormat,szBuffer,SRC1_FPU | SRC2_DMEM);
printf("iReturn  = %d\n",iReturn);
printf("Len()    = %d\n",strlen(szBuffer));
printf("szBuffer = %s\n",szBuffer);
getchar();

return 0;
}

/*

C:\Code\VStudio\VC++9\AsmImport>Test3
iReturn  = 1
Len()    = 7
szBuffer =  123.46

*/


     Realize that another vicious day or two as well as lost blood, hide and sleep were expended on that little endeavor.  And wonderfully it worked when put into my String Class!  However, as you may or may not have guessed, it's no solution!  How am I going to link a 32 bit COFF object file into 64 bit code?  Answer: I'm not.  So its only a 32 bit solution.  So what am I supposed to do?  Come up with one 32 bit solution and another for 64 bit and have conditional compilation directives in the String Class operating off something like...


#ifdef x64
   // code for x64 floats
   ...
#else
   // code for x86 floats
   ...
#endif


     Well, definitely possible but not an optimal solution.  And I'd like to bring this project to conclusion within the next week or two – not three months from now!  I really didn't want to download and learn to use a 64 bit assembler just to try to produce a 64 bit COFF file from Raymond's code.  I really didn't want to take doubles apart bit by bit trying to decipher them.  So all this only leaves one alternative (other than giving up), and that is to sneak a few calls into msvcrt.dll to get use of C Runtime sprintf(), which quite handily translates floating point numbers into a character string buffers.  And of course it exists in both x86 and x64 so I've only one code base to deal with.  So that's what I've done folks!  Down and dirty and underhanded I know.  But its going to have to serve for awhile until I can crack this nut some other way, such as learning the FPU better, writing my own 64 bit assembler code, or maybe Raymond even providing a 64 bit version of his code, which he recently indicated he might be willing to do (I've been back and forth over this issue in Hutch's masm32 assembler forum).

     And I suppose its kind of OK.  My whole purpose in doing this was to attempt to get exe file sizes down.  Dynamically loading what is now a 'system' dll, i.e., msvcrt.dll and calling an exported function through GetProcAddress() won't add anything to the size of my executable.  It will increase the memory footprint of the program but that's hidden, sort of.  And its how MinGW does it.  I am going to try to eventually fix this, but for now I'm letting you know what I did here, i.e., I'm 'coming clean'. 

     One slightly aggravating issue involved here is the scooping of all this.  I hate globally defined variables, but it doesn't make much sense to me to do a LoadLibrary(hLib, "msvcrt.dll") / GetProcAddress("sprintf") every time I need to convert a float.  What if I'm doing a report where tens of thousands of floats need to be converted?  So what I did to further dirty up my code was make it a global entity in my code.  In Windows GUI programs I load msvcrt.dll in my WM_CREATE handler code, as well as setting global function pointer addresses to sprintf and one or two other C Runtime functions (the whole barn door is open now and all the critters are getting out!).  Perhaps I should mention that only fairly restricted portions of my String Class need this functionality.   As I previously alluded, in the past, one of the things I did to my String Class in my efforts to minimize program size was to break up my code into pieces the inclusion of which was governed by conditional compilation statements.  For example, I have these...   


#define NUMERIC_CONVERSIONS          // allows direct assignment of numeric values to Strings, e.g., String s(3.14159) or s=3.14159;
#define FORMAT_MONEY                 // put commas every three places, rounding, left/right justification, specify field sizes (padding), etc.
#define CONSOLE_OUTPUT               // support console output, i.e., enable String::Print()


     The define NUMERIC_CONVERSIONS brings code into my String Class to allow such coding idioms as this...


String s1(123456);
String s2 = 654321;


     The 1st above uses constructor syntax to create a String containing the integral amount 123456.  There is an overloaded constructor for that.  The 2nd (s2) calls an overloaded operator= to convert the integral number 654321 to an asci representation.  I might mention sprintf does this within the implementation.  But if one were writing an app that didn't need this functionality one wouldn't have to include it, and its non-inclusion would save a k or two!

     Same with FORMAT_MONEY.  If that isn't commented out then my String Class will be compiled with functionality to create reports where the decimal places all line up, commas between thousands / thousandth places if desired, field padding, dollar signs, whatever.  So in my Strings.cpp file you'll find this...   


#ifdef NUMERIC_CONVERSIONS
   extern HINSTANCE hLib;
   extern int (*pSPrtf)(char*, const char*, ... );
   #ifdef x64
      extern ssize_t (*pAbs64)(ssize_t);
      extern ssize_t (*pAtoi64)(const char*);
   #else
      extern ssize_t (*pAbs)(ssize_t);
   #endif
#else
   #ifdef FORMAT_MONEY
      extern HINSTANCE hLib;
      extern int (*pSPrtf)(char*, const char*, ... );
      #ifdef x64
         extern ssize_t (*pAbs64)(ssize_t);
         extern ssize_t (*pAtoi64)(const char*);
      #else
         extern ssize_t (*pAbs)(ssize_t);
      #endif
   #endif
#endif


     So the effect of all this is that msvcrt.dll's functions will always be available to me, i.e., they are at global scope.  But all I used msvcrt.dll for were those several functions above. 

     So let me now provide a somewhat sophisticated (at least sophisticated in the sense of it not being a Hello, World! Type program) GUI app that uses a lot of the functionality of my String Class, and presents a lot of windows.  I have posted renditions of this here in Jose's forum before.  This is only a slightly modified version capable of using LibCTiny and with a few more demonstrations added to it. 

continued...

Frederick J. Harris

     What the app named strDemo does is present a main start up form with just five buttons on it as follows...

String Concatenation
String Parsing And Trimming
Good Old Left, Right and Mid
But Let's Not Forget InStr()!
Formatting Integers, Doubles and Money

When you click on any of the buttons the program opens up a little output window with a CreateWindowEx call and demonstrates the various functionalities.  Originally I used GetDC() and TextOut() to draw to the output windows, but of course that's a fairly poor way of operating unless one stores pointers to the strings in a buffer and draws from WM_PAINT instead of a button click procedure.  So I filled each output window with a listbox and added the output strings into it so as to persist the output strings across function calls.  You can click each button as many times as you like and fill your screen up with windows if you like.  I'm coming in 18,844 bytes with VC15 from Visual Studio 2008 in x64 and 20,992 bytes VC19 from Visual Studio 2015 x64.  In x86 its down to an astounding 15,360 bytes!  That's anywhere from 100 k to 175 k smaller than results from Microsoft's or MinGW's compilers building against their Standard Libraries.   And in an apples to apples comparison between VC19 statically linked (/MT) against LIBCMT.LIB and LIBCTINY.LIB its  133,120 compared against 20,992 (for x64)!  That's more than 6 times smaller!  Here is all the code...


// strDemo.cpp
// cl strDemo.cpp Strings.cpp frmOutput.cpp libctiny.lib Kernel32.lib User32.lib Gdi32.lib /O1 /Os
#include <windows.h>
#include <string.h>
#include "stdio.h"
#include "Strings.h"
#include "strDemo.h"
#include "frmOutput.h"


long fnWndProc_OnCreate(WndEventArgs& Wea)
{
char szClassName[]="frmOutput";
WNDCLASSEX wc;
HWND hCtrl;

memset(&wc,0,sizeof(wc));
Wea.hIns=((LPCREATESTRUCT)Wea.lParam)->hInstance;
wc.lpszClassName=szClassName;                          wc.lpfnWndProc=frmOutput;
wc.cbSize=sizeof(WNDCLASSEX);                          wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);
wc.hInstance=Wea.hIns;                                 wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);  wc.cbWndExtra=0;
wc.lpszMenuName=NULL;                                  wc.cbClsExtra=0;
RegisterClassEx(&wc);
hCtrl=CreateWindow("button","String Concatenation",WS_CHILD|WS_VISIBLE,25,20,300,25,Wea.hWnd,(HMENU)IDC_CONCATENATION,Wea.hIns,0);
hCtrl=CreateWindow("button","String Parsing And Trimming",WS_CHILD|WS_VISIBLE,25,55,300,25,Wea.hWnd,(HMENU)IDC_PARSE,Wea.hIns,0);
hCtrl=CreateWindow("button","Good Old Left, Right, And Mid!",WS_CHILD|WS_VISIBLE,25,90,300,25,Wea.hWnd,(HMENU)IDC_LEFT_RIGHT_MID,Wea.hIns,0);
hCtrl=CreateWindow("button","But Led's Not Forget InStr( )!",WS_CHILD|WS_VISIBLE,25,125,300,25,Wea.hWnd,(HMENU)IDC_INSTR,Wea.hIns,0);
hCtrl=CreateWindow("button","Formatting Integers, Doubles And Money",WS_CHILD|WS_VISIBLE,25,160,300,25,Wea.hWnd,(HMENU)IDC_FORMATTING,Wea.hIns,0);
hLib=LoadLibrary("msvcrt.dll");
if(hLib)
{
    pSPrtf=(int (*)(char*, const char*, ...))GetProcAddress(hLib,"sprintf");
    #ifdef x64
       pAbs64=(ssize_t (*)(ssize_t))GetProcAddress(hLib,"_abs64");
       if(!pAbs64)
          return -1;
       pAtoi64=(ssize_t (*)(const char*))GetProcAddress(hLib,"_atoi64");
       if(!pAtoi64)
          return -1;
    #else
       pAbs=(ssize_t (*)(ssize_t))GetProcAddress(hLib,"abs");
       if(!pAbs)
          return -1;
    #endif
}
else
    return -1;

return 0;
}


void btnConcatenation_OnClick(WndEventArgs& Wea)
{
HINSTANCE hIns=NULL;
HWND hOutput=NULL;
HWND hCtl=NULL;
String s;
RECT rc;

hIns=GetModuleHandle(0);
hOutput=CreateWindowEx(0,"frmOutput","String Class Minipulations In C++",WS_OVERLAPPEDWINDOW,850,20,375,480,0,0,hIns,0);
GetClientRect(hOutput,&rc);
hCtl=CreateWindowEx(0,"listbox","",LBS_HASSTRINGS|WS_CHILD|WS_VISIBLE,0,0,rc.right,rc.bottom,hOutput,(HMENU)IDC_LISTBOX,hIns,0);
ShowWindow(hOutput,SW_SHOWNORMAL);
for(unsigned int i=65; i<91; i++)
{
     s=s+i;   //65 Is Capital 'A'
     SendMessage(hCtl,LB_ADDSTRING,0,(LPARAM)s.lpStr());
}
}


void btnParse_OnClick(WndEventArgs& Wea)
{
HINSTANCE hIns=NULL;
String* ptrStrings;
int iParseCount;
HWND hCtl=NULL;
HWND hOutput;
String s;
RECT rc;

hIns=GetModuleHandle(0);
hOutput=CreateWindowEx(0,"frmOutput","String Class Minipulations In C++",WS_OVERLAPPEDWINDOW,475,240,375,180,0,0,hIns,0);
GetClientRect(hOutput,&rc);
hCtl=CreateWindowEx(0,"listbox","",LBS_HASSTRINGS|WS_CHILD|WS_VISIBLE,0,0,rc.right,rc.bottom,hOutput,(HMENU)IDC_LISTBOX,hIns,0);
ShowWindow(hOutput,SW_SHOWNORMAL);
s="Frederick,  Mary,  James,  Samuel,  Edward,  Richard,  Michael,  Joseph";  //Create comma delimited text string
iParseCount=s.ParseCount(',');                                                //Count delimited substrings
ptrStrings = new String[iParseCount];                                         //Create array of String Pointers
s.Parse(ptrStrings,',',iParseCount);                                          //Parse 's' based on comma delimiter
for(unsigned int i=0; i<iParseCount; i++)
{
     ptrStrings[i].LTrim();  //Comment This Out To See Effect.                 //There are some spaces in delimited strings so remove them
     SendMessage(hCtl,LB_ADDSTRING,0,(LPARAM)ptrStrings[i].lpStr());
}
delete [] ptrStrings;
}


void btnLeftRightMid_OnClick(WndEventArgs& Wea)
{
HINSTANCE hIns=NULL;
HWND hCtl=NULL;
HWND hOutput;
String s1,s2;
RECT rc;

hIns=GetModuleHandle(0);
hOutput=CreateWindowEx(0,"frmOutput","String Class Minipulations In C++",WS_OVERLAPPEDWINDOW,450,40,375,150,0,0,hIns,0);
GetClientRect(hOutput,&rc);
hCtl=CreateWindowEx(0,"listbox","",LBS_HASSTRINGS|WS_CHILD|WS_VISIBLE,0,0,rc.right,rc.bottom,hOutput,(HMENU)IDC_LISTBOX,hIns,0);
ShowWindow(hOutput,SW_SHOWNORMAL);
s1="George Washington Carver";
s2=s1.Left(6);
SendMessage(hCtl,LB_ADDSTRING,0,(LPARAM)s1.lpStr());
SendMessage(hCtl,LB_ADDSTRING,0,(LPARAM)s2.lpStr());
s2=s1.Mid(8,10);
SendMessage(hCtl,LB_ADDSTRING,0,(LPARAM)s2.lpStr());
s2=s1.Right(6);
SendMessage(hCtl,LB_ADDSTRING,0,(LPARAM)s2.lpStr());
}


void btnInStr_OnClick(WndEventArgs& Wea)
{
double dblNumber=3.14159;
HINSTANCE hIns=NULL;
String s1,s2,s3;
HWND hCtl=NULL;
HWND hOutput;
int iOffset;
RECT rc;

hIns=GetModuleHandle(0);
hOutput=CreateWindowEx(0,"frmOutput","String Class Minipulations In C++",WS_OVERLAPPEDWINDOW,20,500,625,200,0,0,hIns,0);
GetClientRect(hOutput,&rc);
hCtl=CreateWindowEx(0,"listbox","",LBS_HASSTRINGS|WS_CHILD|WS_VISIBLE,0,0,rc.right,rc.bottom,hOutput,(HMENU)IDC_LISTBOX,hIns,0);
ShowWindow(hOutput,SW_SHOWNORMAL);
s1="InStr('Some Text') Locates The One Based Offset Of Where Something Is At.";
SendMessage(hCtl,LB_ADDSTRING,0,(LPARAM)s1.lpStr());
s1="C++ Can Be A Real Pain In The Butt To Learn!";
SendMessage(hCtl,LB_ADDSTRING,0,(LPARAM)s1.lpStr());
iOffset=s1.InStr("real",false,false);
s2=iOffset;
s3 ="The Offset Of 'real' In The Above String Is ";
s3 = s3 + s2 + '.';
SendMessage(hCtl,LB_ADDSTRING,0,(LPARAM)s3.lpStr());
s3=3.14159;
s3.LTrim();
SendMessage(hCtl,LB_ADDSTRING,0,(LPARAM)s3.lpStr());
s2="Note In The Above Code We Used The Case Insensitive Version Of InStr().";
SendMessage(hCtl,LB_ADDSTRING,0,(LPARAM)s2.lpStr());
s3="We Also Saw We Can Convert A Number (pi) Directly To A String By Simple Assignment.";
SendMessage(hCtl,LB_ADDSTRING,0,(LPARAM)s3.lpStr());
s3="Let's See If We Can Remove() 'Real' From Our 1st String!";
SendMessage(hCtl,LB_ADDSTRING,0,(LPARAM)s3.lpStr());
s2=s1.Remove("Real",true);
SendMessage(hCtl,LB_ADDSTRING,0,(LPARAM)s2.lpStr());
s2="Dieses Program funktioniert aber sehr gut, nicht wahr?";
SendMessage(hCtl,LB_ADDSTRING,0,(LPARAM)s2.lpStr());
}


void btnFormatting_OnClick(WndEventArgs& Wea)
{
double dblNumber=123456789.87654;
size_t iNumber1=1234567898;
HINSTANCE hIns=NULL;
HWND hCtl=NULL;
HWND hOutput;
String s1,s2;
RECT rc;

hIns=GetModuleHandle(0);
hOutput=CreateWindowEx(0,"frmOutput","String Class Minipulations In C++",WS_OVERLAPPEDWINDOW,655,550,775,200,0,0,hIns,0);
GetClientRect(hOutput,&rc);
hCtl=CreateWindowEx(0,"listbox","",LBS_HASSTRINGS|WS_CHILD|WS_VISIBLE,0,0,rc.right,rc.bottom,hOutput,(HMENU)IDC_LISTBOX,hIns,0);
ShowWindow(hOutput,SW_SHOWNORMAL);
s1="Lets Try Reformatting 1234567898 So As To Put Comma Seperators In The Thousands Place - ";
s2.Format(iNumber1,14,',',false);
s1=s1+s2;
SendMessage(hCtl,LB_ADDSTRING,0,(LPARAM)s1.lpStr());
s1="Will It Work With Negative Numbers? ";
ssize_t iNumber2=-1234567898;
s2.Format(iNumber2,14,',',false);
s1=s1+s2+" Yep! Works Just Fine!";
SendMessage(hCtl,LB_ADDSTRING,0,(LPARAM)s1.lpStr());
s1="Let's See If We Can't Do The Same Thing With Floating Point C Doubles...";
s2.Format(dblNumber,18,2);
s1=s1+s2;
SendMessage(hCtl,LB_ADDSTRING,0,(LPARAM)s1.lpStr());
s1="But Maybe We Don't Need Commas Seperating Thousands...";
s2.Format(dblNumber,15,3,true);
s1=s1+s2;
SendMessage(hCtl,LB_ADDSTRING,0,(LPARAM)s1.lpStr());
dblNumber=9876.54321;
s1="Can We Add A dollar Sign In Front Of A Money Formatted Amount? ";
s2.Money(dblNumber,10,true);
s1=s1+s2;
SendMessage(hCtl,LB_ADDSTRING,0,(LPARAM)s1.lpStr());
s1="Can We Specify Location Of dollar Sign In Front Of A Money Formatted Amount? ";
s2.Money(dblNumber,12,(size_t)1);
s1=s1+s2;
SendMessage(hCtl,LB_ADDSTRING,0,(LPARAM)s1.lpStr());
}


long fnWndProc_OnCommand(WndEventArgs& Wea)
{
switch(LOWORD(Wea.wParam))
{
  case IDC_CONCATENATION:
    btnConcatenation_OnClick(Wea);
    break;
  case IDC_PARSE:
    btnParse_OnClick(Wea);
    break;
  case IDC_LEFT_RIGHT_MID:
    btnLeftRightMid_OnClick(Wea);
    break;
  case IDC_INSTR:
    btnInStr_OnClick(Wea);
    break;
  case IDC_FORMATTING:
    btnFormatting_OnClick(Wea);
    break;
}

return 0;
}


long fnWndProc_OnDestroy(WndEventArgs& Wea)
{
HWND hOutput=NULL;

do   //Search And Destroy Mission For Any Output Windows Hanging Around
{
  hOutput=FindWindow("frmOutput","String Class Minipulations In C++");
  if(hOutput)
     SendMessage(hOutput,WM_CLOSE,0,0);
  else
     break;
}while(true);
FreeLibrary(hLib);
PostQuitMessage(0);

return 0;
}



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

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

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


int WINAPI WinMain(HINSTANCE hIns, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
char szClassName[]="strDemo";
WNDCLASSEX wc;
MSG messages;
HWND hWnd;

wc.lpszClassName=szClassName;                    wc.lpfnWndProc=fnWndProc;
wc.cbSize=sizeof (WNDCLASSEX);                   wc.style=0;
wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);         wc.hInstance=hIns;
wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION);      wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);    wc.cbWndExtra=0;
wc.lpszMenuName=NULL;                            wc.cbClsExtra=0;
RegisterClassEx(&wc);
hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,50,50,360,250,HWND_DESKTOP,0,hIns,0);
ShowWindow(hWnd,iShow);
while(GetMessage(&messages,NULL,0,0))
{
    TranslateMessage(&messages);
    DispatchMessage(&messages);
}

return messages.wParam;
}


Here is strDemo.h


//strDemo.h
#ifndef strDemo_h
#define strDemo_h

extern "C" int _fltused=1;                // This _fltused business is one serious piece of work!  I lost over a day on it!
HINSTANCE  hLib;                          // If its not defined like so you won't even be able to assign a floating point
int (*pSPrtf)(char*, const char*, ... );  // number to a variable!  The hLib thing I had to do.  I just couldn't live without
#ifdef x64                                // several routines from the C Runtime, namely sprintf(), _abs64(), _atoi64(), and
   ssize_t (*pAbs64)(ssize_t);            // abs().  In the fullness of time I hope to get rid of the necessity of loading the
   ssize_t (*pAtoi64)(const char*);       // C Runtime (msvcrt.dll) - even dynamically, but haven't reached that point yet!
#else
   ssize_t (*pAbs)(ssize_t);
#endif

#define IDC_CONCATENATION       1500
#define IDC_PARSE               1505
#define IDC_LEFT_RIGHT_MID      1510
#define IDC_INSTR               1515
#define IDC_FORMATTING          1520
#define IDC_LISTBOX             2000
#define dim(x) (sizeof(x) / sizeof(x[0]))

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

long fnWndProc_OnCreate       (WndEventArgs& Wea);
long fnWndProc_OnCommand      (WndEventArgs& Wea);
long fnWndProc_OnDestroy      (WndEventArgs& Wea);

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

const EVENTHANDLER EventHandler[]=
{
{WM_CREATE,                  fnWndProc_OnCreate},
{WM_COMMAND,                   fnWndProc_OnCommand},
{WM_DESTROY,                 fnWndProc_OnDestroy}
};
#endif


Here is frmOutput.h...


//frmOutput.h
LRESULT CALLBACK frmOutput(HWND, unsigned int, WPARAM, LPARAM);


And next frmOutput.cpp...




//frmOutput.cpp  All this file consists of is the Window Procedure for the frmOutput
//Window Class.  A window of class frmOutput is created whose sole purpose is to be
//printed to with TextOut().  All default window procedure processing is ok so the
//window procedure frmOutput doesn't have to do anything on its own other than exist
//and pass all messages received onto DefWindowProc().  It has to exist because its
//referenced in the Class Registration code in fnWndProc_OnCreate() where the frmOutput
//class is registered.
#include <windows.h>
#include "frmOutput.h"

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


Of course, you'll need my previously posted Strings.h and Strings.cpp, as well as a functioning LibCTiny.lib produced as I've already described.  The command line compilation string is at top of strDemo.cpp and is this...

// cl strDemo.cpp Strings.cpp frmOutput.cpp libctiny.lib Kernel32.lib User32.lib Gdi32.lib /O1 /Os


C:\Code\VStudio\VC15\LibCTiny\x64\Test1>cl strDemo.cpp Strings.cpp frmOutput.cpp libctiny.lib Kernel32.lib User32.lib Gdi32.lib /O1 /Os
Microsoft (R) C/C++ Optimizing Compiler Version 19.00.23506 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

strDemo.cpp
Strings.cpp
frmOutput.cpp
Generating Code...
Microsoft (R) Incremental Linker Version 14.00.23506.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:strDemo.exe
strDemo.obj
Strings.obj
frmOutput.obj
libctiny.lib
Kernel32.lib
User32.lib
Gdi32.lib

C:\Code\VStudio\VC15\LibCTiny\x64\Test1>


So I'm getting an 18 k to 20 k stand alone x64 executable from about 1500 lines of code in those six files and I have a decent String Class to work with.   This table tells the full story...


Compiler                       String Class     Archicture   Library Linkage    Size Bytes
==========================================================================================
VC19 C++ Visual Studio 2015    My String Class     x64        LIBCMT.LIB         133,120
GCC g++ 4.8.1 C++              My String Class     x64        MSVCRT.LIB         203,264
VC19 C++ Visual Studio 2015    My String Class     x64        LIBCTINY.LIB        20,992
VC15 C++ Visual Studio 2008    My String Class     x64        LIBCTINY.LIB        18,844
VC19 C++ Visual Studio 2015    My String Class     x86        LIBCTINY.LIB        15,360


     Note the size of the x86 version!  The compiles I've been doing with VC19 from Visual Studio 2015 were done on a new HP Envy 17 laptop I just bought a few days ago with Windows 10 on it.  On that machine I compiled a version without LibCTiny.lib, i.e., using the standard C runtime and the /MD switch, i.e., dynamic linking instead of static linking, and that compiled down to 26 k.  Not too bad, but when I took that file and tried to run it on my Windows 7 Dell M6500 I got this message...

Quote
"The program can't start because VCRUNTIME140.dll is missing from your computer.  Try reinstalling the program to fix this problem."

     So I'm glad I did this my war on bloatware!.  I've been watching the bloat increase for years and just putting up with it.  Now I'm fighting back, and it looks like I'm winning the battle!  When I get Unicode functionality working I'll post if anyone is interested.


Patrice Terrier

Frederick

As you probably remember i am also one of this old dog, liking it small.
I have done several attempts to reduce the size of my C++ EXE and DLL, and i always use the /MT switch, however Unicode is a real mandatory for me, however i keep using ANSI in very limited cases, like when reading setup files
I have also written several functions to mimic the PB's strings. Using UPERCASE for Unicode, and lowercase for the Ansi version.

I am looking forward to see what you could get with UNICODE.

Keep on the good work.

...
Patrice Terrier
GDImage (advanced graphic addon)
http://www.zapsolution.com

Frederick J. Harris

Sometime after I did my initial work on this over a year ago I fiddled around with trying to get it to work with UNICODE and I didn't succeed, but that was not a very concerted effort only involving an hour or so.  I'm fairly confident I can get it to work once I set my mind to it.  As you can imagine something like this takes some time and concentration.  I found a link or two on the internet where someone got Pietrek's LibCTiny.lib working in UNICODE, so I'll have that to start with.  Like you I mostly do wide character now, and of course that works better with COM too.  So its near the top of my list of things to do! 

I decided to concentrate on the ansi first cuz I had success with that first.  You know, you need some kind of foundation to build on.

James C. Fuller

Fred,
  Excellent stuff as always!
  Could you give a quick comparison of your string class vs std::string,std::wstring ?
James

Frederick J. Harris

     Thanks for the kind words Jim.  My String Class is really important to me, that's why I spare no effort in attempting to make it as good and useful as I can.  Its actually the reason I do C++, its that important to me. 

     When I first taught myself C++ many years ago in the late 90s I eventually gave it up and reverted back to C for a long while.  I really hated a great deal of what I was seeing, and at that time I wasn't able to make as clear of a distinction in my mind as I can now between the core C++ language and the Standard Libraries. 

     It was my data recorder programming in C for Windows CE that caused the turn around for me.  One day I spent a whole afternoon taking a comma delimited string apart in C, which was doubly complicated because each of the delimited sub-strings were further delimited by colons, something like this...


"45:TNum:^:edit, 60:Species:^:edit, 50:Dbh:^:edit, 35:Ht:^:edit"


     Something like that would be like 10 or 15 minutes work in PowerBASIC but killed a whole afternoon for me fooling around with strtoken, strcpy, strcat, strlen, malloc, free, etc., etc.  I got thinking back to a class example I had studied hard on many times in one of Ivor Horton's C++ books that looked suspiciously like a String Class.  It wasn't actually, but something close where one had to dynamically allocate memory for various lengths of strings in a message class.  From that I decided to pick it up again and see if I couldn't make my own String Class.  I did manage it.  It was crude, but I had a first taste of success, and couldn't stop.  I guess you could say the intellectual juices were flowing. 

     I know this will amaze you of my ignorance, but at that time I wasn't aware of the String Class in the C++ Standard Library!  I suppose part of the reason for my ignorance was that I was mostly learning in the context of MFC, and I had mostly been using Ivor Horton's book, and in the 1st part of the book where he 1st taught console mode C++ coding he never used String Classes but rather null terminated character arrys.  And when MFC was finally introduced, that Class Framework had its own String Class named CString.  See, the situation with String Classes is kind of this.  Here is a good excerpt from a pretty well respected programmer – Al Stevens, whose book I have...

Quote
The std::string Class

     This chapter is about the std::string class.  When basic programmers switched en masse to C in the mid 1980s, virtually all of them had the same reaction: "What, no string data type?" 

     A string is a contiguous array of (usually) displayable characters.  C programmers implement strings as null-terminated arrays of char variables.  The C and C++ string literal constant is implemented this way.

     Basic programmers loved the language's rich set of string operators for manipulating character strings.  C's null terminated character arrays – its so called string literals and its strcpy – strcmp family of functions seemed lame by comparison. 

     When those same C programmers switched to C++ in the early 1990s, many of them seized upon the oportunity to use the C++ Class mechanism to implement string classes and recover some of the convenience they gave up when they abandoned basic.

     A string class encapsulates the character array and wraps the string object  in a public interface that supports the kinds of things programmers want to do with string data.

     Most early C++ books include string classes as examples.  Most of the early C++ compilers included string classes among their other classes in their proprietary class libraries.  No two of those early string classes were quite the same – or so it seemed – and no standard existed for anyone to follow. 

     With the publication in 1998 of the ANSI/ISO Standard C++ specification, C++ at last has a standard string class..... 

     So that's kind of the story with me.  As I saw things at that early time I didn't have a string class unless I linked with the MFC class framework, which I hated.  I knew C and Visual Basic very well at that time and I thought C++ might be something like Visual Basic with its powerful COM components, but the more I got into it the less I liked it.  So I gave it up as a lost cause, reverted back to C, and at about that time around the late 90s I discovered PowerBASIC and Jose's work.  That gave me everything I wanted, i.e., a powerful low level language that was capable of using COM components like VB had.

     Couple years later though I suspected that perhaps in my abandonment of C++ I had 'thrown the baby out with the bathwater'.  Yes, there were things I detested in C++, but all those things didn't really involve the core language which was based on C and a considerable improvement of it.  They were things and coding idioms involving the C++ standard library and class frameworks.  At that time the distinction became clearer in my mind between the two, and I had a genuine need in my day to day data recorder programming in Windows CE (PowerBASIC didn't work there – it was the C way or the highway) to use some of the improvements of C++, particularly better more basic like string handling.  So it was really the String Class issue that crystalized this all for me in my mind and I began work on my first version of my String Class with fervor.  It was clear to me that if I could succeed with this I'd have use of a really powerful language and I'd be able to be productive with it.

     This has been going on now for 15 years I'd say.  I constantly work at it, think about it, modify it.  The latter has its drawbacks.  I've a million versions of it.  I'm always modifying member functions, changing return types, parameters, etc.  Let me just give an example.  Takr the PowerBASC Trim().  In my String Class I have a String::LTrim(), String::Rtrim, and String::Trim().  How should it work?  Like this...


s.Trim();


or this


s2=s1.Trim();
   

     In the 1st case the String controlled by the class is modified in place.  So that way the original String was initially gone after the call.  In the 2nd or latter case the original string – s1, is unmodified and a new trimmed string is created and returned into s1.  So what I'm saying is that this sort of thinking about it and fiddling with it never ends. 

     Well, enough of this.  You had asked about comparisons between my String Class and the std::string class.  Actually, I never used the one in the std::string class a great deal, but at one point I became very concerned about the performance of my String Class in comparison to the C++ Std. Lib's one.  And by performance I don't mean how much more productive one can be using a String Class as opposed to raw memory buffer manipulations like in C, but how fast it runs.  What prompted me to think about that was a posting by Jaime de los Hoyos in 2010.  Not directly from his original post, but indirectly, due to the course of events the responders caused.  Here is his original post.  Link is still good...

http://www.powerbasic.com/support/pbforums/showthread.php?t=42934&highlight=Fast+PowerBASIC+Compilers+Statement

Some fascinating reading there!  What happened is Jaime had just bought PowerBASIC as he had heard it was as 'fast as C'.  He knew some C and did some tests and C was killing PowerBASIC.  So he started asking about it, not in a confrontational way, but just to understand the claim better.  Actually, as he stated up front, his primary interest really was strings.  That was why he bought PowerBASIC. 

     What he did, to make a long story short, is use some canned number crunching code involving integers and for loops which likely all the compiler writers and optimizers know by heart.  Using that, C was killing PowerBASIC.  I mean, not by a little bit, but by factors of a hundred or more!  Eventually Paul Dixon, a real asm guru, disassembled the PowerBASIC code and the C code and what he found was truly amazing.  It turns out that the mathematical expression involved in Jaime's test code comes to a predetermined quantity that can be ascertained without even running any loops, doing any additions, multiplications, divisions or whatever! And the Microsoft compiler recognized this an optimized the whole thing away!  The PowerBASIC compiler, on the other hand, was simply running the code. 

     This compiler optimization topic is a whole realm onto itself.  Its actually where most efforts are placed I think.  And its fairly easy to understand why I think.  There's only so much performance one can gain by writing better loop code.  Eventually one reaches a point where its just not going to go any faster.  But if efforts are made at examing the logic of the code instead, potentially easier and bigger gains can be made per measure of input time.  I really do believe that's why C++ coders are able to survive at all with the coding styles they now use involving making abstractions out of everything and creating layers upon layers of abstractions.  The compilers are so good they are just optimizing it all away and outputting decent C code as needed.

     Where the above enters the picture of my String Class is that when PowerBASIC responders read of Paul Dixon's determinations about the C compiler optimizing the whole thing away and leaving PowerBASIC sucking dust John Gleason came up with the idea of a fairer test that was more like what an actual application might face in processing data.  He came up with this test, and asked if anyone who knew C or C++ (he didn't) could attempt to implement it...


1) Create a 15 MB string of nulls;
2) Change every 7th null to a "P";
3) Replace every "P" with a "PU" (hehehe);
4) Replace every null with an "8";
5) Put in a carriage return & line feed every 90 characters;
6) Output last 4K to Message Box.


     When I saw that (I was actively following the thread), I realized that would be me.  And here I had this String Class of mine which I thought could do something like that except for one little issue.  I couldn't do it with nulls like John stated.  But I figurred dashes or some other character would serve as well.  So I took a hard look at it.  Upon close inspection I realized I didn't have anything in my String Class like #3 above, i.e., Replace "P" with "PU".  Of course, John knew what he was doing.  He wasn't going to make it easy!  The algorithm would have to constantly grow memory with multiple memory allocations, posibly and likely with the need to constantly be moving the string from one memory block to another. 

     So I wasn't able to do it.  At the time I was busy with stuff at work I had to attend to, so I had to let the matter drop, and the thread eventually died out.  But I was determined to return to that just as soon as I could find the time, because I was curious how my String Class would fare against that, and I was also intrigued by how my String Class might compare against the std::string class of the C++ Std. Lib.  All the details of that can be found in a paper I eventually presented in the PowerBASIC Forums and was pretty well received, and I'm including that in the attached Code.zip file.  Its in text file form there named PBSpeed.txt.

     In a nutshell though I ended up rewritting my String Class.  What happened is that some parts of my String Class were killing the String Class in the C++ Std. Lib., and parts of that String Class were killing mine.  On average mine was faster, and always beat the Std. Lib's String Class, but I could never touch it on string concatenations in loops.  But I'll stop on this here or I could be writing all night about it!  The details are in that document.

     In the attached Code.zip file are all the *.cpp & *.h files to create LibCTiny.lib.  Also there is MyLibCTiny.mak which if run with the x64 version of nmake will create the x64 version, and if run in x86 mode the 32 bit version.  However, the 64 bit version is there too already made.  I renamed it to LibCTiny64.lib and can be used under that name.  Matt Pietrek's original LibCTiny.mak is there too.  Also there are Jim1 through Jim7 exes and cpp files.  I've also put strDemo.exe there in the 64 bit version which is the demo created in my discussion above with the buttons and output screens as previously described which came in 20,992 bytes.

     Jim1.cpp is like so...


// cl Jim1.cpp Strings.cpp LibCTiny64.lib kernel32.lib /O1 /Os
// 7680 bytes x64 VC19
#include <windows.h>
#include "Strings.h"
#include "Init.cpp"
#include "stdio.h"

int main()
{
if(blnLoadMsvcrt())
{
    String s1(12345);         // Use Constructor notation to convert integral literal to String
    String s2;                // Will cause call to uninitialized String Constructor

    s2=12345;                 // Assign numeric literal to String
    s1.Print("s1 = ", true);  // Output s1 converted to a String
    s1.Print("s2 = ", true);  // Output s2 converted to a String
    FreeLibrary(hLib);
    getchar();
}

return 0;
}

// Output
//
// s1 = 12345
// s2 = 12345


It shows how a String object – s1, can be instantiated with a numeric literal constant through an overloaded constructor call.  In other words, this isn't necessary...


Local s1 As String
S1 = Str$(12345)


String s2 is initially constructed uninitialized.  Then an assignment operation causes the numeric literal 12345 to be converted to String s2.  The C++ Standard Library doesn't accept a syntax so simplified I don't believe.  Using the little I know about it my best guess would be this...

// cl tmp1.cpp /EHsc /O1 /Os
// 137,728 bytes
#include <string>
#include <cstdio>

int main()
{
char szBuffer[16];             // char buffer array for characters
std::string s1,s2;             // two uninitialized std::string objects
 
sprintf(szBuffer,"%d",12345);  // converts numeric literal to string and writes to szBuffer
s1=szBuffer;                   // assign character array to std::string
printf("%s\n",s1.c_str());
sprintf(szBuffer,"%d",12345);
s2=szBuffer;
printf("%s\n",s2.c_str());
getchar();

return 0;
}


     And that worked.  Took more lines and about 130,000 more bytes to do it though (137,728 verses 7,680). 
Actually, the way its done just above in the C++ Std. Lib. Example is exactly what my overloaded constructors and overloaded operator= members do.  Here is operator=, for example...


String::String(int iNum)      // assigns int to this
{
this->lpBuffer=new char[16];
this->iCapacity=15;
this->iLen=sprintf(this->lpBuffer,"%d",iNum);
}


Jim2 is a parsing example...


// cl Jim2.cpp Strings.cpp LibCTiny64.lib kernel32.lib /O1 /Os
// 8192 bytes x64 VC19
#include <windows.h>
#include "Strings.h"
#include "Init.cpp"
#include "stdio.h"

int main()
{
if(blnLoadMsvcrt())
{
    String s1("Zero, One, Two, Three, Four, Five");  // Will call Constructor for character String literal
    String* pStrs=NULL;                              // We'll obtain array of Strings after parsing above CSVs
    int iParseCount=0;                               // For use in String::ParseCount(...)

    iParseCount=s1.ParseCount(',');                  // String::ParseCount() returns number of delimited strings
    pStrs=new String[iParseCount];                   // Create String::ParseCount() number of Strings
    if(pStrs)
    {   
       s1.Parse(pStrs,',',iParseCount);              // Pass into String::Parse enough Strings, delimiter, and count
       for(int i=0; i<iParseCount; i++)
       {   
           pStrs[i].Trim();                          // Some of the Strings will have a leading space
           pStrs[i].Print(true);                     // Use String::Print method
       }   
       delete [] pStrs;                              // Free dynamic memory
    }
    FreeLibrary(hLib);
    getchar();                                       // My version of getchar()/Waitkey$
}

return 0;
}

// Output
// ======
// Zero
// One
// Two
// Three
// Four
// Five


     That's just about exactly how it would be done in PowerBASIC.  Isn't that a coincidence!  Sorry I can't provide a C++ Std. Lib. Version of that.  I have an example I copied from somewhere that's something like it, but I don't understand it well enough to modify it or explain it.  And I can't really say I want to.  Its around 500 k of bloat when compiled.

     Jim3 is a concatenation example that concatenates the 26 letters of the English alphabet together....


// cl Jim3.cpp Strings.cpp LibCTiny64.lib kernel32.lib /O1 /Os
// 7680 bytes x64 VC19
#include <windows.h>
#include "Strings.h"
#include "Init.cpp"
#include "stdio.h"

int main()
{
if(blnLoadMsvcrt())
{
    String s;

    for(int i=65; i<=90; i++)
    {
        s=s+(char)i;
        s.Print(true);
    }
    FreeLibrary(hLib);   
    getchar();
}

return 0;
}

#if 0
A
AB
ABC
ABCD
ABCDE
ABCDEF
ABCDEFG
ABCDEFGH
ABCDEFGHI
ABCDEFGHIJ
ABCDEFGHIJK
ABCDEFGHIJKL
ABCDEFGHIJKLM
ABCDEFGHIJKLMN
ABCDEFGHIJKLMNO
ABCDEFGHIJKLMNOP
ABCDEFGHIJKLMNOPQ
ABCDEFGHIJKLMNOPQR
ABCDEFGHIJKLMNOPQRS
ABCDEFGHIJKLMNOPQRST
ABCDEFGHIJKLMNOPQRSTU
ABCDEFGHIJKLMNOPQRSTUV
ABCDEFGHIJKLMNOPQRSTUVW
ABCDEFGHIJKLMNOPQRSTUVWX
ABCDEFGHIJKLMNOPQRSTUVWXY
ABCDEFGHIJKLMNOPQRSTUVWXYZ
#endif


     Jim4.cpp is kind of interesting.  Its what I couldn't do when John Gleason provided his vicious little string processing algorithm...   


// cl Jim4.cpp Strings.cpp LibCTiny64.lib kernel32.lib /O1 /Os
// 9216 bytes x64 VC19
#include <windows.h>
#include "Strings.h"
#include "Init.cpp"
#include "stdio.h"

int main()
{
if(blnLoadMsvcrt())
{
    String s1,s2;

    s1="AAAAAAAAAABAAAAAAAAAABAAAAABAAAAAAAAAAAAAAAB";
    s2=s1.Replace("B","ZZZZZ");
    s1.Print(true);
    s2.Print(true);
    FreeLibrary(hLib);
    getchar();
}

return 0;
}

// Output
// ============================================================
// AAAAAAAAAABAAAAAAAAAABAAAAABAAAAAAAAAAAAAAAB
// AAAAAAAAAAZZZZZAAAAAAAAAAZZZZZAAAAAZZZZZAAAAAAAAAAAAAAAZZZZZ



     I had to write my String::Replace(...) method inorder to implement fully John's test.  You can see my replace in Strings.cpp.  When I wrote it I didn't think it was anything special and I wasn't thinking of performance.  I was simply thinking of getting the job done any reasonable way which came to mind.  But that particular code is what allowed me to beat the C++ Standard Library in every case in which I benchmarked it.  There is great detail about this in my PBSpeed.txt file which I posted on the PowerBASIC Forums years ago.  Here is just a brief excerpt from that which shows the tick counts from a run with my String Class...


//My String Class
Processing...
After Allocating s1(MAX_MEM)...         : 0
After Making 2,000,000 dashes...        : 10
After Assigning Ps Every 7th Char...    : 50
After Doing John's PU Thing...          : 300
After Replacing Dashes With 8s...       : 551
After Big Concatenation With CrLfs...   : 26538
Finished!                               : 26538


What that table shows is that we were into the algorithm 50 ticks when my String::Replace() was called.  It finished at 300n ticks, so it took 250 ticks to make the replacement which grew the String.  The whole algorithm was something of an obstacle for the C++ Std. Lib's std::string class because it doesn't have any member which effects replacements where the replacement text is larger than what is being replaced.  However, I used a function former C++ Standards Committee member and C++ author Bruce Eckel had in his "Thinking In C++" book, and here are the results from that...


//C++ Standard Library String Class
Starting....
Finished Creating 2000000 Of These -    :   milliseconds elapsed - 0
Finished Inserting 'P's In s1!          :   milliseconds elapsed - 51
Finished Replacing 'P's With PU!        :   milliseconds elapsed - 347340
Finished Replacing '-'s With 8!         :   milliseconds elapsed - 347620
Now Going To Create Lines With CrLfs!
Finished Creating Lines!                :   milliseconds elapsed - 347660
t1 = 347660


     As you can see from the table, going into the Replace() call we were at 51 ticks – about neck and neck with mine.  It finished the replacements at 347340 ticks – about five and a half minutes later!  But look at the time it took for the last part, which involved a big concatenation of I believe 90 character lines together in a loop where CrLfs were added too.  It did in 40 ticks what my String Class took 26000 ticks!  So I can't say I feel really good about beating it overall.  It killed me on the concatenations, even though my code ran like 15 times faster due to my pretty good replacement.

     And I have some thoughts on that.  I put in a tremendous amount of time trying to figure out what I could do to my concatenations to speed them up.  But I basically hit a brick wall.  I couldn't do it.  That raises the question of just how the C++ Std. Lib's code is doing it.

     I'm stating opinion here, but this is my guess.  Recall I mentioned above a couple pages back about how the C++ compiler was able to look at the algorithm, recognize what the end result was of what it was trying to accomplish, further determine that the coder's algorithm wasn't the most efficient way to go about it, and substitute an ultimate replacement that got the job done much, much more efficiently?  Well, that's really the only possibility.  What the algorithm was doing, or, perhaps better said, what the end result was supposed to be, was the insertion of a CrLf every 90 characters so as to break the large buffer into 92 byte line lengths.  I interpreted that as a concatenation and addition in a For Loop.  I think the way the compiler looked at it was one long memory write inserting CrLfs every 90 characters.  There really isn't any other possibility.

     As another aside, I had a nasty run in with the compiler on that very thing right before I posted my LibCTiny a few days ago.  This is interesting enough I'll elaborate on it.  In the WM_CREATE handler of my GUI strDemo app I RegisterClassEx() my "Output" Window Class, so I have to fill out the fields of a WndClassEx struct...


long fnWndProc_OnCreate(WndEventArgs& Wea)
{
char szClassName[]="frmOutput";
WNDCLASSEX wc;
HWND hCtrl;

memset(&wc,0,sizeof(wc));
Wea.hIns=((LPCREATESTRUCT)Wea.lParam)->hInstance;
wc.lpszClassName=szClassName;                          wc.lpfnWndProc=frmOutput;
wc.cbSize=sizeof(WNDCLASSEX);                          wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);
wc.hInstance=Wea.hIns;                                 wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);  wc.cbWndExtra=0;
wc.lpszMenuName=NULL;                                  wc.cbClsExtra=0;
RegisterClassEx(&wc);


See that memset() rigt in the middle of the code above?  Well, I had tested this LibCTiny code on my old but still excellent XP laptop in Win 32 and it worked perfectly.  Neither I nor Matt Pietrek had declared or implemented memset() but somehow it was working.  It may be a compiler intrinsic.  I don't know.  All I know is that I don't have to fix code that is compiling, linking and working.  When I tested this code on my Windows 7 laptop in both x86 and x64 it worked there too.  Then last Saturday I bought a Windows 10 laptop with the new UCRT (Universal C Runtime) of which I spoke, and BAM!  Linker Error!  Unresolved External!  It can't find memset()!  Well, I didn't add it to Matt's LibCTiny, and it wasn't there to begin with.  But it was finding it OK on the previous operating systems.  If you look at what I'm doing with it there you'll see its nothing unusual, not that it really matters.  C/C++ coders use memset all the time to zero out memory underlying structs because C/C++ family languages oftentimes won't initialize memory to zero like with PowerBASIC.  The memory is allocated but can contain junk.  Its particularly pernicious with RegisterClassEx() because with PowerBASIC one can get away with only filling out a few of the most important members recognizing that the others will be set to zero, and Windows won't do anything with them.  In the case of C however, if there is a garbage value in the memory where one of the handles go you're just s*** out of luck.  Anyway, I just removed the memset() and put a for loop there to write nulls to the bytes occupied by the WNDCLASSEX struct.  I did that to temporarily get rid of the linker error on the missing memset.  Guess what happened when I tried to re-link with the offending memset was gone?  What happened was another linker error telling me it couldn't find memset()!!!  And there weren't any other memset()s in the code, but I was still getting a memset() linker error.

     What happened is the optimizer looked at my for loop setting about 50 or 60 bytes to null and decided there was a more efficient way to do it, namely memset(), whose implementation likely isn't a for loop but fast asm string register (esi, edi) calls.  So memset()'s back whether I want it or not!  Same as the deal with Jaime de los Hoyos's tricky code and the C++ Std. Lib's string concatenation code.  I'll tell you another amazing thing about it too. When I saw that I wrote a memset function right above that WM_CREATE function figuring if the linker wants a memset function I'll give it a memset function.  Well, that shows how simple minded I am.  My memset function was implemented with the for loop.  Same linker error.  I'd been fighting errors day in and day out for a month and now this ridiculous exasperation over a stupid memset() after I have nearly everything working!

     Finally I put the memset in a memset.cpp file like all the other additions, compiled it to a memset.obj file, and put that in LibCTiny.lib.  For whatever reason that quieted the linker.  You can see it in the files in the zip.

     Jim5.cpp shows how one of my overloaded String::Remove() members works...


// cl Jim5.cpp Strings.cpp LibCTiny64.lib kernel32.lib /O1 /Os
// 8192 bytes x64 VC19
#include <windows.h>
#include "Strings.h"
#include "Init.cpp"
#include "stdio.h"

int main()
{
if(blnLoadMsvcrt())
{
    String s1,s2;

    s1="ABCDEFGHIJKLMNOPQRSTUVWXYZ";  // Let's Try Removing The Vowels, i.e., A, E, I, O, And U
    s2=s1.Remove("AEIOU");            // From s1 And Returning Remaining String In s2.
    s1.Print(true);
    s2.Print(true);
    FreeLibrary(hLib);
    getchar();
}

return 0;
}

// Output
// ==========================
// ABCDEFGHIJKLMNOPQRSTUVWXYZ
// BCDFGHJKLMNPQRSTVWXYZ
 

     That particular overload of Remove() does what I call 'individual character removal' for lack of any better way of explaining it.  Above it takes the letters 'A', 'E', 'I', 'O', and 'U' out of the English alphabet, i.e., the vowels.  Jim6.cpp's Remove() takes out whole words, such as 'Real' in the phrase "C++ Can Be A Real Pain In The Butt To Learn!".  That program also exercises a few other members....


// cl Jim6.cpp Strings.cpp LibCTiny64.lib kernel32.lib /O1 /Os
// 9728 bytes x64 VC19
#include <windows.h>
#include "Strings.h"
#include "Init.cpp"
#include "stdio.h"

int main()
{
if(blnLoadMsvcrt())
{
    String s1,s2,s3;
    int iMatch=0;

    s1="C++ Can Be A Real Pain In The Butt To Learn!";  // Create A String Through Assignment
    printf("s1.Len() = %d\n",s1.Len());                 // printf its length
    printf("s1.lpStr() = %s\n", s1.lpStr());            // Output it through String::lpStr() which is counterpart to std::string::c_str()
    iMatch=s1.InStr("Real",true,true);                  // Like PowerBASIC INSTR().  Doing Case Sensitive Starting From Left
    printf("iMatch = %d\n", iMatch);                    // Output One Based Offset Of Where 'Real' Occurs In String
    s3=s1.Mid(iMatch,4);                                // Try String::Mid
    s3.Print(true);                                     // Do String::Print()
    s2=s1.Remove("Real",true);                          // Remove 'Real' From s1 And Return Result In s2.
    s2.Print(true);                                     // Do String::Print()
    FreeLibrary(hLib);
    getchar();
}
return 0;
}

// Output
// ==========================
// C:\Code\VStudio\VC15\LibCTiny\x64\Test2>jim6
// s1.Len() = 44
// s1.lpStr() = C++ Can Be A Real Pain In The Butt To Learn!
// iMatch = 14
// Real
// C++ Can Be A  Pain In The Butt To Learn!


     Jim7.cpp shows some stuff I've come up with within the past year, i.e., number formatting like PowerBASIC's Format$() function does.  Its not as good as PowerBASIC's Format$(), but what is?  The example below shows a bunch of doubles formatted as money where they are right aligned within a field and the decimal points line up.  I need that for the reports I do...


// cl Jim7.cpp Strings.cpp LibCTiny.lib kernel32.lib /O1 /Os
// 11,264 bytes x64 VC19
#include <windows.h>
#include "Strings.h"
#include "Init.cpp"
#include "stdio.h"

int main()
{
double dblMoney[]={0.95, 4567.89876, -2345.6789, 21356.12345, 0.987654321};   
String s;

if(blnLoadMsvcrt())
{
    for(int i=0; i<5; i++)
    {
        s.Money(dblMoney[i], 16, (size_t)4);
        s.Print(true);     
    }
    FreeLibrary(hLib);
}
getchar();

return 0;
}

// Output
// =============
// $        0.95
// $    4,567.90
// $   -2,345.68
// $   21,356.12
// $        0.99


Frederick J. Harris


Patrice Terrier

Sometimes C++ could make things easier, especially when dealing with ANSI or UNICODE

Rather than
WNDCLASSEX wc;
memset(&wc,0,sizeof(wc));

what about
WNDCLASSEX wcx = { 0 };

Same code syntax for 32 or 64-bit, and ansi or unicode.
Patrice Terrier
GDImage (advanced graphic addon)
http://www.zapsolution.com

James C. Fuller

Fred,
  Thank you for your detailed explanation.

I, like Patrice, have always used = {0} to zero.

James

James C. Fuller

Fred,
  Maybe you can find some useful code in this:
https://bitbucket.org/Airr/jade

Forum post by Armando:
Quote
To use, just copy "jade.h", "header.inc", and "runtime.inc" to your project folder.
Then just add
#include "jade.h"
To your source.
There are demos for SDL1, SDL2, FLTK, wxWidgets, and QT4 in the repository.
At the root is demo.cpp, which tests the string functions in runtime.inc.
Enjoy!
AIR.

James

Frederick J. Harris

Quote
what about
WNDCLASSEX wcx = { 0 };

Yes! :)

In the heat of doing battle I had forgotten about that.  But I occasionally use it too.