IT-Consultant: Frederick J. Harris > Discussion

Reducing Program Size In UNICODE And X64 Builds With Matt Pietrek’s LibCTiny.lib

(1/3) > >>

Frederick J. Harris:
     This is yet another continuation of my previous posts on this topic from here…

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

…and here…

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

However, we’ll start from absolute fundamentals.  From Jeffrey Richter and Christopher Nasarre’s book “Windows Via C\C++” published by Microsoft Press, which, by the way, covers Windows internals in much more detail than Charles Petzold’s “Programming Windows” book, they state this…


--- Quote ---When you use Microsoft Visual Studio to create an application project the integrated environment sets up various linker switches so that the linker embeds the proper type of subsystem in the resulting executable.  This linker switch is /SUBSYSTEM:CONSOLE for CUI applications and /SUBSYSTEM:WINDOWS for GUI applications.  When the user runs an application, the operating system’s loader looks inside the executable image’s header and grabs this subsystem value.  If the value indicates a CUI-based application, the loader automatically ensures that a text console window is available for the application – such as when the application is started from a command prompt - and, if needed, another one is created - such as when the same CUI based application is started from Windows Explorer.  If the value indicates a GUI based application, the loader doesn’t create the console window and just loads the application.  Once the application starts running, the operating system doesn’t care what type of UI your application has.

Your Windows application must have an entry point function that is called when the application starts running.  As a C/C++ developer, there are two possible entry point functions you can use:

int WINAPI _tWinMain(HINSTANCE hInstanceExe, HINSTANCE, PTSTR pszCmdLine, int nCmdShow);
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]);

Notice that the exact symbol depends on whether you are using Unicode strings or not.  The operating system doesn't actually call the entry point function you write.  Instead,  it calls a C/C++ runtime startup function implemented by the C/C++ runtime and set at link time with the -entry: command line option.  This function initializes the C/C++ runtime library so that you can call functions such as malloc and free.  It also ensures that any global and static C++ objects that you have declared are constructed properly before your code executes.  Table 4-1 tells you which entry point to implement in your source code and when.

--- End quote ---


--- Code: ---Table 4-1  Application Types and Corresponding Entry Points

Application Type                     Entry Point              Startup Function Embedded In Your Executable
=========================================================================================================
GUI Application that wants ANSI      _tWinMain (WinMain)      WinMainCRTStartup
characters and striungs

GUI Application that wants Unicode   _tWinMain (wWinMain)     wWinMainCRTStartup
characters and strings

CUI Application that wants ANSI      _tmain (main)            mainCRTStartup
characters and strings

CUI Application that wants Unicode   _tmain (wmain)           wmainCRTStartup
characters and strings

--- End code ---

There’s quite a bit to say about the above, especially this…


--- Quote ---The operating system doesn't actually call the entry point function you write.  Instead,  it calls a C/C++ runtime startup function implemented by the C/C++ runtime and set at link time with the -entry: command line option.  This function initializes the C/C++ runtime library so that you can call functions such as malloc and free.  It also ensures that any global and static C++ objects that you have declared are constructed properly before your code executes.

--- End quote ---

Let’s start with the latter…


--- Quote ---It also ensures that any global and static C++ objects that you have declared are constructed properly before your code executes.

--- End quote ---

What they are talking about there are objects declared and instantiated at global scope outside of any function such as my GlobalClass object below…


--- Code: ---// cl GlobalCon.cpp /O1 /Os
// App includes C Runtime.  126,464 bytes
#include <stdio.h>
FILE* fp=NULL;

class GlobalClass    // C++ Class definition at global scope, which is typical.  The
{                    // Class definition does not by itself, cause any instances
 public:             // of the class to be created, i.e., instantiated.
 GlobalClass()
 {
  fp=fopen("Output.txt","w");
  fprintf(fp, "Entering GlobalClass Constructor!\n");
  iCounter=1;
  fprintf(fp,"  iCounter = %d\n",iCounter);
  fprintf(fp,"Leaving GlobalClass Constructor!\n\n");
 }

