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


