Powerbasic Museum 2020-B

IT-Consultant: Patrice Terrier => C++ programming (SDK style) => Topic started by: Patrice Terrier on January 07, 2014, 11:02:08 AM

Title: Layered or DWM Composited Spinner
Post by: Patrice Terrier on January 07, 2014, 11:02:08 AM
This is a two projects in one, to show the difference between using a layered window, and a DWM composited window.

Both are able to play a PNG animation in TOP_MOST mode using variable opacity.

The purpose of a Spinner control is to inform the user that a lengthy process or critical task is running.
But it could also be used to display a nag screen or any transparent PNG file in overlay mode.

SPINNER_LAYERED

Is using the WS_EX_LAYERED extended style, in this type of window the mouse could be used to click on another window overlayed by the spinner as long as you click on a transparent part (empty region).

The layered version uses in SpinnerInit a timer to perform the animation:
void SpinnerInit (IN HWND hParent, IN HWND hWnd, IN WCHAR* szFileName, IN long nSpeedDelay) {

    if (IsWindow(hParent) == 0) { hParent = GetDesktopWindow(); }

    gn_FrameCount = 0;
    if (ghImg) { GdipDisposeImage(ghImg); ghImg = 0; }

    if (GdipLoadImageFromFile(szFileName, ghImg) == 0) {
        GdipGetImageWidth(ghImg, gn_W);
        GdipGetImageHeight(ghImg, gn_H);
        if (gn_W > gn_H) {
            gn_FrameCount = gn_W / gn_H;
            gn_IsHorizontal = -1; }
        else {
            gn_FrameCount = gn_H / gn_W;
            gn_IsHorizontal = 0;
        }

        RECT rw = {0}; GetWindowRect(hParent, &rw);
        MoveWindow(hWnd, rw.left + ((rw.right - rw.left - gn_H) / 2), rw.top + ((rw.bottom - rw.top - gn_H) / 2), gn_H, gn_H, 0);
       
        if (nSpeedDelay == 0) {
            if (gn_FrameCount < 4) {
                gn_DELAY = 75; }
            else if (gn_FrameCount < 8) {
                gn_DELAY = 50; }
            else if (gn_FrameCount < 12) {
                gn_DELAY = 37; }
            else {
                gn_DELAY = TIMER_DELAY;
            }
        }

        SetTimer(hWnd, TIMER_SPINNER, 0, 0); // 66 FPS
    }
}


SPINNER_DWM

Is using a DWM composited window, in this type of window the mouse couldn't be used to click on another window overlayed by the spinner even if the region is fully transparent.

This version uses a child thread rather than a timer to perform the animation, this is the best solution when used on modern hardware built on multi-core I7 (with other config better to use a timer):
long StartAnimation (IN long Delay) {
    long nRet = LB_ERR;
    gn_DELAY = Delay;
    DWORD dwThreadId = 0;
    HANDLE hThread = CreateThread(NULL,                                  // default security attributes
                                  0,                                     // use default stack size 
                                  (LPTHREAD_START_ROUTINE ) Animate,     // thread function name
                                  (LPVOID) Delay,                        // argument to thread function
                                  0,                                     // use default creation flags
                                  &dwThreadId);                          // returns the thread identifier
    if (hThread) { nRet = 0; Sleep(100); }
    CloseHandle(hThread);
    return nRet;
}


HOW TO USE IT

Both window can be dragged at any location, and you have to press the Escape key to close the Spinner.

This code is provided altogether with a small subset of the GDIPLUS Flat API to keep the code as small as possible, and also because it is a mandatory to work in full 32-bit with alpha channel to render correctly the variable opacity.

Several PNG files designed specifically for the purpose of Spinner animation are provided into the attached SPINNER_PNG.zip file. All of them have been reworked with Photoshop to produce the best visual output, and credit must also be given to the original artists who created the original animated GIF files that were transposed into PNG.

The projects have been set to create VS2010+ 64-bit EXE by default, the bin EXE files are not provided inside of the ZIP file, you have to recompile the code first.

In order to play a spinner (with either version):

Unzip first the PNG files into a dedicated folder, and use Explorer to drag and drop any of the PNG file on the Spinner.exe or DwmSpinner.exe.

Title: DWM Composited Spinner (PowerBASIC version)
Post by: Patrice Terrier on January 09, 2014, 10:05:17 AM
Here is the translation of the DWM Spinner code into PowerBASIC.

The code must be used altogether with the PNG files attached to the first post of this tread.

I have translated only the DWM composited version to PB, because it is the most advanced one.
Note: This code has been moved from the PowerBASIC forum here, because i couldn't post anymore ZIP or picture attachments on PB. And also because message notifications is broken.

About the source code:
I have used exactly the same code for C++ and PB, using UNICODE and SDK coding style, and a small subset of the GDIPLUS Flat API.

In order to create the transparent composited window, DWM must be first enabled with zSetCrystalBehindMode and we fool the OS by using a fake rectangular region with CreateRectRgn(-1, -1, 0, 0). This is what allows us to render the composition directly onto the double buffer of the DirectDraw surface.

However in order to render the transparency correctly, we need first to clear the background of our composited window, just like what we do in OpenGL when using glClear, this is the reason of the creation of a NULL brush to paint the permanent bitmap background, before redrawing each frame of the animation like this:
Quote'// Create a null brush, to clear the background of our permanent bitmap.
hBrush = CreateSolidBrush(%NULL)
SelectObject(g_hDC, hBrush)
FillRect(g_hDC, r, hBrush)
DeleteObject(hBrush)

Once our permanent bitmap has been composited then we BitBlt it onto the DD surface.
Note: BitBlt is able to deal with alpha channel as long as we use it in full 32-bit mode, it does a direct copy of our memory DIB block into the graphic card GPU (at least this what years of practice teached me).

If you have questions about this code, feel free to ask here.
Title: Re: Layered or DWM Composited Spinner
Post by: Theo Gottwald on January 09, 2014, 02:50:50 PM
Looks amazing, Patrice!
Title: Amazing size comparison between C++ and PowerBASIC
Post by: Patrice Terrier on January 09, 2014, 04:42:43 PM
Because i like to produce the smallest and fastest code for my projects.

I was worried to see that i was able to compile the PowerBASIC "DWM spinner" code in a little 26 Kb, while the C++ 32-bit version was 35 Kb, and the 64-bit version 41 Kb.

Thus i challenged myself to learn more about the arcane of C++ optimization, and finally i was able to produce a resulting C++ 32-bit as small as 10 Kb, and a C++ 64-bit as small as 12 Kb.

I never thought i could produce a 64-bit EXE as small as 12 Kb for this DWM spinner utility,
and like the {Droopy dog} and {Frederick J. Harris} would say:
Quote"you know what... i am happy".

See below the serie of screen shots, showing the parameters i am using to reach this result, and the new C++ DwmSpinner.zip attached to this post (both 32-bit and 64-bit binary EXE are into the ZIP file).

64-bit EXE half the size of PB's 32-bit, could you believe this!

...
Title: Size reduction of the DWMspinner PowerBASIC version
Post by: Patrice Terrier on January 11, 2014, 03:25:35 PM
I wanted to understand why there was such a huge difference of size between my 12 Kb C++ code and the {bloated} 26Kb PowerBASIC version.

After doing further optimization without significant code size reduction.

I took the decision to get rid of the #INCLUDE "WIN32API.INC" creating a small declaration subset, with only those needed by the application.

And doing that, BINGO, i was able to reduce the size down, from 26 Kb to 16 Kb, that is only 4 Kb larger than the C++ 64-bit version, thus i am attaching the new optimized PB version to this post :)

...
Title: Re: Layered or DWM Composited Spinner
Post by: Patrice Terrier on January 12, 2014, 05:21:31 PM
I have been able to further reduce the size of the 64-bit C++ EXE down to 11 Kb with Visual Studio 2013.
That was not obvious, because the linked .LIB files are not the same than those used in VS 2010, and there is almost nothing in the whole MSDN, showing how to get rid of the CRT. I had to fight whith the compiler and the linker for a couple of hours, just to figure how to do it.  >:(

If you use VS2013, and want to see the settings i have used in the project, then let me know, and i shall post the VS2013 project there.

Note: one of the main optimization is to use only WCHAR (wchar_t) and get rid of any string/wstring engine (usually a saving of 12 Kb), and avoid any OOP or GDIPLUS class encapsulation.

...

Title: Ultimate optimization (yes i did it)
Post by: Patrice Terrier on January 13, 2014, 08:21:11 PM
I have been able to perform the ultimate optimization of the DWMspinner.

And the result is:

PB32:  DWMspinnerPB.exe = 11260 bytes, and C++64: DWMspinner = 11264 bytes

A difference of only 4 bytes, could you believe this  ???

To achieve this result, i have removed all the extra encapsulations done by the languages, using my own set of functions based on direct call to the core API.

nothing could beat SDK coding style  ;D

See the attached ZIP files

Title: Re: Layered or DWM Composited Spinner
Post by: Frederick J. Harris on January 15, 2014, 02:38:54 AM
Interesting and nice work Patrice!  When I have time I'm going to look at what you did in more detail and see if I can't shave a few bytes off my recent work.  I hadn't done anything real detailed in my work - just Os and O1 I believe, so maybe with a few more arcane settings I can make it a little smaller. 
Title: Re: Layered or DWM Composited Spinner
Post by: Patrice Terrier on January 15, 2014, 10:20:05 AM
Frederick

Thanks for the feedback.

QuoteI'm going to look at what you did in more detail and see if I can't shave a few bytes off my recent work.

Indeed, try to write C++, just like plain C.  :)