 ~GlobalClass()
 {
  fprintf(fp, "Entering GlobalClass Destructor!\n");
  iCounter=0;
  fprintf(fp,"  iCounter = %d\n",iCounter);
  fprintf(fp,"Leaving GlobalClass Destructor!\n\n");
  fclose(fp);
 }

 private:
 int iCounter;
} gc;             // However, this line causes a GlobalClass object to be instantiated
                  // before the operating system calls the main() function.
int main()
{
 printf("Hello, World!\n");
 if(fp)
 {
    fprintf(fp,"Entering main()\n");
    fprintf(fp, "  Hello, World!\n");
    fprintf(fp,"Leaving main()\n\n");
 }
 getchar();

 return 0;
}

--- End code ---

In the above code note this line at the trailing brace of the class definition…


--- Quote ---} gc;             // However, this line causes a GlobalClass object to be instantiated
                  // before the operating system calls the main() function.

--- End quote ---

That will necessitate that the C/C++ runtime create an object of the class before calling the main function.  The code to do this, which is extremely, extremely involved, runs within the mainCRTStartup or WinMainCRTStartup referenced and referred to above.  Did I say it was complicated?  Matt Pietrek went into the details of this in his LibCTiny article I’ve also provided links for his topic …

“The Soft Underbelly Of Constructors”

Anyway, here is the output of the above program which I had to direct mostly to a text file to catch the creation and destruction output statements.  If you would try to output this to the console you would miss all the important stuff because it happens before and after the existence of the console…


--- Code: ---Entering GlobalClass Constructor!
  iCounter = 1
Leaving GlobalClass Constructor!

Entering main()
  Hello, World!
Leaving main()

Entering GlobalClass Destructor!
  iCounter = 0
Leaving GlobalClass Destructor! 

--- End code ---

Matt Pietrek provided the code in his LibCTiny.lib to take care of all this.  It adds maybe a kilobyte to the executable size.  But do we really need it?  I very seldom do.  In fact, I can only think of one instance where I’ve ever done this, and likely with a little experimentation I could have found another way without it.  So what I did was eliminate it.  In the examples I’m going to show in this thread that code won’t be there.  So you won’t be able to instantiate global C++ objects before main/WinMain is entered.  However, if you really want that code, Matt Pietrek provided it and it isn’t hard to replace it.  It'll add a about a kilobyte or so to your executable.

Next issue not really mentioned above from the Richter and Nasarre quote is command line arguments.  Any guesses how I’ve dealt with that?  You’ve guessed it.  They’re gone!  The elimination of the code to process command line arguments and pass them to main/wmain or WinMain/wWinMain involves about another kilobyte or so of startup code.  Again, I seldom use command line arguments.  Of course, they have their place (more so in the distant past, I think), and the code to deal with them can be found in Pietrek’s original code.   You can obtain it from there if you need it.  Here is Matt’s original mainCRTStartup() function which shows his call to his _ConvertCommandLineToArgcArgv(), which deals of course with the command line arguments, and his calls to atexitinit() and initterm() which are involved in setting up the global constructors…


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

--- End code ---

With the elimination of that code from the CRT startup what’s left?  Well, not much!  What’s left is what you’ll see below in my crt_con_a.cpp and  crt_con_w.cpp files, which are for, respectively, ansi or wide character initializations…

crt_con_a.cpp   // mainCRTStartup, or lack thereof, for asci builds


--- Code: ---//========================================================================================
//                 Developed As An Addition To Matt Pietrek's LibCTiny.lib
//                              By Fred Harris, January 2016
//
// cl crt_con_a.cpp /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN
//========================================================================================
#include <windows.h>
#pragma comment(linker, "/defaultlib:kernel32.lib")
#pragma comment(linker, "/nodefaultlib:libc.lib")
#pragma comment(linker, "/nodefaultlib:libcmt.lib")
extern "C" int __cdecl  main();

