a little Win32 hacking under Linux
Kragen Javier Sitaker
kragen at canonical.org
Sat Aug 2 03:37:01 EDT 2008
(The programs in this post will be available in
<http://canonical.org/~kragen/sw/win32>, including in compiled form,
when I get around to uploading them, probably before this post goes
out on kragen-hacks. Like everything else posted to kragen-hacks
without a notice to the contrary, these programs are in the public
domain.)
Debian Etch has both Wine, to run Win32 programs, and MinGW32 3.4.5,
to create them (although it's missing the MinGW GDB, so there's no
debugger). I've been toying with that lately. Here are some things I
learned:
1. Wine is remarkably compatible with the proprietary implementations
of Win32.
2. By default MinGW compiles things for the "Windows CUI" subsystem,
which makes a console window pop up when you run them (on Windows
XP but not in Wine); you can see this problem with `winedump dump
foo.exe` and fix it by telling the linker `--subsystem 0x2`.
3. MinGW puts "stabs" debugging information into the "PE"-format
executables it creates, but `winedbg --gdb` doesn't know how to use
stabs in PE; it's for debugging Wine, not debugging Win32 programs
running in Wine. As a consequence, running GDB with `winedbg
--gdb` to debug Win32 programs is more or less comparable to
running GDB with `target remote localhost:1234` to debug MS-DOS
programs in QEMU.
4. Win32 is a pretty ugly API, more or less comparable to Xlib. I
sort of knew that before. For example, I used to have a Win32
equivalent of `perror` somewhere (something like 10 lines to
interpret `GetLastError`) but I can't find it or figure out how to
reimplement it. (There's a nice list in `winesrc/include/winerror.h`
though.)
5. Wine has really fantastic debugging dumps when programs crash. You
can debug a lot of problems faster by compiling a crashing C
program with MinGW, and running it under Wine, and looking at the
debug dump, than you often can by compiling it normally and running
it under GDB.
6. PE files have an "entrypoint RVA" in the PE header that tells them
where to start running.
7. Wine and MinGW32 do not include any significant documentation of
Win32. However, the Wine source code is really helpful, even
though it's 1.2-1.7 million lines of code, about the size of the
Encyclopedia Britannica, which can make it hard to find things
sometimes. `etags` helps a lot with that.
8. Most Win32 API tutorials online are really terrible, full of errors
and badly written. The exception is theForger's:
<http://www.winprog.org/tutorial/>
9. Win32 debugging information is gigantic. `strip` can reduce a
600kB program to less than 8kB.
10. Wine crashes when run without a stack size ulimit. GNU Make
removes the stack size ulimit that is normally present.
Consequently, programs run in Wine crash when run from a Makefile
unless you do something like `bash -c "ulimit -s 8192; $(WINE)
$<"` in the Makefile to run them.
Anyway, I wrote this hello-world assembly program, based on Jeff
Huang's Windows Assembly Programming Tutorial:
## little "hello, world" bare Win32 program in gas
## I compile it with Debian Etch's MinGW32 3.4.5.20060117 as
## follows:
## i586-mingw32msvc-gcc -g -Wall -nostdlib w32hello3.S \
## -lkernel32 -luser32 -Wl,--subsystem,0x2 -o w32hello3.exe
MB_OK = 0
.data
hello: .asciz "hello, world"
.text
push $MB_OK
push $hello # title
push $hello # contents
push $0 # parent window handle
# I don't know why it's @16 and not @4 or something. Maybe
# it's the number of bytes it pops off the stack when it
# returns?
call _MessageBoxA at 16 # ASCII version, not Wide
push $0
call _ExitProcess at 4
I also worked through theForger's fantastic Win32-in-C programming
tutorial and wrote some simple C programs. They seem to work on the
Windows XP machines in the nearby locutorio as well as in Wine on my
laptop. Here's a slow, but pretty, graphics hack, based in part on
Farbrausch's "fr-016: bytes"; the original version is multiple files,
but I've turned it into a single file for the purpose of this post.
#include <windows.h>
#include <stdio.h>
// will probably only compile with MinGW GCC
// compile as follows:
// i586-mingw32msvc-gcc -Wall circ.c -Wl,--subsystem,0x2 -o circ.exe -lgdi32
typedef struct {
PWNDCLASSEX class;
HWND hWnd;
char *title;
int style;
int x, y;
} window_instance;
// call this to initialize a WNDCLASSEX and a window_instance.
// doesn't register the window class. do that yourself.
void init_window_classex(PWNDCLASSEX class, // pointer to uninitialized class
window_instance *win, // pointer to uninitialized inst
HINSTANCE hInstance, // of the application
WNDPROC WndProc,
LPCSTR name); // of the window class
// call this to actually instantiate a window handle
// returns 0 on failure
int create_window(window_instance *win, int width, int height);
// for things that you want to MessageBox about and exit if they fail
#define check(foo) pcheck(foo, int)
#define pcheck(foo, type) (type)_check(#foo, (int)foo)
int _check(char *contents, int result);
void init_window_classex(PWNDCLASSEX class,
window_instance *win,
HINSTANCE hInstance,
WNDPROC WndProc,
LPCSTR name)
{
WNDCLASSEX mine = { // What a fucking pain in the ass.
// never changing:
.cbSize = sizeof(WNDCLASSEX),
.style = 0,
.cbClsExtra = 0,
.cbWndExtra = 0,
// supplied by user:
.lpfnWndProc = WndProc,
.hInstance = hInstance,
.lpszClassName = name,
// sensible defaults:
.hIcon = LoadIcon(NULL, IDI_APPLICATION),
.hCursor = LoadCursor(NULL, IDC_ARROW),
.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH),
.lpszMenuName = NULL,
.hIconSm = LoadIcon(NULL, IDI_WINLOGO),
};
*class = mine;
win->class = class;
win->hWnd = NULL;
win->title = "Anonymous coward window"; // default value
win->style = WS_OVERLAPPEDWINDOW;
win->x = win->y = CW_USEDEFAULT;
}
int create_window(window_instance *win, int width, int height)
{
win->hWnd = CreateWindow(win->class->lpszClassName,
win->title,
win->style,
win->x, win->y,
width, height,
NULL, // parent handle
NULL, // menu handle
win->class->hInstance,
NULL);
return !!win->hWnd;
}
int _check(char *contents, int result)
{
char errcodespace[32];
if (result) return result;
MessageBox(NULL, contents, "fatal _check error: this code failed", MB_OK);
sprintf(errcodespace, "error code %d", (int)GetLastError());
MessageBox(NULL, errcodespace, "here's why", MB_OK);
ExitProcess(1);
}
int counter = 0;
enum { maxsize = 505 };
int y = 0, mousex = 0, mousey = 0;
void draw_in(HWND hWnd) {
HDC hDC = pcheck(GetDC(hWnd), HDC);
// double buffer one scan line
HDC hDCBuffer = pcheck(CreateCompatibleDC(hDC), HDC);
HBITMAP hbmBuffer = pcheck(CreateCompatibleBitmap(hDC, maxsize, 1), HBITMAP);
HBITMAP hbmOldBuffer = pcheck(SelectObject(hDCBuffer, hbmBuffer), HBITMAP);
int x;
for (x = 0; x < maxsize; x++) {
int dx = x - mousex;
int dy = y - mousey;
// YES this is a slow way of doing it; less than a megapixel per
// second on my laptop. Double buffering doesn't help much.
// Sometimes, apparently at random, this call returns false (in
// WINE). I don't know why. So I don't check the result.
SetPixel(hDCBuffer, x, 0, (COLORREF)(dx*dx + dy*dy + counter));
counter = (counter + 1) & 0xffffff;
}
check(BitBlt(hDC, 0, y, maxsize, 1, hDCBuffer, 0, 0, SRCCOPY));
y = (y + 1) % maxsize;
check(SelectObject(hDCBuffer, hbmOldBuffer));
check(DeleteObject(hbmBuffer));
check(DeleteDC(hDCBuffer));
check(ReleaseDC(hWnd, hDC));
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg) {
case WM_MOUSEMOVE:
mousex = LOWORD(lParam);
mousey = HIWORD(lParam);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, msg, wParam, lParam);
}
return 0;
}
int my_message_loop(window_instance *win)
{
MSG msg;
for (;;) {
// PM_NOYIELD means don't block.
int rv = PeekMessage(&msg, NULL, 0, 0, PM_NOYIELD);
if (!rv) draw_in(win->hWnd); // no messages pending
else if (msg.message == WM_QUIT) return msg.wParam;
else {
// I hope this second PeekMessage is guaranteed to work, and get
// the same message.
check(PeekMessage(&msg, NULL, 0, 0, PM_NOYIELD | PM_REMOVE));
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
WNDCLASSEX windowClass;
window_instance win;
init_window_classex(&windowClass, &win, hInstance, WndProc, "AClass");
windowClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
check(RegisterClassEx(&windowClass));
win.title = "hello, world";
check(create_window(&win, maxsize, maxsize));
ShowWindow(win.hWnd, nCmdShow);
return my_message_loop(&win);
}
More information about the Kragen-hacks
mailing list