Title: Re: Layered or DWM Composited Spinner
Post by: Frederick J. Harris on January 16, 2014, 12:10:25 AM
I bet I could slim that grid control down by translating that String::Parse() back to a simple C function, converting the whole thing to C (build VTables by hand like some of my other earlier C examples), and use Microsoft's versions of the string primitives rather than the C Runtime versions, i.e., lstrcpy() instead of strcpy(), etc.  That way I could eliminate linking with the C Runtime.  Actually, that was the trick Microsoft uses in ATL, I believe.  They even have a #define _NO_CRT_xxx or something to that effect.  I guess its a matter of how much effort one wants to make and how many K one will get out of it.  If one wouldn't value one's time much, one could just use assembler.  If one wouldn't need floating point math one could save a whooping 3 or 4K or so by not compiling in floating point math! :)
Title: Re: Layered or DWM Composited Spinner
Post by: José Roca on January 16, 2014, 01:24:09 AM
If you're going to avoid any C++ features, why don't use a plain ansi C compiler instead?
Title: Re: Layered or DWM Composited Spinner
Post by: Patrice Terrier on January 16, 2014, 09:40:37 AM
José--

QuoteIf you're going to avoid any C++ features, why don't use a plain ansi C compiler instead?
It is all a matter of the kind of application you have to write.
When writing utilities and DLLs, i think they should be as fast and as small as possible.

When writing complex CRM or large multimedia projects, like what i am doing with WinDev, then the main factor is efficiency and how long it would take to produce the final EXE. This is the reason why i am using several tools each one having its own set of merits.

For years Bob Zale told us, that 64-bit produces larger code than 32-bit, and based on my own experimentation i would say that this is only a matter of the coding style being used. The PowerBASIC language encapsulation is also producing largest code than a direct call to the core API (aka: 26 Kb down to 11 Kb).

I like the VISUAL STUDIO editor, and especially the new features of VS2013, but what i like the most when using C++ is that i do not have to worry anymore about using the right include files to use the latest and newest API.

The main reason why i have selected C++, was to convert my DLLs to 64-bit, and the use of the vector and the wstring engine helped me much to convert my PowerBASIC code, and allows me to maintain all sources in parallel with a minimum of effort.
Title: Re: Layered or DWM Composited Spinner
Post by: James C. Fuller on January 16, 2014, 03:00:28 PM
Patrice,
  Outstanding size reduction.
It compiled and ran on my system with Visual Studio 2013 Express but I wanted to take it a step forward (backward :)) and compile from the command line.
I looked up all the command line options listed in VS and came up with this:

from a command prompt to set the paths and machine type:
"%VS120COMNTOOLS%..\..\VC\vcvarsall.bat" x86_amd64



compile:
cl -c dwmspinner.cpp /GL /W3 /Gy /Zc:wchar_t /Gm- /O1 /sdl-  /fp:precise /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /GF /WX- /Zc:forScope /GR- /Gz /Oy /Oi /MT /EHsc  /nologo /Os

compiler Notes:
/GL -> Whole Program Optimization
/W3 -> set warning level 3
/Gy -> Enable Function-Level Linking
/Zc:wchar_t -> a bit confusing to me
/Zc:forScope -> needed? it is the default
/Gm- -> needed ?
/O1 -> small code
/sdl -> Enables additional security features and warnings
/fp:precise -> needed? it is the default
/GF -> Eliminate Duplicate Strings
/WX- -> needed? witout "-" Treats all compiler warnings as errors
/GR- -> disables run-time type information
/Gz -> specifies the __stdcall calling convention
/Oy -> Suppresses creation of frame pointers on the call stack.
/Oi -> Generate Intrinsic Functions (may make app larger)
/MT -> Creates a multithreaded executable file using LIBCMT.lib.
/EHsc -> Exception Handling Model
/nologo -> Suppresses display of sign-on banner.
/Os -> Favors small code.




link:
LINK dwmspinner.obj /OUT:"DWMspinner.exe" /MANIFEST:NO "Dwmapi.lib" "libcpmt.lib" "msvcrt.lib" "kernel32.lib" "user32.lib" "gdi32.lib" "winspool.lib" "comdlg32.lib" "advapi32.lib" "shell32.lib" "ole32.lib" "oleaut32.lib" "uuid.lib" "odbc32.lib" "odbccp32.lib" /MACHINE:X64 /OPT:REF /INCREMENTAL:NO /SUBSYSTEM:WINDOWS /NOLOGO /NODEFAULTLIB


linker notes:
/MANIFEST:NO do not create a manifest
/LTCG is implied with /GL so is it needed?
/NXCOMPAT -> Compatible with Data Execution Prevention. by default it is on
/DYNAMICBASE -> By default, /DYNAMICBASE is on.
/OPT:ICF is on if /OPT:REF is on
/INCREMENTAL:NO not sure on it's use with above /OPT


Would you please address the needed? items
also how about /Oi. msdn says it may create larger exe.


James
Title: Re: Layered or DWM Composited Spinner
Post by: Frederick J. Harris on January 16, 2014, 06:58:25 PM
Regarding your question Jose about 'why not just us a C compiler', there are some advantages to mostly using C isms, but compiling as C++, i.e., with a *.cpp extension, as opposed to just a *.c extension.  Some of the stuff is almost silly, such as this ...

struct SomeStruct
{
  ....
};

To use that in C you would have to do this ..

struct SomeStruct ss;

Or, you would have to put the typedef keyword in front of the struct declaration to just be able to declare it like so ...

SomeStruct ss;

That's why you see the typedef specifier so much in headers; to help make declarations cleaner.  In C++ though one can just  be more natural in creating an instance like so ...

SomeStruct ss;

No need for typedefs.

More significant is the issue of passing references.  Strictly speaking, C doesn't have reference passing.  Parameters have to be all passed as pointers if one want the procedure to be able to modify the arguement.

And of course, C++ has classes.  I for one don't use that capability a lot, but I do use it.  In fact, the only way one can deal with strings in any major application is through a string class. 

So I think what actually happened among folks in the C world once C++ became popular, is that some folks just started compiling their C code as C++ and that mostly works with oftentimes only minor modifications.  Sometimes none at all in trivial instances.  I suppose that's how a C coder becomes an instant C++ coder! :)

Then I guess folks start teaching themselves the various parts of C++ such as classes and templates, and they either use them or not, depending on their personal inclinations. 

In my particular case I started in C a long time ago, and actually learned GUI SDK with C.  After that I learned C++ but essentially hated it when I saw how it was used to create class frameworks, which I disliked immensely.  I thought they were horrendous.  So I threw the whole works out and went back to C and later PowerBASIC.  So essentially, back in the early 2000s, I gave up C++.  I just figurred it was horrible and not worth using.

But I suffered tremendously writing everything I had to do in C with only the low level string buffer manipulation functions.  I worked with Windows CE a lot, and used C there because PowerBASIC as you know doesn't work on Windows CE.  Its really, really, really miserable writing major applications that involve substantial string manipulation in C.  So while I was suffering through a lot of this the thought kept nagging at me that if I used C++ and had a string class perhaps the going wouldn't be so hard.  But on the other hand, I hated C++!  And the thing was there that in VC6 times the string class that C++ had was part of MFC, and couldn't be seperated from it.  And at that time - just through my personal ignorance, I didn't know about the string class that was part of the C++ Standard Library.  So my choices seemed to me to be link with MFC to get a string class, and start with 1.5 mb "Hello, World!" programs, or suffer through C with no string handling.

I began to wonder if I hadn't "Thrown The Baby Out With The Bathwater", when I abandoned C++.  Sure, I hated bloated class frameworks that required massive runtimes to be attached to binaries, but I saw no inherent evil in the class keyword.  I saw no inherent evil in C++'s Byref parameter passing, and a number of other improvements to C. 

So it occurred to me that if I could create my own String Class to alleviate the miserableness of C's lack of string handling, I'd have solved the main issue I had with C.  Really, that was the only difficulty I had with the language of C - having to constantly copy bytes around in memory for the silliest things.

So essentially, I'm perfectly happy with the outcome I've now achieved.  I have a completely eclectic approach to the C++ language.  I use only what I want and ignore the rest.  Like you, programming is an enjoyable hobby for me, and I don't have anyone enforcing coding standards on me telling me, for example, that I have to use vectors instead of arrays.  I don't like vectors, I'm under no compunction to use them, so I ignore them.  Actually, I ignore most of what's considered modern C++.  And I feel no obligation to use a C++ ism over a C ism if I like the C ism better.  And I don't even care if the C++ ism is considered by most superior to the C ism.  I don't require total rationality in my choices, because, as I said, I do this for pleasure in many instances. 

So I believe eclectic fairly well describes my use of C++, in that I took from it what I wanted, and left the rest.  And basically what I took was the class keyword which allowed me to solve C's miserable string buffer handling, and that's about it.  Otherwise, I'm basically a C coder.  I really suspect though that I'm not alone.  In one exchange I had on a C++ forum one time a fellow told me that its a natural progression C coders go through in their slow adoption of C++.  He said that the advantages of C++ usually become clear after a time and C coders generally adopt most or all of C and just do things the C++ way.  But I suppose its possible to suffer from 'arrested development', and fail to move on to the next stage.  That's me. 