extern "C" void __cdecl mainCRTStartup()
{
 int iReturn = main();
 ExitProcess(iReturn);
}

--- End code ---

crt_con_w.cpp   // wmainCRTStartup, or lack thereof, for wide character builds


--- Code: ---//========================================================================================
//                 Developed As An Addition To Matt Pietrek's LibCTiny.lib
//                              By Fred Harris, January 2016
//
// cl crt_con_w.cpp /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN
//========================================================================================
#include <windows.h>
#pragma comment(linker, "/defaultlib:kernel32.lib")
#pragma comment(linker, "/nodefaultlib:libc.lib")
#pragma comment(linker, "/nodefaultlib:libcmt.lib")
extern "C" int __cdecl  wmain();

extern "C" void __cdecl wmainCRTStartup()
{
 int iReturn = wmain();
 ExitProcess(iReturn);
}

--- End code ---

If you don't mind getting your hands a little dirty at the command prompt working with this stuff, let's create a library for ourselves and put both those functins in it as well as this printf implementation below...

printf.cpp   // console output wide/narrow versions


--- Code: ---//=====================================================================================
//               Developed As An Addition To Matt Pietrek's LibCTiny.lib
//                            By Fred Harris, January 2016
//
//                    cl printf.cpp /GS- /c /W3 /DWIN32_LEAN_AND_MEAN
//=====================================================================================
#include <windows.h>
#include <stdarg.h>
extern "C" int __cdecl  printf(const char*    format, ...);
extern "C" int __cdecl wprintf(const wchar_t* format, ...);
#pragma comment(linker, "/defaultlib:user32.lib") 


extern "C" int __cdecl printf(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(GetStdHandle(STD_OUTPUT_HANDLE), szBuff, retValue, &cbWritten, 0);

 return retValue;
}


extern "C" int __cdecl wprintf(const wchar_t* format, ...)
{
 wchar_t szBuffW[1024];
 char szBuffA[1024];
 int iChars,iBytes;
 DWORD cbWritten;
 va_list argptr;
           
 va_start(argptr, format);
 iChars = wvsprintfW(szBuffW, format, argptr);
 va_end(argptr);
 iBytes=WideCharToMultiByte(CP_ACP,0,szBuffW,iChars,szBuffA,1024,NULL,NULL);
 WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), szBuffA, iBytes, &cbWritten, 0);

 return iChars;
}

--- End code ---

I'll provide a make file to make it easy, although you could simply run those three command line compilation strings included in the text headers for the above three functions to create *.obj files, then use lib.exe commands to create and add them to the library as I described in my last essay.  But in any case, here is the make file to run through nmake.  Save it to TCLib.mak...


--- Code: ---PROJ       = TCLib

OBJS       = crt_con_a.obj crt_con_w.obj printf.obj
       
CC         = CL
CC_OPTIONS = /D "_CRT_SECURE_NO_WARNINGS" /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN

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

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

--- End code ---


Running the above make file using nmake produced this console output for me…


--- Code: ---C:\Code\VStudio\VC15\LibCTiny\x64\Test8>nmake TCLib.mak

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

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

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

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

printf.CPP
        LIB /NODEFAULTLIB /machine:x64 /OUT:TCLib.LIB crt_con_a.obj crt_con_w.obj printf.obj
Microsoft (R) Library Manager Version 14.00.23506.0
Copyright (C) Microsoft Corporation.  All rights reserved.

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

--- End code ---

continued...

Frederick J. Harris:
Let's build some very simple executables to test this stuff.  But first I need to say a word about include files.  In C or C++ when you place include file names within '<IncludeFile.h>' brackets, that causes the preprocessor to look in specific places for include files, such as the INCLUDE environment path, or paths specified when the compiler was installed.  If, on the other hand, one places include file names within double quotes, i.e.,


--- Code: ---#include "MyAppInclude.h"

--- End code ---

