Multithreading und GetMessage



  • Ich hatte heute mal Lust was neues zu schreiben, daher habe ich ein vollständiges Programm zu deinem Problem geschreiben. Allerdings verwende ich nicht die originale WinAPI, sondern meinen eigenen Header, der sich im Grunde 1-zu-1 zum Microsofts windows.h mappen lässt. Bei mir haben die Windows Dinge Präfixe wie win_ , WIN_ und Win . Der größte Unterschied sind die primitiven Datentypen: so werden bei mir z.B. statt WPARAM uintptr_t und statt INT int32_t verwendet.

    Programm ist etwas konfigurierbar durch die Konstanten ganz oben im Code.

    #include <libwindows/windows.h>
    
    #include <stdlib.h>
    #include <signal.h>
    #include <assert.h>
    #include <stdio.h>
    #include <limits.h>
    #include <inttypes.h>
    #include <stdatomic.h>
    #include <stdbool.h>
    
    // Global Constants
    enum
    {
    	window_width = 640,
    	window_height = 480,
    
    	ball_radius = 50,
    	vsync = 1,
    };
    static double const refresh_rate = 60.0; // In Hertz
    static double const tick_rate = 1.0 / 100.0; // In seconds
    
    struct State
    {
    	// Coordinates specify the center of the ball.
    	int x;
    	int y;
    	bool downwards;
    };
    
    static atomic_bool g_running = ATOMIC_VAR_INIT(true);
    
    static int64_t get_pfreq(void)
    {
    	static int64_t freq = 0;
    	if (freq == 0)
    	{
    		union WinLargeInteger li;
    		WinBool const ret = win_query_performance_frequency(&li);
    		assert(ret != 0);
    		freq = li.quad_part;
    	}
    	return freq;
    }
    
    static int64_t get_pcount(void)
    {
    	union WinLargeInteger li;
    	WinBool const ret = win_query_performance_counter(&li);
    	assert(ret != 0);
    	return li.quad_part;
    }
    
    static double get_time(void)
    {
    	return (double)get_pcount() / (double)get_pfreq();
    }
    
    static void signal_handler(int sig)
    {
    	atomic_store(&g_running, false);
    }
    
    static void update(struct State* const state)
    {
    	assert(state);
    
    	if (state->downwards)
    		state->y += 1;
    	else
    		state->y -= 1;
    
    	if ((state->y <= ball_radius) || (state->y >= window_height - ball_radius))
    		state->downwards = !state->downwards;
    }
    
    static void render(WinDeviceContext* const dc, struct WinRect const* const rect, struct State const* const state)
    {
    	assert(dc);
    	assert(rect);
    	assert(state);
    
    	// Draw background.
    	WinBrush* const background = (WinBrush*)win_get_stock_object(win_BLACK_BRUSH);
    	win_fill_rect(dc, rect, background);
    
    	// Draw ball.
    	win_ellipse(dc, state->x - ball_radius, state->y - ball_radius, state->x + ball_radius, state->y + ball_radius); // Default color is white.
    }
    
    static uint32_t WIN_CALLBACK thread_proc(void* const param)
    {
    	WinWindow* const wnd = param;
    	assert(wnd);
    
    	struct WinRect client_rect;
    	win_get_client_rect(wnd, &client_rect);
    
    	WinDeviceContext* const dc = win_get_dc(wnd);
    	WinDeviceContext* const mem_dc = win_create_compatible_dc(dc);
    	WinBitmap* const bitmap = win_create_compatible_bitmap(dc, client_rect.right - client_rect.left, client_rect.bottom - client_rect.top);
    	if (!bitmap)
    		return 1;
    	WinBitmap* const old_bitmap = (WinBitmap*)win_select_object(mem_dc, (WinGdiObj*)bitmap);
    
    	struct State state = { window_width / 2, ball_radius, true }; 
    
    	double seconds_counter = 0.0; // This counter is used to print a message every second.
    	uint64_t frames = 0;
    	uint64_t updates = 0;
    
    	double previous_time = get_time();
    	double time_lag = 0.0;
    
    	if (vsync)
    		win_time_begin_period(1);
    
    	while (atomic_load(&g_running))
    	{
    		while (time_lag >= tick_rate)
    		{
    			update(&state);
    			++updates;
    
    			time_lag -= tick_rate;
    		}
    
    		if (seconds_counter >= 1.0)
    		{
    			printf("FPS: %"PRIu64", Updates: %"PRIu64"\n", frames, updates);
    
    			frames = 0;
    			updates = 0;
    			seconds_counter -= 1.0;
    		}
    
    		render(mem_dc, &client_rect, &state);
    		win_bit_blt(dc, client_rect.left, client_rect.top, client_rect.right - client_rect.left, client_rect.bottom - client_rect.top, mem_dc, 0, 0, win_SRCCOPY);
    		++frames;
    
    		double const target_seconds = 1.0 / refresh_rate;
    		double current_time = get_time();
    		double elapsed_time = current_time - previous_time;
    		if (vsync && elapsed_time < target_seconds) // Poor mans V-Sync
    		{
    			// Take a nap.
    			uint32_t const sleep_ms = (uint32_t)(1000.0 * (target_seconds - elapsed_time));
    			win_sleep(sleep_ms);
    
    			// Spinlock for the rest of the current frame time.
    			while ((elapsed_time = current_time - previous_time) < target_seconds)
    				current_time = get_time();
    		}
    		previous_time = current_time;
    		time_lag += elapsed_time;
    
    		seconds_counter += elapsed_time;
    	}
    
    	win_select_object(mem_dc, (WinGdiObj*)old_bitmap);
    	win_delete_object((WinGdiObj*)bitmap);
    	win_delete_dc(mem_dc);
    	win_release_dc(wnd, dc);
    	if (vsync)
    		win_time_end_period(1);
    	return 0;
    }
    
    static intptr_t WIN_CALLBACK wnd_proc(WinWindow* const wnd, uint32_t const msg, uintptr_t const wparam, intptr_t const lparam)
    {
    	static WinHandle* thread = 0;
    
    	switch (msg)
    	{
    		case win_WM_CREATE:
    		{
    			thread = win_create_thread(0, 0, &thread_proc, wnd, 0, 0);
    			if (!thread)
    				return -1;
    			return 0;
    		}
    
    		case win_WM_DESTROY:
    		{
    			atomic_store(&g_running, false);
    			win_wait_for_single_object(thread, win_INFINITE);
    
    			win_post_quit_message(EXIT_SUCCESS);
    			return 0;
    		}
    	}
    
    	return win_def_window_proc(wnd, msg, wparam, lparam);
    }
    
    int main(void)
    {
    	signal(SIGINT, &signal_handler);
    	signal(SIGTERM, &signal_handler);
    
    	WinInstance* const instance = win_get_module_handle(0);
    
    	struct WinWindowClassEx const wc =
    	{
    		.size = sizeof wc,
    		.instance = instance,
    		.window_proc = &wnd_proc,
    		.class_name = u"main window",
    		.cursor = win_load_cursor(0, WIN_IDC_CROSS),
    	};
    
    	WinAtom const atom = win_register_class_ex(&wc);
    	if (!atom)
    		return EXIT_FAILURE;
    
    	uint32_t const styles = win_WS_BORDER | win_WS_CAPTION | win_WS_SYSMENU;
    	uint32_t const ex_styles = win_WS_EX_APPWINDOW;
    	struct WinRect window_rect = { 0, 0, window_width, window_height };
    	if (!win_adjust_window_rect_ex(&window_rect, styles, win_FALSE, ex_styles))
    		return EXIT_FAILURE;
    
    	WinWindow* const wnd = win_create_window_ex(ex_styles, wc.class_name, u"thread test", styles, win_CW_USEDEFAULT, win_CW_USEDEFAULT, window_rect.right - window_rect.left, window_rect.bottom - window_rect.top, 0, 0, instance, 0);
    	if (!wnd)
    		return EXIT_FAILURE;
    
    	win_show_window(wnd, win_SW_SHOW);
    
    	struct WinMessage msg;
    	WinBool ret = 0;
    	while ((ret = win_get_message(&msg, 0, 0, 0)) != 0)
    	{
    		if (ret == -1)
    			return EXIT_FAILURE;
    
    		win_translate_message(&msg);
    		win_dispatch_message(&msg);
    	}
    
    	assert(msg.wparam <= INT_MAX);
    	return (int)msg.wparam;
    }
    


  • AnfängerX schrieb:

    @Wade1234: wahrscheinlich mache ich das falsch, aber bei mir führt das zu einem deadlock...

    und bei meiner anderen Frage, bitte keine Scheu...

    Danke für eure Hilfe!

    mir fiel irgendwann heute nacht ein, dass du den thread auch nicht "gejoined" hast.



  • @Biolunar: Vielen Dank - super cool! ich schaue es mir am Wochenende noch einmal in Ruhe an. Denn die atomic Sachen sind mir neu und die muss ich googeln. 🙄

    @wade1234: das stimmt, ich wusste nicht, dass das bei einem WindowsThread auch notwendig ist. An dem Problem als solches würde es aber auch nichts ändern oder?

    danke für eure Hilfe !



  • AnfängerX schrieb:

    @wade1234: das stimmt, ich wusste nicht, dass das bei einem WindowsThread auch notwendig ist. An dem Problem als solches würde es aber auch nichts ändern oder?

    danke für eure Hilfe !

    eigentlich müsste sich das problem doch genau dadurch lösen, weil du erst den thread beendest und dann das fenster geschlossen wird.

    oder gibt es noch ein anderes problem?


Anmelden zum Antworten