In my case though I've analyzed the situation back and forth so many times I just can't see what folks see in so much of the stuff I see in C++.  So, as I said, I don't use a lot of it.  While Patrice and I work in quite different areas, we are in total agreement on a lot of it.  He uses vectors, and by association templates, and I don't, but other than that I agree with all he said.  So basically, there aren't really any advantages to compiling in C as opposed to C++.  It isn't the .cpp extension that bloats code - its what gets compiled and linked in. 

Title: Re: Layered or DWM Composited Spinner
Post by: Patrice Terrier on January 16, 2014, 07:16:42 PM
James--

Here is my VS2013 compiler command line:
/GL /W3 /Gy /Zc:wchar_t /Zi /Gm- /O1 /sdl- /Fd"x64\Release\vc120.pdb" /fp:precise /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /GF /WX- /Zc:forScope /GR- /Gz /Oy /Oi /MT /Fa"x64\Release\" /EHsc /nologo /Fo"x64\Release\" /Os /Fp"x64\Release\DWMspinner.pch"

And the linker one:
/OUT:"D:\VS2013\DWMspinner\x64\Release\DWMspinner.exe" /MANIFEST:NO /LTCG /NXCOMPAT /PDB:"D:\VS2013\DWMspinner\x64\Release\DWMspinner.pdb" /DYNAMICBASE "Dwmapi.lib" "libcpmt.lib" "msvcrt.lib" "kernel32.lib" "user32.lib" "gdi32.lib" /MACHINE:X64 /OPT:REF /INCREMENTAL:NO /PGD:"D:\VS2013\DWMspinner\x64\Release\DWMspinner.pgd" /SUBSYSTEM:WINDOWS /MANIFESTUAC:"level='asInvoker' uiAccess='false'" /ManifestFile:"x64\Release\DWMspinner.exe.intermediate.manifest" /OPT:ICF /ERRORREPORT:PROMPT /NOLOGO /NODEFAULTLIB /TLBID:1
Title: Re: Layered or DWM Composited Spinner
Post by: James C. Fuller on January 17, 2014, 11:36:55 AM
Patrice,
  I know what your command lines are I retrieved then from the VS project and modified them to those shown.

I was looking more for your comments on my notes.

James
Title: Re: Layered or DWM Composited Spinner
Post by: Patrice Terrier on January 17, 2014, 07:00:26 PM
James--

QuoteWould you please address the needed? items
also how about /Oi. msdn says it may create larger exe.

I did check all the settings and as long as faster parameters do not produce larger EXE, i keep using them.
But this must be customized for each application.

For example in the case of DWMspinner the /Ox (full optimization) and /Ot (in favor of faster code) do not produce larger EXE, thus they could be kept.

...
Title: New Layered version (and the winner is...)
Post by: Patrice Terrier on January 18, 2014, 04:22:11 PM
The new optimized LAYERED versions are attached to this post.

And the new winner is C++ 64-bit with 10240 bytes, while PB is 11264 bytes.

Note: Starting with Windows 8, the WS_EX_LAYERED extended style can also be used with child Windows.

...
Title: Re: Layered or DWM Composited Spinner
Post by: James C. Fuller on January 19, 2014, 12:59:22 PM
Patrice,
  I noticed you changed to __cdecl from __stdcall .
Was this the only change you made for the current size reduction?

James
Title: Re: Layered or DWM Composited Spinner
Post by: James C. Fuller on January 19, 2014, 03:39:44 PM
Patrice,
  On closer examination of the source I would think getting the procedure address of the gdiplus functions  every time they are called would be very time consuming?

James
Title: Re: Layered or DWM Composited Spinner
Post by: Patrice Terrier on January 19, 2014, 08:38:52 PM
James--

Using either __cdecl or __stdcall wouldn't make any difference on the resulting size.

QuoteI would think getting the procedure address of the gdiplus functions  every time they are called would be very time consuming?
No, because using GetProcAddress, is always used under the hood by the languages specific encapsulations.
Using a global proc array or static variables to save the procedure addresses, would produce larger code and probably little speed gain. What could make a difference is the use of ordinal, but then you need to use a LIB file.

Dixit MSDN:
Calling GetProcAddress with an export ordinal, as opposed to the function name, is slightly faster if the DLL has many exported functions because the export ordinals serve as indexes into the DLL's export table. With an export ordinal, GetProcAddress can locate the function directly as opposed to comparing the specified name to the function names in the DLL's export table. However, you should call GetProcAddress with an export ordinal only if you have control over assigning the ordinals to the exported functions in the .DEF file.
Title: Static GetProcAdress
Post by: Patrice Terrier on January 20, 2014, 02:53:45 PM
James--

Here is the version using static zProc hProc; to call GetProcAddress only once per session

//+--------------------------------------------------------------------------+
//|                                                                          |
//|                               PNG spinner                                |
//|                                                                          |
//|                       layered spinner annimation                         |
//|                                                                          |
//+--------------------------------------------------------------------------+
//|                                                                          |
//|                         Author Patrice TERRIER                           |
//|                            copyright(c) 2014                             |
//|                           www.zapsolution.com                            |
//|                        pterrier@zapsolution.com                          |
//|                                                                          |
//+--------------------------------------------------------------------------+
//|                  Project started on : 01-05-2013 (MM-DD-YYYY)            |
//|                        Last revised : 01-13-2013 (MM-DD-YYYY)            |
//+--------------------------------------------------------------------------+

#include <windows.h>
#include <WinBase.h>
#include <Dwmapi.h>

#define TIMER_DELAY   30
#define TIMER_SPINNER -1

// GDIPLUS Flat API
#ifndef _GDIPLUS_H
#define _GDIPLUS_H
struct IDirectDrawSurface7;
typedef signed   short  INT16;
typedef unsigned short  UINT16;
#include <pshpack8.h>   // set structure packing to 8
namespace DllExports {
#include "GdiplusMem.h"
};
#include "GdiplusBase.h"
#include "GdiplusEnums.h"
#include "GdiplusTypes.h"
#include "GdiplusInit.h"
#include "GdiplusPixelFormats.h"
#include "GdiplusColor.h"
#include "GdiplusMetaHeader.h"
#include "GdiplusImaging.h"
#include "GdiplusGpStubs.h"
#include "GdiplusHeaders.h"
#endif // !_GDIPLUS_HPP

struct PROP {
    HWND      hwnd;
    LONG_PTR  img;
    long      framecount;
    long      frametouse;
    HDC       hdc;
    HBITMAP   hbitmap;
    HMODULE   gdiplib;
    LONG_PTR  hgdiplus;
};

PROP gP;

#define long_proc typedef long (__stdcall *zProc)

long Load_GDIPLUS() {
    static long nRet;
    if (nRet == 0) {
        gP.gdiplib = LoadLibrary(L"GDIPLUS");
        if (gP.gdiplib != 0) { nRet = -1; }
    }
    return nRet;
}

long GdiplusStart(OUT LONG_PTR &hGDIplus, GdiplusStartupInput &inputbuf, IN LONG_PTR outputbuf) {
    long nRet = -1; // Error
    if (gP.gdiplib) {
        long_proc(LONG_PTR*, GdiplusStartupInput*, LONG_PTR);
        zProc hPROC = (zProc)GetProcAddress(gP.gdiplib, "GdiplusStartup");
        if (hPROC) {
            nRet = hPROC(&hGDIplus, &inputbuf, outputbuf);
        }
    }
    return nRet;
}

long GdiplusShutdown(IN LONG_PTR hGDIplus) {
    long nRet = -1; // Error
    if (gP.gdiplib) {
        long_proc(LONG_PTR);
        zProc hProc = (zProc)GetProcAddress(gP.gdiplib, "GdiplusShutdown");
        if (hProc) { nRet = hProc(hGDIplus); }
    }
    return nRet;
}

long GdipLoadImageFromFile(IN WCHAR* szImgName, OUT LONG_PTR &lpImg) {
    long nRet = -1; // Error
    lpImg = 0;
    if (gP.gdiplib) {
        long_proc(WCHAR*, LONG_PTR*);
        static zProc hProc;
        if (hProc == 0) { hProc = (zProc)GetProcAddress(gP.gdiplib, "GdipLoadImageFromFile"); }
        if (hProc) { nRet = hProc(szImgName, &lpImg); }
    }
    return nRet;
}

long GdipDeleteGraphics(IN LONG_PTR graphics) {
    long nRet = -1; // Error
    if (gP.gdiplib) {
        long_proc(LONG_PTR);
        static zProc hProc;
        if (hProc == 0) { hProc = (zProc)GetProcAddress(gP.gdiplib, "GdipDeleteGraphics"); }
        if (hProc) { nRet = hProc(graphics); }
    }
    return nRet;
}

long GdipDrawImageRectRectI(IN LONG_PTR graphics, IN LONG_PTR lpImg, IN long dstX, IN long dstY, IN long dstW, IN long dstH, IN long srcX, IN long srcY, IN long srcW, IN long srcH, IN long srcUnit, IN LONG_PTR imageattr, IN LONG_PTR lpCallback, IN LONG_PTR callbackdata) {
    long nRet = -1; // Error
    if (gP.gdiplib) {
        long_proc(LONG_PTR, LONG_PTR, long, long, long, long, long, long, long, long, long, LONG_PTR, LONG_PTR, LONG_PTR);
        static zProc hProc;
        if (hProc == 0) { hProc = (zProc)GetProcAddress(gP.gdiplib, "GdipDrawImageRectRectI"); }
        if (hProc) { nRet = hProc(graphics, lpImg, dstX, dstY, dstW, dstH, srcX, srcY, srcW, srcH, srcUnit, imageattr, lpCallback, callbackdata); }
    }
    return nRet;
}