...then the compiler looks for the include in the current directory first.  That is what we want.  I have had great difficulty using the Visual Studio C/C++ includes for this work involving eliminating the C runtime.  Especially with stdio.h, but to a lesser extent with string.h.  What happens is that something or other, either in those includes or includes included from those includes, triggers the loading of the C runtime.  Or there are definition conflicts with my replacement functions.  The decoration of C/C++ function declarations in the headers with bizarre macros is extreme.  In any case, to solve the problem I made my own replacements for stdio.h, string.h and tchar.h.  They are minimal to be sure.  stdio.h only has declarations for four functions.  Here are stdio.h and tchar.h. Put them in the directory where you are doing this work...   

stdio.h


--- Code: ---// stdio.h
#ifndef stdio_h
#define stdio_h

extern "C" char __cdecl getchar();

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

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

#endif

--- End code ---

tchar.h


--- Code: ---// tchar.h
#ifndef tchar_h
#define tchar_h

#ifdef  _UNICODE
   typedef wchar_t  TCHAR;
   #define _tmain   wmain
   #define _T(x)    L## x
   #define _tprintf wprintf
#else
   typedef char     TCHAR;
   #define _tmain   main
   #define _T(x)    x
   #define _tprintf printf
#endif

#endif

--- End code ---


For stdio.h, don’t include this like so…


--- Code: ---#include <stdio.h>

--- End code ---

…nor this way either…


--- Code: ---#include <cstdio>

--- End code ---

Rather, do this…


--- Code: ---#include “stdio.h”

--- End code ---

Before proceeding with our first test program, make sure you have TCLib.lib created, and within it are crt_con_a.obj, crt_con_w.obj, and printf.obj.  You can test that like so...

C:\........\>lib TCLib.lib /list /verbose  [ENTER]

....and it'll list those three objs.  Hopefully.  Also, you'll then need stdio.h and tchar.h in your working directory.  Here is our first test program to exercise what little we have so far…


--- Code: ---// cl Ex01.cpp /O1 /Os /GS- /link TCLib.lib kernel32.lib
#define UNICODE         // 2,560 bytes x64 UNICODE
#define _UNICODE
#include <windows.h>
#include "tchar.h"

int _tmain()
{
 return 0;
}

--- End code ---

That should give us the smallest x64 asci or Unicode program I know how to produce that still has a main() function, and its coming in 2,560 bytes for me with VC19 (from Visual Studio 2015).  That'll give us an idea of the baseline we're starting from.  Lets try adding a wprintf() to it to generate our classic “Hello, World!”…


--- Code: ---// cl Ex02.cpp /O1 /Os /GS- /link TCLib.lib kernel32.lib
#define UNICODE          // 3,072 bytes x64 UNICODE
#define _UNICODE
#include <windows.h>
#include "stdio.h"
#include "tchar.h"

int _tmain()
{
 _tprintf(_T("Hello, World!\n"));
 return 0;
}

--- End code ---

So we’re up to 3 kilobytes with wprintf or printf.  That cost us 512 bytes.  I don’t really consider console programs complete unless I have something to keep the console from closing in case the program was started from Explorer, so let’s add a getchar() to our TCLib.lib.  Here is that code.  You can compile it to an object file with the supplied command line string…


--- Code: ---//=====================================================================================
//               Developed As An Addition To Matt Pietrek's LibCTiny.lib
//                            By Fred Harris, January 2016
//
//              cl getchar.cpp /O1 /Os /GS- /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;
}

--- End code ---

That's pretty 'industrial strength' and not really a minimal implementation, but I wrote it so I'll live with it.  To add that to our TCLib.lib simply execute this line in your console window…


--- Code: ---Lib TCLib.lib getchar.obj  [ENTER]

--- End code ---

To make sure it worked just dump the contents of TCLib.lib like so…


--- Code: ---Lib TCLib.lib /list /verbose

--- End code ---

My console dump looks like so…


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

getchar.obj
crt_con_a.obj
crt_con_w.obj
printf.obj

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

--- End code ---

And now with a getchar() to keep the console open (if started from Explorer), here is Ex03.cpp…


