I was working on a trial task today.
The idea was to create a simple GUI application that downloads a file using HTTP protocol.
This application should contain:
- Text box, in which a user can enter a URI.
- Start button, custom draw
- Stop button, custom draw
- Log box, in which log messages are displayed
The application should also:
- Show a progress of downloading
- When app window is minimized, show an icon in the tray area, hide from taskbar
- When clicked on the tray icon, restore main window
- Report network errors
The major requirement is the size of the executable file.
It should be as little as possible.
My answer is 14,848 bytes, although I did a little cheating ;)
The VC solution is here, in case you don’t want to listen the whole story.
If I only had a several hours more, I could make it much better!
Forgive the style and ugly code, I was in a hurry to write the whole thing in time.
The sequence
At start I decided that it will be more interesting and I will have some new experience if I create a single threaded application for that problem.
So I started with WSAAsyncSelect to arrange Winsock messages in a window procedure.
I was also using WSARecv and WSASend along with completion procedures.
But soon I realized, that things are getting too complicated for the task I am solving so I thought of another way.
Finally I decided to go with one timer and select() function.
Here is the final scheme.
Imagine one thread, which loops windows messages in a regular manner.
- Start of operation will be initiated when WM_COMMAND message is received (Start button clicked).
- Parse the URI string and fetch server address by using gethostbyname().
-
Create TCP connection to the server.
I assumed that TCP connect() is not a significally slow operation, but in some cases the application can block at this point because we have only one thread. This is not a problem if WSAConnect is used in asynchrnonous manner instead ofconnect())
-
Send request to the server using WSASend().
This allows the thread to continue without blocking. - When send operation completes, create timer to process receive operations on socket. Later, I noticed that in my application I created timer just right after WSASend() call regardless of the asynchronous completion and that is not effecient because there is no need to check for data until first send operation is successful.
-
When timer message arrives, use select() with small timeout to test for incoming data.
I could be using ioctlsocket() to get the same result but select() puts the thread into allertable state
so the completion routines can be executed. -
If new data is available, receive it and save to the file.
I also assumed here that synchronous file operation is fine for this case.
In a few words this is not the best solution but I needed to make it quickly so it worked well.
So as I said, it worked pretty well but now, when I am writing about it a couple of hours later, I can clearly see the problems in my own code:
-
When error condition is met, it is needed to bring the controls and the socket to their initial states.
It would be much better to have one function that enables controls, shutdowns connection and etc.
instead of doing it on each error condition. - When main window is closed eventually my code leaves all network stuff to OS. In other words I forgot to close connection when application is exitting.
- HTTP parsing and olther points in code allow buffer overflows.
- I used many C runtime functions but to keep the exe size short I needed to get rid of the C runtime.
That’s why
#pragma comment(linker,"/NODEFAULTLIB")
did not worked for me. - I used a lot of stack variables though a couple of global buffers would allow me to save some memory.
Anyway, I went further and my next problem was the shortening of the file size.
First, I used the lcc compiler and tried it with different options but I noticed that VC 7.0 compiler can produce even better results.
LCC made 48K executable and VC 7.0 produced 36K file.
I turned all optimizations and compiled it as C code.
Finally I came to 36,864 bytes executable and I did not have more time to work on the size.
By the way, it was possible to get better results if I hade time to remove all uses of C runtime library.
Then comes some cheating!
In requirements to this task nothing was restricted so I thought why not to use a code compression?
I used UPX to compress the exe file and after that the file size became 14,848 bytes.
I don’t know how good I performed comparing with others in this trial but it was a lot of fun for me!
UPDATE: I went home and compiled the code again with some minor modifications.
VC 7.0 produced 25Kb executable.
UPX was able to compress the size down to 15Kb.
Since I know that it is possible to make it at least two times smaller I will be happy to hear some thoughts.