long GdipDisposeImage(IN LONG_PTR lpImg) {
    long nRet = -1; // Error
    if (gP.gdiplib) {
        long_proc(LONG_PTR);
        static zProc hProc;
        if (hProc == 0) { hProc = (zProc)GetProcAddress(gP.gdiplib, "GdipDisposeImage"); }
        if (hProc) { nRet = hProc(lpImg); }
    }
    return nRet;
}

long GdipGetImageWidth(IN LONG_PTR lpImg, OUT long &imgW) {
    long nRet = -1; // Error
    imgW = 0;
    if (gP.gdiplib) {
        long_proc(LONG_PTR, long*);
        static zProc hProc;
        if (hProc == 0) { hProc = (zProc)GetProcAddress(gP.gdiplib, "GdipGetImageWidth"); }
        if (hProc) { nRet = hProc(lpImg, &imgW); }
    }
    return nRet;
}

long GdipGetImageHeight(IN LONG_PTR lpImg, OUT long &imgH) {
    long nRet = -1; // Error
    imgH = 0;
    if (gP.gdiplib) {
        long_proc(LONG_PTR, long*);
        static zProc hProc;
        if (hProc == 0) { hProc = (zProc)GetProcAddress(gP.gdiplib, "GdipGetImageHeight"); }
        if (hProc) { nRet = hProc(lpImg, &imgH); }
    }
    return nRet;
}

long GdipCreateFromHDC(IN HDC hDC, OUT LONG_PTR &graphics) {
    long nRet = -1; // Error
    graphics = 0;
    if (gP.gdiplib) {
        long_proc(HDC, LONG_PTR*);
        static zProc hProc;
        if (hProc == 0) { hProc = (zProc)GetProcAddress(gP.gdiplib, "GdipCreateFromHDC"); }
        if (hProc) { nRet = hProc(hDC, &graphics); }
    }
    return nRet;
}

LONG_PTR GdipStart() {
    if (Load_GDIPLUS()) {
        // Load the GDI+ Dll
        GdiplusStartupInput GpInput;
        GpInput.GdiplusVersion = 1;
        LONG_PTR hGDIplus = 0;
        if (GdiplusStart(hGDIplus, GpInput, NULL) == 0) {
            gP.hgdiplus = hGDIplus;
        }
    }
    return gP.hgdiplus;
}

void GdipEnd(IN LONG_PTR hGDIplus) {
    if (hGDIplus) { GdiplusShutdown(hGDIplus); }
}

BOOL FileExist(IN WCHAR* szFileSpec) {
    WIN32_FIND_DATA fd = { 0 };
    BOOL bRet = FALSE;
    if (wcslen(szFileSpec)) {
        HANDLE hFind;
        hFind = FindFirstFile(szFileSpec, &fd);
        if (hFind != INVALID_HANDLE_VALUE) {
            FindClose(hFind);
            bRet = TRUE;
        }
    }
    return bRet;
}

HBITMAP CreateDIBSection32(IN HDC hDC, IN long nWidth, IN long nHeight) {
    BITMAPINFO bi = { 0 };
    bi.bmiHeader.biSize = sizeof(bi.bmiHeader);
    bi.bmiHeader.biWidth = nWidth;
    bi.bmiHeader.biHeight = -nHeight; // We want to use top left at coordinates 0,0.
    bi.bmiHeader.biPlanes = 1;
    bi.bmiHeader.biBitCount = 32;     // We use 32-bit (alpha channel).
    bi.bmiHeader.biCompression = BI_RGB;
    return CreateDIBSection(hDC, &bi, DIB_RGB_COLORS, NULL, NULL, NULL);
}

void Animate(IN LPVOID Delay) {
    LONG_PTR graphics = 0;
    RECT r = { 0 };
    SIZEL lpSize = { 0 };
    GetWindowRect(gP.hwnd, &r);
    lpSize.cx = r.right - r.left; lpSize.cy = r.bottom - r.top;
    SetRect(&r, 0, 0, lpSize.cx, lpSize.cy);

    if (gP.img == 0) { return; }

    RECT rw = { 0 };
    BLENDFUNCTION bf = { 0 };
    bf.BlendOp = AC_SRC_OVER;
    bf.BlendFlags = 0;
    bf.AlphaFormat = AC_SRC_ALPHA;
    bf.SourceConstantAlpha = 255;

    POINT lp = { 0 }, ptSrc = { 0 };

DoLoop:
    if (gP.hbitmap == 0) {
        HDC DesktopDC = GetDC(0);
        gP.hdc = CreateCompatibleDC(DesktopDC);
        gP.hbitmap = CreateDIBSection32(gP.hdc, lpSize.cx, lpSize.cy);
        SelectObject(gP.hdc, gP.hbitmap);
        ReleaseDC(0, DesktopDC);
    }
    if (gP.hbitmap) {
        // Create a GDI32 transparent brush, to clear the background of our permanent bitmap.
        HGDIOBJ hBrush = CreateSolidBrush(NULL);
        SelectObject(gP.hdc, hBrush);
        FillRect(gP.hdc, &r, (HBRUSH)hBrush);
        DeleteObject(hBrush);

        if (GdipCreateFromHDC(gP.hdc, graphics) == 0) {
            gP.frametouse += 1; if (gP.frametouse > gP.framecount) { gP.frametouse = 1; }
            if (gP.img) {
                GdipDrawImageRectRectI(graphics, gP.img, 0, 0, lpSize.cx, lpSize.cy, lpSize.cx * gP.frametouse - lpSize.cx, 0, lpSize.cx, lpSize.cy, 2, 0, NULL, NULL);
            }
            GdipDeleteGraphics(graphics);
        }

        GetWindowRect(gP.hwnd, &rw); lp.x = rw.left; lp.y = rw.top;
        UpdateLayeredWindow(gP.hwnd, 0, &lp, &lpSize, gP.hdc, &ptSrc, 0, &bf, ULW_ALPHA);

        Sleep((DWORD)Delay);
    }

    goto DoLoop; // Nothing faster than a good ASM jump.
}

long StartAnimation(IN long Delay) {
    long nRet = LB_ERR;
    DWORD dwThreadId = 0;
    HANDLE hThread = CreateThread(NULL,        // default security attributes
        0,                                     // use default stack size 
        (LPTHREAD_START_ROUTINE)Animate,       // thread function name
        (LPVOID)Delay,                         // argument to thread function
        0,                                     // use default creation flags
        &dwThreadId);                          // returns the thread identifier
    if (hThread) { nRet = 0; Sleep(100); }
    CloseHandle(hThread);
    return nRet;
}

void SpinnerInit(IN HWND hParent, IN HWND hWnd, IN WCHAR* szFileName, IN long nSpeedDelay) {

    if (IsWindow(hParent) == 0) { hParent = GetDesktopWindow(); }

    gP.framecount = 0;
    if (gP.img) { GdipDisposeImage(gP.img); gP.img = 0; }

    if (GdipLoadImageFromFile(szFileName, gP.img) == 0) {
        long w, h;
        RECT r = { 0 }; GetWindowRect(hParent, &r);
        GdipGetImageWidth(gP.img, w);
        GdipGetImageHeight(gP.img, h);
        gP.framecount = w / h;
        MoveWindow(hWnd, r.left + ((r.right - r.left - h) / 2), r.top + ((r.bottom - r.top - h) / 2), h, h, 0);

        // Compute animation speed, based on the number of frames.
        if (nSpeedDelay == 0) {
            if (gP.framecount < 4) {
                nSpeedDelay = 75;
            }
            else if (gP.framecount < 8) {
                nSpeedDelay = 50;
            }
            else if (gP.framecount < 12) {
                nSpeedDelay = 37;
            }
            else {
                nSpeedDelay = TIMER_DELAY;
            }
        }

        gP.hwnd = hWnd; StartAnimation(nSpeedDelay);

    }
}