--- Code: ---// cl Ex03.cpp /O1 /Os /GS- /link TCLib.lib kernel32.lib
#define UNICODE          // 3,072 bytes x64 UNICODE
#define _UNICODE
#include <windows.h>
#include "stdio.h"
#include "tchar.h"

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

--- End code ---

So that doesn’t seem to have cost us anything, at least this session with VC19, as we’re still at 3 k for x64 UNICODE.

Now let’s move on to GUIs!  As mentioned in the Richter/Nasarre quote, we’ll need some kind of WinMain() for that.  You hopefully already have a TCLib.lib which works, so let’s just add a crt_win_a.cpp and a crt_win_w.cpp to it (after compiling to objs, of course).  Here is crt_win_a.cpp…


--- Code: ---// crt_win_a.cpp
// cl crt_win_a.cpp /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN
#include <windows.h>
#pragma comment(linker, "/defaultlib:kernel32.lib")
#pragma comment(linker, "/nodefaultlib:libc.lib")
#pragma comment(linker, "/nodefaultlib:libcmt.lib")
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int);

extern "C" void __cdecl WinMainCRTStartup(void)
{
 int iReturn = WinMain(GetModuleHandle(NULL),NULL,NULL,SW_SHOWDEFAULT);
 ExitProcess(iReturn);
}

--- End code ---


And here is crt_win_w.cpp…



--- Code: ---// crt_win_w.cpp
// cl crt_win_w.cpp /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN
#include <windows.h>
#pragma comment(linker, "/defaultlib:kernel32.lib")
#pragma comment(linker, "/nodefaultlib:libc.lib")
#pragma comment(linker, "/nodefaultlib:libcmt.lib")
int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int);

extern "C" void __cdecl wWinMainCRTStartup(void)
{
 int iReturn = wWinMain(GetModuleHandle(NULL),NULL,NULL,SW_SHOWDEFAULT);
 ExitProcess(iReturn);
}

--- End code ---

Here is the command line session output of compiling both those files to object files, adding them to TCLib.lib, then dumping the contents of that library…


--- Code: ---C:\Code\VStudio\VC15\LibCTiny\x64\Test8>cl crt_win_a.cpp /D /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN
Microsoft (R) C/C++ Optimizing Compiler Version 19.00.23506 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

crt_win_a.cpp

C:\Code\VStudio\VC15\LibCTiny\x64\Test8>cl crt_win_w.cpp /O1 /Os /GS- /c /W3 /DWIN32_LEAN_AND_MEAN
Microsoft (R) C/C++ Optimizing Compiler Version 19.00.23506 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

crt_win_w.cpp

C:\Code\VStudio\VC15\LibCTiny\x64\Test8>Lib TCLib.lib crt_win_a.obj crt_win_w.obj
Microsoft (R) Library Manager Version 14.00.23506.0
Copyright (C) Microsoft Corporation.  All rights reserved.

Replacing crt_win_a.obj
Replacing crt_win_w.obj

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

crt_win_a.obj
crt_win_w.obj
getchar.obj
printf.obj
crt_con_w.obj
crt_con_a.obj

--- End code ---


Now with WinMain and wWinMain entry points made known within the C Runtime Start up procedures we can try our luck with our GUI Form1.cpp….


