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.
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.
- Using a thread animation, instead of a timer
- Using DWM to deal with transparency and variable opacity.
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.
Looks amazing, Patrice!
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!
...
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 :)
...
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.
...
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
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.
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.
- Always use direct call to the core Flat API (everything is already there).
- Use only native data type, especially char and wchar_t (this alone saves 13 Kb).
- Avoid the use of classes and OOP, the extra level of encapsulations requires much more dependencies.
- Always link with the core API LIBs like: user32.lib;kernel32.lib;gdi32; etc.,
- and ignore all default LIBs with Yes(/NODEFAULTLIB).
- Do not include a MANIFEST.
- When using a WS_EX_TOOLWINDOW, no need to embed ICON (or any other bloated resources).
Indeed, try to write C++, just like plain C. :)
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! :)
If you're going to avoid any C++ features, why don't use a plain ansi C compiler instead?
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.
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
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.
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
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
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.
...
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.
...
Patrice,
I noticed you changed to __cdecl from __stdcall .
Was this the only change you made for the current size reduction?
James
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
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.
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.
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
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
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...
Here is a nice new animation.
Very good for summer time :)
...