LRESULT CALLBACK SpinnerProc(IN HWND hWnd, IN UINT uMsg, IN WPARAM wParam, IN LPARAM lParam) {
    switch (uMsg) {
    case WM_KEYDOWN:
        if (wParam == VK_ESCAPE) { DestroyWindow(hWnd); }
        break;

    case WM_NCHITTEST:
        return HTCAPTION;

    case WM_DESTROY:
        if (gP.hbitmap) { DeleteObject(gP.hbitmap); }
        if (gP.hdc) { DeleteDC(gP.hdc); }
        PostQuitMessage(0);
        if (gP.img) { GdipDisposeImage(gP.img); gP.img = 0; }
        return 0;
    }
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) {
    long nRet = 0;
    HWND hWnd = 0, hParent = 0;
    long nSpeedDelay = 0;
    MSG msg = { 0 };
    WNDCLASSEX wcx = { 0 };
    WCHAR szClassName[] = L"ZSPIN_LAYERED";
    WCHAR szFileName[MAX_PATH] = { 0 };

    wcx.cbSize = sizeof(wcx);
    long IsInitialized = GetClassInfoEx(hInstance, szClassName, &wcx);
    if (IsInitialized == 0) {
        wcx.style = CS_HREDRAW | CS_VREDRAW;
        wcx.lpfnWndProc = &SpinnerProc;
        wcx.cbClsExtra = 0;
        wcx.cbWndExtra = 0;
        wcx.hInstance = hInstance;
        wcx.hIcon = NULL;
        wcx.hCursor = LoadCursor(NULL, IDC_ARROW);
        wcx.hbrBackground = NULL;
        wcx.lpszMenuName = NULL;
        wcx.lpszClassName = szClassName;
        wcx.hIconSm = wcx.hIcon;
        if (RegisterClassEx(&wcx)) { IsInitialized = -1; }
    }

    if (IsInitialized) {
        hWnd = CreateWindowEx(WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_LAYERED, szClassName, szClassName, WS_POPUP | WS_VISIBLE,
            0, 0, 0, 0, NULL, (HMENU)NULL, hInstance, NULL);
        if (hWnd) {

            LONG_PTR hGDIplus = 0;
            RtlMoveMemory(&szFileName, lpCmdLine, sizeof(szFileName));
            if (FileExist(szFileName)) {
                hGDIplus = GdipStart(); if (hGDIplus) { SpinnerInit(hParent, hWnd, szFileName, nSpeedDelay); }
            }
            if (hGDIplus == 0) { DestroyWindow(hWnd); }

            UpdateWindow(hWnd);

            while (GetMessage(&msg, NULL, 0, 0)) {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
            if (hGDIplus) { GdipEnd(hGDIplus); }
            nRet = (long)msg.wParam;
        }
    }
    return nRet;
}


Note: That doesn't increase the size of the resulting EXE.
Title: Re: Layered or DWM Composited Spinner
Post by: James C. Fuller on January 22, 2014, 04:22:08 PM
Patrice,
  Now with the static added it is worthy of a bc9Basic translation :)
Note, I have never used single line If statements. I find it much easier to read source without them.
Nor do I ever put more than one statement on a line.
I removed a couple items:

typedef signed   short  INT16;
typedef unsigned short  UINT16;
#include <pshpack8.h>   // set structure packing to 8

the first 2 were not needed and the third I believe is the default.
the bc9Basic Do/Loop translates to an empty for block so no goto.
That's not to say bc9Basic doesn't use goto's. It does for SELECT/CASE.
I left the FileExist as is in a bc9 $CCODE block

James


Bc9Basic code:

'=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
'Patrices pngspinner(static) -> bc9Basic
'=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
$CPPHDR
$NOMAIN
$HEADER
    #include <windows.h>
    #include <WinBase.h>
    #include <Dwmapi.h>
    #define TIMER_DELAY   30
    #define TIMER_SPINNER -1
    // GDIPLUS Flat API
    #ifndef _GDIPLUS_H
        #define _GDIPLUS_H
        struct IDirectDrawSurface7;
        namespace DllExports {
        #include "GdiplusMem.h"
        };
        #include "GdiplusBase.h"
        #include "GdiplusEnums.h"
        #include "GdiplusTypes.h"
        #include "GdiplusInit.h"
        #include "GdiplusPixelFormats.h"
        #include "GdiplusColor.h"
        #include "GdiplusMetaHeader.h"
        #include "GdiplusImaging.h"
        #include "GdiplusGpStubs.h"
        #include "GdiplusHeaders.h"
    #endif // !_GDIPLUS_HPP
   
    #define long_proc typedef long (__stdcall *zProc)   
$HEADER

$ONEXIT "jcfpng.BAT $FILE$"

TYPE PROP
    hwnd         As HWND
    img            AS LONG_PTR
    framecount     AS long
    frametouse    AS long
    hdc            AS HDC
    hbitmap        AS HBITMAP
    gdiplib        AS HMODULE
    hgdiplus    AS LONG_PTR
End TYPE

RAW As PROP gP
'==============================================================================
Function Load_GDIPLUS() As long
    static nRet As long
    If nRet = 0 Then
        gP.gdiplib = LoadLibrary(L"GDIPLUS")
        If gP.gdiplib <> 0 Then
            nRet = -1
        End If
    End If
    Function = nRet
End Function
'==============================================================================
Function GdiplusStart(&hGDIplus As LONG_PTR ,&inputbuf As GdiplusStartupInput , outputbuf As LONG_PTR) As long
    Raw As long nRet = -1
    If gP.gdiplib Then
        long_proc(LONG_PTR*, GdiplusStartupInput*, LONG_PTR)
        Raw As zProc hProc = (zProc)GetProcAddress(gP.gdiplib, "GdiplusStartup")
        If hProc Then
            nRet = hProc(&hGDIplus, &inputbuf, outputbuf)
        End If
    End If
    Function = nRet
End Function
'==============================================================================
Function GdiplusShutdown(hGDIplus As LONG_PTR) As long
    Raw As long nRet = -1
    If gP.gdiplib Then
        long_proc(LONG_PTR)
        Raw As zProc hProc = (zProc)GetProcAddress(gP.gdiplib, "GdiplusShutdown")
        If hProc Then
            nRet = hProc(hGDIplus)
        End If
    End If
    Function = nRet
End Function
'==============================================================================
Function GdipLoadImageFromFile(szImgName As WCHAR Ptr, &lpImg As LONG_PTR ) As long
    Raw As long nRet = -1
    lpImg = 0
    If gP.gdiplib Then
        long_proc(WCHAR*, LONG_PTR*)
        static As zProc hProc
        If hProc = 0 Then
            hProc = (zProc)GetProcAddress(gP.gdiplib, "GdipLoadImageFromFile")
        End If
        If hProc Then
            nRet = hProc(szImgName, &lpImg)
        End If
    End If
    Function = nRet
End Function
'==============================================================================
Function GdipDeleteGraphics(graphics As LONG_PTR) As long
    Raw As long nRet = -1
    If gP.gdiplib Then
        long_proc(LONG_PTR)
        static As zProc hProc
        If hProc = 0 Then
            hProc = (zProc)GetProcAddress(gP.gdiplib, "GdipDeleteGraphics")
        End If
        If hProc Then
            nRet = hProc(graphics)
        End If
    End If
    Function = nRet
End Function
'==============================================================================
Function GdipDrawImageRectRectI(graphics As LONG_PTR ,lpImg As LONG_PTR ,dstX As long ,dstY As long ,dstW As long ,dstH As long ,srcX As long ,srcY As long ,srcW As long ,srcH As long ,srcUnit As long ,imageattr As LONG_PTR,lpCallback As LONG_PTR, callbackdata As LONG_PTR) As long
    Raw As long nRet = -1
    If gP.gdiplib Then
        long_proc(LONG_PTR, LONG_PTR, long, long, long, long, long, long, long, long, long, LONG_PTR, LONG_PTR, LONG_PTR)
        static As zProc hProc
        If hProc = 0 Then
            hProc = (zProc)GetProcAddress(gP.gdiplib, "GdipDrawImageRectRectI")
        End If
        If hProc Then
            nRet = hProc(graphics, lpImg, dstX, dstY, dstW, dstH, srcX, srcY, srcW, srcH, srcUnit, imageattr, lpCallback, callbackdata)
        End If
    End If
    Function = nRet
End Function
'==============================================================================
Function GdipDisposeImage(lpImg As LONG_PTR) As long
    Raw As long nRet = -1
    If gP.gdiplib Then
        long_proc(LONG_PTR)
        static As zProc hProc
        If hProc = 0 Then
            hProc = (zProc)GetProcAddress(gP.gdiplib, "GdipDisposeImage")
        End If
        If hProc Then
            nRet = hProc(lpImg)
        End If
    End If
    Function = nRet
End Function
'==============================================================================
Function GdipGetImageWidth(lpImg As LONG_PTR ,&imgW As long ) As long
    Raw As long nRet = -1
    imgW = 0
    If gP.gdiplib Then
        long_proc(LONG_PTR, long*)
        static As zProc hProc
        If hProc = 0 Then
            hProc = (zProc)GetProcAddress(gP.gdiplib, "GdipGetImageWidth")
        End If
        If hProc Then
            nRet = hProc(lpImg, &imgW)
        End If
    End If
    Function = nRet   
End Function
'==============================================================================
Function GdipGetImageHeight(lpImg As LONG_PTR ,&imgH As long ) As long
    Raw As long nRet = -1
    imgH = 0
    If gP.gdiplib Then
        long_proc(LONG_PTR, long*)
        static As zProc hProc
        If hProc = 0 Then
            hProc = (zProc)GetProcAddress(gP.gdiplib, "GdipGetImageHeight")
            If hProc Then
                nRet = hProc(lpImg, &imgH)
            End If
        End If
    End If
    Function = nRet   
End Function
'==============================================================================
Function GdipCreateFromHDC(hDC As HDC, &graphics As LONG_PTR ) As long
    Raw As long nRet = -1
    graphics = 0
    If gP.gdiplib Then
        long_proc(HDC, LONG_PTR*)
        static As zProc hProc
        If hProc = 0 Then
            hProc = (zProc)GetProcAddress(gP.gdiplib, "GdipCreateFromHDC")
        End If
        If hProc Then
            nRet = hProc(hDC, &graphics)
        End If
    End If
    Function = nRet
End Function
'==============================================================================
Function GdipStart() As LONG_PTR
    If Load_GDIPLUS() Then
        Raw As GdiplusStartupInput GpInput
        GpInput.GdiplusVersion = 1
        Raw As LONG_PTR hGDIplus = 0
        If GdiplusStart(hGDIplus, GpInput, NULL) = 0 Then
            gP.hgdiplus = hGDIplus
        End If
    End If
    Function = gP.hgdiplus
End Function
'==============================================================================
Sub GdipEnd(hGDIplus As LONG_PTR )
    If hGDIplus Then
        GdiplusShutdown(hGDIplus)
    End If