--- Code: ---// cl Form1.cpp /O1 /Os /GS- /link TCLib.lib kernel32.lib user32.lib gdi32.lib
// 3,072 Bytes x64 UNICODE
#define UNICODE   // /entry:wWinMainCRTStartup
#define _UNICODE
#include <windows.h>
#include "tchar.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 _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevIns, LPTSTR lpszArgument, int iShow)
{
 WNDCLASSEX wc={};
 MSG messages;
 HWND hWnd;

 wc.lpszClassName = _T("Form1");
 wc.lpfnWndProc   = fnWndProc;
 wc.cbSize        = sizeof(WNDCLASSEX);
 wc.hInstance     = hInstance;
 wc.hbrBackground = (HBRUSH)COLOR_BTNSHADOW;
 RegisterClassEx(&wc);
 hWnd=CreateWindowEx(0,_T("Form1"),_T("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;
}

--- End code ---

And our luck’s not so good, ehh?  Here’s what I got….


--- Code: ---C:\Code\VStudio\VC15\LibCTiny\x64\Test8>cl Form1.cpp /O1 /Os /GS- /link TCLib.lib kernel32.lib user32.lib gdi32.lib
Microsoft (R) C/C++ Optimizing Compiler Version 19.00.23506 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

Form1.cpp
Microsoft (R) Incremental Linker Version 14.00.23506.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:Form1.exe
TCLib.lib
kernel32.lib
user32.lib
gdi32.lib
Form1.obj
Form1.obj : error LNK2019: unresolved external symbol memset referenced in function wWinMain
Form1.exe : fatal error LNK1120: 1 unresolved externals

--- End code ---


If you are reading this Patrice, remember you mentioned trying this to zero out a WNDCLASSEX struct…


--- Code: ---WNDCLASSEX wc={};

--- End code ---

Well, now you know how {} on a struct is implemented!  :)  It’s a bit disconcerting to realize the compiler isn’t doing exactly what you would expect, isn’t it?

But not to worry!  We can get around that.  In the above command line compilation string for Form1…


cl Form1.cpp /O1 /Os /GS- /link TCLib.lib kernel32.lib user32.lib gdi32.lib

…we are telling cl to optimize for size and not put any debugging symbols in the executable.  Lets remove the /O1 and /Os and in its place substitute /Oi, which tells the compiler to use compiler intrinsics when possible…

https://en.wikipedia.org/wiki/Intrinsic_function

And this from Microsoft…

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

Our cl string then becomes this…


cl Form1.cpp /Oi /GS- /link TCLib.lib kernel32.lib user32.lib gdi32.lib

…and we succeed!  Which is an understatement.  Look at the size of this x64 UNICODE executable.  Its 3,072 bytes!  Run it and you’ll see it works just fine.

Next thing to do is get this all working with classes, particularly my String Class.

To be continued………

James C. Fuller:

--- Quote ---If you are reading this Patrice, remember you mentioned trying this to zero out a WNDCLASSEX struct…
WNDCLASSEX wc={};

--- End quote ---

Fred,
  I believe that should be:

--- Code: ---WNDCLASSEX wc={0};

--- End code ---

James

Frederick J. Harris:
Thanks Jim, you're likely right!  Alas, I need to beef up my knowledge of the usage of that construct.  I picked that up from browsing C++ forums I think.  Likely I fell into the usage of not putting the zero in there by accident.  Never saw that described in any of my various C or C++ books. 

Putting it in doesn't change anything though in the program.  It'll still error out on a missing memset call.  That memset thing is kind of interesting.  It seems to be some kind of primal force or something.  When I first ran into problems with it on my post a couple weeks ago, I tried substituting a for loop for it, but as I said, the optimizer substituted a memset call for the for loop which still resulted in the linker error missing memset.  Then I decided to get tricky and call ZeroMemory instead, which, if my memory serves me right (and it might not), is a Windows function - not C Runtime.  But got the same missing memset linker error on that too!  So that's why I'm saying it seems to be some kind of primal thing in the operating system.  All kinds of stuff results in memset calls. 

But the odd thing about it that kind of doesn't make sense is that it is a compiler intrinsic, and the compiler should know about it, but doesn't seem to in some instances.  In researching the topic I discovered to my pleasure that some of the most important string functions were intrinsics.  Here is a list of them...

memset
wmemset
strcpy
wcscpy
strcat
wcscat
strcmp
wcscmp

So I didn't need to implement them in my versions of LibCTiny.lib because the compiler always had them.  You do need function prototypes though.  But the memset is different.  It seems to be needed in some instances. 

James C. Fuller:
Fred,
Not sure this is relevant to any of your work but to help stop optimizations check out Volatile variables.

James

Navigation

[0] Message Index

[#] Next page

Go to full version