End Sub
'==============================================================================
$CCODE funcsub
BOOL FileExist(IN WCHAR* szFileSpec) {
    WIN32_FIND_DATA fd = { 0 };
    BOOL bRet = FALSE;
    if (wcslen(szFileSpec)) {
        HANDLE hFind;
        hFind = FindFirstFile(szFileSpec, &fd);
        if (hFind != INVALID_HANDLE_VALUE) {
            FindClose(hFind);
            bRet = TRUE;
        }
    }
    return bRet;
}
$CCODE
'==============================================================================
Function CreateDIBSection32(hDC As HDC, nWidth As long, nHeight As long) As HBITMAP
    Raw As BITMAPINFO bi = {0}
    bi.bmiHeader.biSize = sizeof(bi.bmiHeader)
    bi.bmiHeader.biWidth = nWidth
    bi.bmiHeader.biHeight = -nHeight ' We want to use top left at coordinates 0,0.
    bi.bmiHeader.biPlanes = 1
    bi.bmiHeader.biBitCount = 32     ' We use 32-bit (alpha channel).
    bi.bmiHeader.biCompression = BI_RGB
    Function = CreateDIBSection(hDC, &bi, DIB_RGB_COLORS, NULL, NULL, NULL)
End Function
'==============================================================================
Sub Animate(Delay As LPVOID)
    Raw As LONG_PTR graphics = 0
    Raw As RECT r = {0}
    Raw As SIZEL lpSize = { 0 }
   
    GetWindowRect(gP.hwnd, &r)
    lpSize.cx = r.right - r.left
    lpSize.cy = r.bottom - r.top
    SetRect(&r, 0, 0, lpSize.cx, lpSize.cy)
    If gP.img = 0 Then
        Exit Sub
    End If
    Raw As RECT rw = {0}
    Raw As BLENDFUNCTION bf = {0}
    bf.BlendOp = AC_SRC_OVER
    bf.BlendFlags = 0
    bf.AlphaFormat = AC_SRC_ALPHA
    bf.SourceConstantAlpha = 255

    Raw As POINT lp = {0}, ptSrc = {0}
    Do
        If gP.hbitmap = 0 Then
            Raw As HDC DesktopDC = GetDC(0)
            gP.hdc = CreateCompatibleDC(DesktopDC)
            gP.hbitmap = CreateDIBSection32(gP.hdc, lpSize.cx, lpSize.cy)
            SelectObject(gP.hdc, gP.hbitmap)
            ReleaseDC(0, DesktopDC)
        End If
       
        If gP.hbitmap Then
            ' Create a GDI32 transparent brush, to clear the background of our permanent bitmap.
            Raw As HGDIOBJ hBrush = CreateSolidBrush(NULL)
            SelectObject(gP.hdc, hBrush)
            FillRect(gP.hdc, &r, (HBRUSH)hBrush)
            DeleteObject(hBrush)
            If GdipCreateFromHDC(gP.hdc, graphics) = 0 Then
                gP.frametouse += 1
                If gP.frametouse > gP.framecount Then
                     gP.frametouse = 1   
                End If
                If gP.img Then
                    GdipDrawImageRectRectI(graphics, gP.img, 0, 0, lpSize.cx, lpSize.cy, lpSize.cx * gP.frametouse - lpSize.cx, 0, lpSize.cx, lpSize.cy, 2, 0, NULL, NULL)
                End If
                GdipDeleteGraphics(graphics)
            End If
            GetWindowRect(gP.hwnd, &rw)
            lp.x = rw.left
            lp.y = rw.top
            UpdateLayeredWindow(gP.hwnd, 0, &lp, &lpSize, gP.hdc, &ptSrc, 0, &bf, ULW_ALPHA)
            Sleep((DWORD)Delay)
        End If
                 
    Loop
End Sub
'==============================================================================
Function StartAnimation(Delay As long) As long
    Raw As long nRet = LB_ERR
    Raw As DWORD dwThreadId = 0
    Raw As HANDLE hThread = CreateThread(NULL, _        ' default security attributes
        0, _                                               ' use default stack size 
        (LPTHREAD_START_ROUTINE)Animate, _                ' thread function name
        (LPVOID)Delay, _                                   ' argument to thread function
        0, _                                               ' use default creation flags
        &dwThreadId)                                       ' returns the thread identifier
    If hThread Then
         nRet = 0
         Sleep(100)
    End If
    CloseHandle(hThread)
    Function = nRet
End Function
'==============================================================================
Sub SpinnerInit(hParent As HWND,hWnd As HWND, szFileName As WCHAR Ptr,nSpeedDelay As long)
    If IsWindow(hParent) = 0 Then
        hParent = GetDesktopWindow()
    End If
   
    gP.framecount = 0
    If gP.img Then
        GdipDisposeImage(gP.img)
        gP.img = 0
    End If
    If GdipLoadImageFromFile(szFileName, gP.img) = 0 Then
        Raw As long w,h
        Raw As RECT r = {0}
        GetWindowRect(hParent, &r)
        GdipGetImageWidth(gP.img, w)
        GdipGetImageHeight(gP.img, h)
        gP.framecount = w / h
        MoveWindow(hWnd, r.left + ((r.right - r.left - h) / 2), r.top + ((r.bottom - r.top - h) / 2), h, h, 0)

        ' Compute animation speed, based on the number of frames.
        If nSpeedDelay = 0 Then
            If gP.framecount < 4 Then
                nSpeedDelay = 75
            ElseIf gP.framecount < 8 Then
                nSpeedDelay = 50
            ElseIf gP.framecount < 12 Then
                nSpeedDelay = 37
            Else
                nSpeedDelay = TIMER_DELAY   
            End If
        End If
        gP.hwnd = hWnd
        StartAnimation(nSpeedDelay)       
    End If
End Sub
'==============================================================================
CALLBACK Function SpinnerProc()
   
    Select Case CBMSG
        Case WM_KEYDOWN
            If CBWPARAM = VK_ESCAPE Then
                DestroyWindow(CBHWND)
            End If   
        Case WM_NCHITTEST     
            Function = HTCAPTION
        Case WM_DESTROY
            If gP.hbitmap Then
                DeleteObject(gP.hbitmap)
            End If   
            If gP.hdc Then
                DeleteDC(gP.hdc)
            End If
            PostQuitMessage(0)
            If gP.img Then
                GdipDisposeImage(gP.img)
                gP.img = 0
            End If
            Function = 0
           
    End Select
End Function
'==============================================================================


Function wWinMain(hInst As HINSTANCE,hPrev As HINSTANCE,CmdLine As LPTSTR, CmdShow As int)
    'MessageBox(0,L"Hello",L"Caption",MB_OK)
    Dim As long nRet,nSpeedDelay
    Dim As HWND hWnd, hParent
    Dim As MSG msg
    Dim As WNDCLASSEX wcx
    Raw As WCHAR szClassName[]= L"ZSPIN_LAYERED"
    Dim As WCHAR szFileName[MAX_PATH]
    wcx.cbSize = sizeof(wcx)
    Raw As long IsInitialized = GetClassInfoEx(hInst, szClassName, &wcx)
   
    If IsInitialized = 0 Then
        wcx.style = CS_HREDRAW | CS_VREDRAW
        wcx.lpfnWndProc = &SpinnerProc
        wcx.cbClsExtra = 0
        wcx.cbWndExtra = 0
        wcx.hInstance = hInst
        wcx.hIcon = NULL
        wcx.hCursor = LoadCursor(NULL, IDC_ARROW)
        wcx.hbrBackground = NULL
        wcx.lpszMenuName = NULL
        wcx.lpszClassName = szClassName
        wcx.hIconSm = wcx.hIcon
        If RegisterClassEx(&wcx) Then
            IsInitialized = -1
        End If
    End If
    If IsInitialized Then
        hWnd = CreateWindowEx(WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_LAYERED, szClassName, szClassName, WS_POPUP | WS_VISIBLE, _
            0, 0, 0, 0, NULL, (HMENU)NULL, hInst, NULL)
        If hWnd Then
            Raw As LONG_PTR hGDIplus = 0
            RtlMoveMemory(&szFileName, CmdLine, sizeof(szFileName))
            If FileExist(szFileName) Then
                hGDIplus = GdipStart()
                If hGDIplus Then
                    SpinnerInit(hParent, hWnd, szFileName, nSpeedDelay)
                End If
            End If   
            If hGDIplus = 0 Then
                DestroyWindow(hWnd)
            End If
           
            UpdateWindow(hWnd)   
           
            While GetMessage(&msg, NULL, 0, 0)
                TranslateMessage(&msg)
                DispatchMessage(&msg)
            Wend
            If hGDIplus Then
                GdipEnd(hGDIplus)
            End If
            nRet = (long)msg.wParam
        End If
    End If
   
    Function = nRet
   
End Function



C++ translation:


// *********************************************************************
//  Created with bc9Basic - BASIC To C/C++ Translator (V) 9.1.8.5 (2014/01/18)
//       The bc9Basic translator (bc9.exe) was compiled with
//               g++ (rev0, Built by MinGW-W64 project) 4.8.2
// ----------------------------------------------------------------------
//                 BCX (c) 1999 - 2009 by Kevin Diggins
// *********************************************************************
//              Translated for compiling with a C++ Compiler
//                           On MS Windows
// *********************************************************************

// ***************************************************
// $HEADER
// ***************************************************

#include <windows.h>
#include <WinBase.h>
#include <Dwmapi.h>
#define TIMER_DELAY   30
#define TIMER_SPINNER -1
// GDIPLUS Flat API
#ifndef _GDIPLUS_H
#define _GDIPLUS_H
struct IDirectDrawSurface7;
namespace DllExports {
#include "GdiplusMem.h"
};
#include "GdiplusBase.h"
#include "GdiplusEnums.h"
#include "GdiplusTypes.h"
#include "GdiplusInit.h"
#include "GdiplusPixelFormats.h"
#include "GdiplusColor.h"
#include "GdiplusMetaHeader.h"
#include "GdiplusImaging.h"
#include "GdiplusGpStubs.h"
#include "GdiplusHeaders.h"
#endif // !_GDIPLUS_HPP

#define long_proc typedef long (__stdcall *zProc)


// *************************************************
//        User's GLOBAL ENUM blocks
// *************************************************

// *************************************************
//            System Defined Constants
// *************************************************

typedef const char* ccptr;
#define CCPTR const char*
#define cfree free
#define WAITKEY system("pause")
#define cSizeOfDefaultString 2048

// *************************************************
//            User Defined Constants
// *************************************************

#define PROP_CLASS struct _PROP*
// *************************************************
//          User Defined Types And Unions
// *************************************************


typedef struct _PROP
{
    HWND     hwnd;
    LONG_PTR  img;
    long     framecount;
    long     frametouse;
    HDC      hdc;
    HBITMAP  hbitmap;
    HMODULE  gdiplib;
    LONG_PTR  hgdiplus;
} PROP, *LPPROP;


// *************************************************
//            User Global Variables
// *************************************************

static PCHAR   *g_argv;
static int     g_argc;
PROP    gP;


// *************************************************
//               User Prototypes
// *************************************************

long    Load_GDIPLUS (void);
long    GdiplusStart (LONG_PTR &, GdiplusStartupInput &, LONG_PTR);
long    GdiplusShutdown (LONG_PTR);
long    GdipLoadImageFromFile (WCHAR* , LONG_PTR &);
long    GdipDeleteGraphics (LONG_PTR);
long    GdipDrawImageRectRectI (LONG_PTR, LONG_PTR, long, long, long, long, long, long, long, long, long, LONG_PTR, LONG_PTR, LONG_PTR);
long    GdipDisposeImage (LONG_PTR);
long    GdipGetImageWidth (LONG_PTR, long &);
long    GdipGetImageHeight (LONG_PTR, long &);
long    GdipCreateFromHDC (HDC, LONG_PTR &);
LONG_PTR GdipStart (void);
void    GdipEnd (LONG_PTR);
HBITMAP CreateDIBSection32 (HDC, long, long);
void    Animate (LPVOID);
long    StartAnimation (long);
void    SpinnerInit (HWND, HWND, WCHAR* , long);
LRESULT CALLBACK SpinnerProc (HWND, UINT, WPARAM, LPARAM);
int     wWinMain (HINSTANCE, HINSTANCE, LPTSTR, int);

// *************************************************
//            User Global Initialized Arrays
// *************************************************



// *************************************************
//                 Runtime Functions
// *************************************************


// ************************************
//       User C code
// ************************************

BOOL FileExist(IN WCHAR* szFileSpec) {
    WIN32_FIND_DATA fd = { 0 };
    BOOL bRet = FALSE;
    if (wcslen(szFileSpec)) {
        HANDLE hFind;
        hFind = FindFirstFile(szFileSpec, &fd);
        if (hFind != INVALID_HANDLE_VALUE) {
            FindClose(hFind);
            bRet = TRUE;
        }
    }
    return bRet;
}

// ************************************
//       User Subs and Functions
// ************************************

long Load_GDIPLUS ()
{
    static long     nRet;
    if(nRet == 0 )
    {
        gP.gdiplib = LoadLibrary( L"GDIPLUS");
        if(gP.gdiplib != 0 )
        {
            nRet = - 1;
        }
    }
    return nRet;
}


long GdiplusStart (LONG_PTR   &hGDIplus, GdiplusStartupInput   &inputbuf, LONG_PTR  outputbuf)
{
    long     nRet = -1;
    if(gP.gdiplib )
    {
        long_proc(LONG_PTR*, GdiplusStartupInput*, LONG_PTR);
        zProc    hProc = (zProc)GetProcAddress(gP.gdiplib, "GdiplusStartup");
        if(hProc )
        {
            nRet = hProc(  &hGDIplus,  &inputbuf, outputbuf);
        }
    }
    return nRet;
}


long GdiplusShutdown (LONG_PTR  hGDIplus)
{
    long     nRet = -1;
    if(gP.gdiplib )
    {
        long_proc(LONG_PTR);
        zProc    hProc = (zProc)GetProcAddress(gP.gdiplib, "GdiplusShutdown");
        if(hProc )
        {
            nRet = hProc( hGDIplus);
        }
    }
    return nRet;
}


long GdipLoadImageFromFile (WCHAR*  szImgName, LONG_PTR   &lpImg)
{
    long     nRet = -1;
    lpImg = 0;
    if(gP.gdiplib )
    {
        long_proc(WCHAR*, LONG_PTR*);
        static zProc    hProc;
        if(hProc == 0 )
        {
            hProc = ( zProc) GetProcAddress( gP.gdiplib, "GdipLoadImageFromFile");
        }
        if(hProc )
        {
            nRet = hProc( szImgName,  &lpImg);
        }
    }
    return nRet;
}


long GdipDeleteGraphics (LONG_PTR  graphics)
{
    long     nRet = -1;
    if(gP.gdiplib )
    {
        long_proc(LONG_PTR);
        static zProc    hProc;
        if(hProc == 0 )
        {
            hProc = ( zProc) GetProcAddress( gP.gdiplib, "GdipDeleteGraphics");
        }
        if(hProc )
        {
            nRet = hProc( graphics);
        }
    }
    return nRet;
}


long GdipDrawImageRectRectI (LONG_PTR  graphics, LONG_PTR  lpImg, long dstX, long dstY, long dstW, long dstH, long srcX, long srcY, long srcW, long srcH, long srcUnit, LONG_PTR  imageattr, LONG_PTR  lpCallback, LONG_PTR  callbackdata)
{
    long     nRet = -1;
    if(gP.gdiplib )
    {
        long_proc(LONG_PTR, LONG_PTR, long, long, long, long, long, long, long, long, long, LONG_PTR, LONG_PTR, LONG_PTR);
        static zProc    hProc;
        if(hProc == 0 )
        {
            hProc = ( zProc) GetProcAddress( gP.gdiplib, "GdipDrawImageRectRectI");
        }
        if(hProc )
        {
            nRet = hProc( graphics, lpImg, dstX, dstY, dstW, dstH, srcX, srcY, srcW, srcH, srcUnit, imageattr, lpCallback, callbackdata);
        }
    }
    return nRet;
}


long GdipDisposeImage (LONG_PTR  lpImg)
{
    long     nRet = -1;
    if(gP.gdiplib )
    {
        long_proc(LONG_PTR);
        static zProc    hProc;
        if(hProc == 0 )
        {
            hProc = ( zProc) GetProcAddress( gP.gdiplib, "GdipDisposeImage");
        }
        if(hProc )
        {
            nRet = hProc( lpImg);
        }
    }
    return nRet;
}


long GdipGetImageWidth (LONG_PTR  lpImg, long  &imgW)
{
    long     nRet = -1;
    imgW = 0;
    if(gP.gdiplib )
    {
        long_proc(LONG_PTR, long*);
        static zProc    hProc;
        if(hProc == 0 )
        {
            hProc = ( zProc) GetProcAddress( gP.gdiplib, "GdipGetImageWidth");
        }
        if(hProc )
        {
            nRet = hProc( lpImg,  &imgW);
        }
    }
    return nRet;
}


long GdipGetImageHeight (LONG_PTR  lpImg, long  &imgH)
{
    long     nRet = -1;
    imgH = 0;
    if(gP.gdiplib )
    {
        long_proc(LONG_PTR, long*);
        static zProc    hProc;
        if(hProc == 0 )
        {
            hProc = ( zProc) GetProcAddress( gP.gdiplib, "GdipGetImageHeight");
            if(hProc )
            {
                nRet = hProc( lpImg,  &imgH);
            }
        }
    }
    return nRet;
}


long GdipCreateFromHDC (HDC hDC, LONG_PTR   &graphics)
{
    long     nRet = -1;
    graphics = 0;
    if(gP.gdiplib )
    {
        long_proc(HDC, LONG_PTR*);
        static zProc    hProc;
        if(hProc == 0 )
        {
            hProc = ( zProc) GetProcAddress( gP.gdiplib, "GdipCreateFromHDC");
        }
        if(hProc )
        {
            nRet = hProc( hDC,  &graphics);
        }
    }
    return nRet;
}


LONG_PTR GdipStart ()
{
    if(Load_GDIPLUS())
    {
        GdiplusStartupInput  GpInput;
        GpInput.GdiplusVersion = 1;
        LONG_PTR  hGDIplus = 0;
        if(GdiplusStart(hGDIplus, GpInput, NULL) == 0 )
        {
            gP.hgdiplus = hGDIplus;
        }
    }
    return gP.hgdiplus;
}


void GdipEnd (LONG_PTR  hGDIplus)
{
    if(hGDIplus )
    {
        GdiplusShutdown(hGDIplus);
    }
}


HBITMAP CreateDIBSection32 (HDC hDC, long nWidth, long nHeight)
{
    BITMAPINFO  bi = {0};
    bi.bmiHeader.biSize = sizeof( bi.bmiHeader);
    bi.bmiHeader.biWidth = nWidth;
    bi.bmiHeader.biHeight = - nHeight;
    bi.bmiHeader.biPlanes = 1;
    bi.bmiHeader.biBitCount = 32;
    bi.bmiHeader.biCompression = BI_RGB;
    return CreateDIBSection(hDC, &bi, DIB_RGB_COLORS, NULL, NULL, NULL);
}


void Animate (LPVOID  delay)
{
    LONG_PTR  graphics = 0;
    RECT     r = {0};
    SIZEL    lpSize = {0};
    GetWindowRect(gP.hwnd, &r);
    lpSize.cx = r.right - r.left;
    lpSize.cy = r.bottom - r.top;
    SetRect( &r, 0, 0, lpSize.cx, lpSize.cy);
    if(gP.img == 0 )
    {
        return;
    }
    RECT     rw = {0};
    BLENDFUNCTION  bf = {0};
    bf.BlendOp = AC_SRC_OVER;
    bf.BlendFlags = 0;
    bf.AlphaFormat = AC_SRC_ALPHA;
    bf.SourceConstantAlpha = 255;
    POINT    lp = {0};
    POINT    ptSrc = {0};
    for(;;)
    {
        if(gP.hbitmap == 0 )
        {
            HDC      DesktopDC = GetDC(0);
            gP.hdc = CreateCompatibleDC( DesktopDC);
            gP.hbitmap = CreateDIBSection32( gP.hdc, lpSize.cx, lpSize.cy);
            SelectObject(gP.hdc, gP.hbitmap);
            ReleaseDC(0, DesktopDC);
        }
        if(gP.hbitmap )
        {
            HGDIOBJ  hBrush = CreateSolidBrush(NULL);
            SelectObject(gP.hdc, hBrush);
            FillRect(gP.hdc, &r, (HBRUSH)hBrush);
            DeleteObject(hBrush);
            if(GdipCreateFromHDC(gP.hdc, graphics) == 0 )
            {
                gP.frametouse += 1;
                if(gP.frametouse > gP.framecount )
                {
                    gP.frametouse = 1;
                }
                if(gP.img )
                {
                    GdipDrawImageRectRectI(graphics, gP.img, 0, 0, lpSize.cx, lpSize.cy, lpSize.cx * gP.frametouse - lpSize.cx, 0, lpSize.cx, lpSize.cy, 2, 0, NULL, NULL);
                }
                GdipDeleteGraphics(graphics);
            }
            GetWindowRect(gP.hwnd, &rw);
            lp.x = rw.left;
            lp.y = rw.top;
            UpdateLayeredWindow(gP.hwnd, 0, &lp, &lpSize, gP.hdc, &ptSrc, 0, &bf, ULW_ALPHA);
            Sleep((DWORD)delay);
        }
    }

}


long StartAnimation (long delay)
{
    long     nRet = LB_ERR;
    DWORD    dwThreadId = 0;
    HANDLE   hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Animate, (LPVOID)delay, 0, &dwThreadId);
    if(hThread )
    {
        nRet = 0;
        Sleep(100);
    }
    CloseHandle(hThread);
    return nRet;
}


void SpinnerInit (HWND hParent, HWND hWnd, WCHAR*  szFileName, long nSpeedDelay)
{
    if(IsWindow(hParent) == 0 )
    {
        hParent = GetDesktopWindow();
    }
    gP.framecount = 0;
    if(gP.img )
    {
        GdipDisposeImage(gP.img);
        gP.img = 0;
    }
    if(GdipLoadImageFromFile(szFileName, gP.img) == 0 )
    {
        long     w;
        long     h;
        RECT     r = {0};
        GetWindowRect(hParent, &r);
        GdipGetImageWidth(gP.img, w);
        GdipGetImageHeight(gP.img, h);
        gP.framecount = w / h;
        MoveWindow(hWnd, r.left + ((r.right - r.left - h) / 2), r.top + ((r.bottom - r.top - h) / 2), h, h, 0);
        if(nSpeedDelay == 0 )
        {
            if(gP.framecount < 4 )
            {
                nSpeedDelay = 75;
            }
            else if(gP.framecount < 8 )
            {
                nSpeedDelay = 50;
            }
            else if(gP.framecount < 12 )
            {
                nSpeedDelay = 37;
            }
            else
            {
                nSpeedDelay = TIMER_DELAY;
            }
        }
        gP.hwnd = hWnd;
        StartAnimation(nSpeedDelay);
    }
}


LRESULT CALLBACK SpinnerProc (HWND hWnd, UINT Msg, WPARAM  wParam, LPARAM  lParam)
{
    if(Msg == WM_KEYDOWN )
    {
        if(wParam == VK_ESCAPE )
        {
            DestroyWindow(hWnd);
        }
        goto L1001;
    }
    if(Msg == WM_NCHITTEST )
    {
        return HTCAPTION;
    }
    if(Msg == WM_DESTROY )
    {
        if(gP.hbitmap )
        {
            DeleteObject(gP.hbitmap);
        }
        if(gP.hdc )
        {
            DeleteDC(gP.hdc);
        }
        PostQuitMessage(0);
        if(gP.img )
        {
            GdipDisposeImage(gP.img);
            gP.img = 0;
        }
        return 0;
    }
L1001:
    ;
    return DefWindowProc(hWnd, Msg, wParam, lParam);
}


int wWinMain (HINSTANCE  hInst, HINSTANCE  hPrev, LPTSTR  CmdLine, int CmdShow)
{
    long     nRet = {0};
    long     nSpeedDelay = {0};
    HWND     hWnd = {0};
    HWND     hParent = {0};
    MSG      msg = {0};
    WNDCLASSEX  wcx = {0};
    WCHAR    szClassName[] = L"ZSPIN_LAYERED";
    WCHAR    szFileName[MAX_PATH] = {0};
    wcx.cbSize = sizeof( wcx);
    long     IsInitialized = GetClassInfoEx(hInst, szClassName, &wcx);
    if(IsInitialized == 0 )
    {
        wcx.style = CS_HREDRAW | CS_VREDRAW;
        wcx.lpfnWndProc =  &SpinnerProc;
        wcx.cbClsExtra = 0;
        wcx.cbWndExtra = 0;
        wcx.hInstance = hInst;
        wcx.hIcon = NULL;
        wcx.hCursor = LoadCursor( NULL, IDC_ARROW);
        wcx.hbrBackground = NULL;
        wcx.lpszMenuName = NULL;
        wcx.lpszClassName = szClassName;
        wcx.hIconSm = wcx.hIcon;
        if(RegisterClassEx( &wcx))
        {
            IsInitialized = - 1;
        }
    }
    if(IsInitialized )
    {
        hWnd = CreateWindowEx( WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_LAYERED, szClassName, szClassName, WS_POPUP | WS_VISIBLE, 0, 0, 0, 0, NULL, (HMENU)( HMENU) NULL, hInst, NULL);
        if(hWnd )
        {
            LONG_PTR  hGDIplus = 0;
            RtlMoveMemory( &szFileName, CmdLine, sizeof(szFileName));
            if(FileExist(szFileName))
            {
                hGDIplus = GdipStart();
                if(hGDIplus )
                {
                    SpinnerInit(hParent, hWnd, szFileName, nSpeedDelay);
                }
            }
            if(hGDIplus == 0 )
            {
                DestroyWindow(hWnd);
            }
            UpdateWindow(hWnd);
            while(GetMessage( &msg, NULL, 0, 0))
            {
                TranslateMessage( &msg);
                DispatchMessage( &msg);
            }

            if(hGDIplus )
            {
                GdipEnd(hGDIplus);
            }
            nRet = ( long) msg.wParam;
        }
    }
    return nRet;
}




My batch file:


CALL "%VS120COMNTOOLS%..\..\VC\vcvarsall.bat" x86_amd64

cl -c %1.cpp /GL /W3 /Gy /Zc:wchar_t /Zi /Gm- /Ox  /fp:precise /D "_UNICODE" /D "UNICODE" /errorReport:prompt /GF /WX- /Zc:forScope /GR- /Gd /Oy /Oi /MT /EHsc  /nologo /Ot



LINK %1.obj /OUT:"%1.exe" /MANIFEST:NO /LTCG /NXCOMPAT /DYNAMICBASE "Dwmapi.lib" "libcpmt.lib" "msvcrt.lib" "kernel32.lib" "user32.lib" "gdi32.lib" /MACHINE:X64 /OPT:REF /NODEFAULTLIB /NOLOGO








Title: Re: Layered or DWM Composited Spinner
Post by: James C. Fuller on January 23, 2014, 01:00:07 PM
Patrice,
  I want to thank you for posting your excursions into c++.
This last one has shown how to really reduce app size.
I was especially interested in your dynamic calls to dll functions.
I am going to try this approach on an app I've written and maybe even use it in bc9Basic.

James
Title: Re: Layered or DWM Composited Spinner
Post by: Patrice Terrier on January 23, 2014, 02:15:15 PM
James--

I am experimenting the same concept of size reduction with a 64-bit version of BassBox.

I have also written a couple of new functions to avoid the use of the string class.

Note: the size reduction is also increasing much the resulting code speed.

More to come...

Title: Sea wave animation
Post by: Patrice Terrier on July 17, 2014, 04:06:34 PM
Here is a nice new animation.

Very good for summer time  :)

...