Jump to page: 1 2
Thread overview
Help with Win32: PostQuitMessage(0) doesn't post WM_QUIT apparently, because the message loop is not exited.
Aug 13, 2021
Ruby The Roobster
Aug 13, 2021
frame
Aug 13, 2021
Mike Parker
Aug 13, 2021
Ruby The Roobster
Aug 13, 2021
Ruby The Roobster
Aug 13, 2021
Ruby The Roobster
Aug 13, 2021
Mike Parker
Aug 13, 2021
Ruby The Roobster
Aug 14, 2021
Mike Parker
Aug 13, 2021
Ruby The Roobster
Aug 14, 2021
Mike Parker
August 13, 2021

Here is the message loop:

while(GetMessage(&msg, hwnd,0,0))   {
   if(msg.message == WM_QUIT)
      break;
   TranslateMessage(&msg);
   DispatchMessage(&msg);
}

The WndProc(LRESULT CALLBACK):

extern(Windows)
LRESULT WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) nothrow	{
	switch(msg)	{
		case WM_CREATE:
			//...
			return 0;
			break;
		case WM_DESTROY:
			try	{
				entity.terminate();
			}
			catch(Throwable e)	{
				PostQuitMessage(1);
			}
			PostQuitMessage(0);
			break;
		default:
	}
return DefWindowProc(hwnd,msg,wparam,lparam);
}

When I run the program and close the window, the program still runs in background mode. I don't know why this happens nor how to fix it. Does anybody know what's going on?

August 13, 2021

On Friday, 13 August 2021 at 00:30:59 UTC, Ruby The Roobster wrote:

>

When I run the program and close the window, the program still runs in background mode. I don't know why this happens nor how to fix it. Does anybody know what's going on?

AFAIK GetMessage() may return -1 if some error happend, so your loop won't exit.

August 13, 2021

On Friday, 13 August 2021 at 00:30:59 UTC, Ruby The Roobster wrote:

>

When I run the program and close the window, the program still runs in background mode. I don't know why this happens nor how to fix it. Does anybody know what's going on?

frame beat me to it, but it may well be that you're getting -1. The documentation says that a window that has already been destroyed will result in the hWnd parameter being invalid, which will cause the function to return -1.

August 13, 2021

On Friday, 13 August 2021 at 03:05:22 UTC, Mike Parker wrote:

>

On Friday, 13 August 2021 at 00:30:59 UTC, Ruby The Roobster wrote:

>

When I run the program and close the window, the program still runs in background mode. I don't know why this happens nor how to fix it. Does anybody know what's going on?

frame beat me to it, but it may well be that you're getting -1. The documentation says that a window that has already been destroyed will result in the hWnd parameter being invalid, which will cause the function to return -1.

So I edited the message loop:

        	while((b = GetMessage(&msg,hwnd,0,0)) != 0)	{
        		if(exit || b == -1)	{
        			return msg.wParam;
        		}
        		TranslateMessage(&msg);
        		DispatchMessage(&msg);
  	}

And some of the WndProc:

        extern(Windows)
        LRESULT WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) nothrow	{
        	switch(msg)	{
        		case WM_CREATE:
        			//...
        			return 0;
        			break;
        		case WM_CLOSE:
        			DestroyWindow(hwnd);
        			try	{
        				entity.terminate();
        				exit = true;
        			}
        			catch(Throwable e){
        			}
        			break;
        		case WM_DESTROY:
        			try	{
        				entity.terminate();
        				exit = true;
        			}
        			catch(Throwable e)	{
        				PostQuitMessage(1);
        			}
        			PostQuitMessage(0);
        			break;
        		default:
        		return DefWindowProc(hwnd,msg,wparam,lparam);
        	}
        		return 0;
        }

It still produces the same result(the program never exits), so it doesn't look like GetMessage() is returning -1...

August 13, 2021

On Friday, 13 August 2021 at 03:05:22 UTC, Mike Parker wrote:

>

On Friday, 13 August 2021 at 00:30:59 UTC, Ruby The Roobster wrote:

>

When I run the program and close the window, the program still runs in background mode. I don't know why this happens nor how to fix it. Does anybody know what's going on?

frame beat me to it, but it may well be that you're getting -1. The documentation says that a window that has already been destroyed will result in the hWnd parameter being invalid, which will cause the function to return -1.

Nevermind. I am just redoing the LRESULT function. Seems to work now.

August 13, 2021

On Friday, 13 August 2021 at 03:05:22 UTC, Mike Parker wrote:

>

On Friday, 13 August 2021 at 00:30:59 UTC, Ruby The Roobster wrote:

>

When I run the program and close the window, the program still runs in background mode. I don't know why this happens nor how to fix it. Does anybody know what's going on?

frame beat me to it, but it may well be that you're getting -1. The documentation says that a window that has already been destroyed will result in the hWnd parameter being invalid, which will cause the function to return -1.

After rewriting the program, I figured out that the bug was still there. Here is the function that causes it:

public class Entity	{
	package:
		ulong id;
		wchar[] name;
		static ulong nextid;
		Point centre;
		Skeleton skeleton;
	public:
		this(ulong id, inout(wchar)[] name,Point centre, Skeleton skeleton)	{
			assert(init == true, "Error: dutils.entity has not been initialized yet. Call dutils.entity.initialize() to initialize the library.");
			this.id = id;
			this.name.length = name.length;
			for(uint i = 0; i < name.length; i++)	{
				this.name[i] = name[i];
			}
			this.nextid = this.id + 1;
			this.centre = centre;
			if(!skeleton.init)	{
				this.skeleton = skeleton;
			}
			entitytable.length +=1;
			entitytable[cast(uint)id] = this;
		}

		~this()	{
			entitytable.length -= 1;
			this.nextid -=1;
			did(cast(uint)this.id);
		}

		debug immutable(char)[] f() @property	{
			import std.format : format;
			return format("ID: %s, Name: %s, NextID: %s, Centre: %s, Skeleton: %s", this.id, this.name, this.nextid, this.centre, this.skeleton);
		}

		ulong Id() @property	{
			return id;
		}
}

Context for this: I am creating a module of my own, and this is a class contained in the module. You will notice that after calling this class' constructor anywhere in a Win32 API program, that the program doesn't close after the window is closed.

Here are the files:

entity.d:

module dutils.entity;

debug { package import core.sys.windows.winuser; } //For MessageBoxA in debug messages...

public struct Skeleton	{
		public Face[] faces;
		package bool init = false;
		public this(Face[] faces)	{
			this.faces.length = faces.length;
			for(uint i = 0; i < faces.length; i++)	{
				this.faces[i] = faces[i];
			}
		}
		package this(bool init)	{
			this.init = init;
		}
		public void opAssign(Skeleton rhs)	{
			this.faces.length = rhs.faces.length;
			for(uint i; i < rhs.faces.length; i++)	{
				this.faces[i] = rhs.faces[i];
			}
			this.init = rhs.init;
		}
}

public struct Face	{
	Point[] points;
	Point centre;
	void opAssign(Face rhs)	{
		this.points.length = rhs.points.length;
		for(uint i = 0; i < rhs.points.length; i++)	{
			this.points[i] = rhs.points[i];
		}
		this.centre = rhs.centre;
	}
}

public struct Point	{
	real x;
	real y;
	real z;
	void opAssign(Point rhs)	{
		this.x = rhs.x;
		this.y = rhs.y;
		this.z = rhs.z;
	}
}

public class Entity	{
	package:
		ulong id;
		wchar[] name;
		static ulong nextid;
		Point centre;
		Skeleton skeleton;
	public:
		this(ulong id, inout(wchar)[] name,Point centre, Skeleton skeleton)	{
			assert(init == true, "Error: dutils.entity has not been initialized yet. Call dutils.entity.initialize() to initialize the library.");
			this.id = id;
			this.name.length = name.length;
			for(uint i = 0; i < name.length; i++)	{
				this.name[i] = name[i];
			}
			this.nextid = this.id + 1;
			this.centre = centre;
			if(!skeleton.init)	{
				this.skeleton = skeleton;
			}
			entitytable.length +=1;
			entitytable[cast(uint)id] = this;
		}

		~this()	{
			entitytable.length -= 1;
			this.nextid -=1;
			did(cast(uint)this.id);
		}

		debug immutable(char)[] f() @property	{
			import std.format : format;
			return format("ID: %s, Name: %s, NextID: %s, Centre: %s, Skeleton: %s", this.id, this.name, this.nextid, this.centre, this.skeleton);
		}

		ulong Id() @property	{
			return id;
		}
}

public class ImmobileEntity : Entity	{ //Same as Entity, but with a different constructor...
	public:
		this(inout(wchar)[] name, Point centre, Skeleton skeleton)
			in	{
				assert(skeleton.init == false, "Error: Member Skeleton.init must always be set to false.");
			}
			do	{
				super(this.nextid, name, centre, skeleton);
			}
}

public class MobileEntity : Entity	{ //Same as entity, but has a move feature(and a speed in miliseconds per whole number moved)...
	package:
		uint speed;
	public:
		this(inout(wchar)[] name, Point centre, Skeleton skeleton, uint speed)
			in	{
				assert(skeleton.init == false, "Error: Member Skeleton.init must always be set to false.");
			}
			do	{
				super(this.nextid, name, centre, skeleton);
				this.speed = speed;
			}

		void move(Point newpos)	{ //Distance between one point and another
			import core.thread;
			import core.math : sqrt;
			real temp = sqrt(((newpos.x - this.centre.x)^^2) + ((newpos.y - this.centre.y)^^2) + ((newpos.z - this.centre.z)^^2));
			temp *= speed;
			uint time = cast(uint) temp;
			Thread.sleep(dur!("msecs")(time));
			this.centre = newpos;
		}
}
package bool init;
package Entity entity;
public Entity[] entitytable;

public void initialize()	{
	entitytable.length = 0;
	init = true;
	entity = new Entity(0, "BaseEntityInitialized@id0"w, Point(0,0,0), Skeleton(true));
}

public void terminate()	{
	for(uint i = 0; i < entitytable.length; i++)	{
		destroy(entitytable[i]);
	}
	entitytable.length = 0;
}

package void did(uint currid)	{
	for(uint i = currid; i < entitytable.length; i++)	{
		entitytable[i].id -=1;
	}
}
debug	{
	public void DebugInfo()	{ //Debug messages...
		for(uint i = 0; i < entitytable.length; i++)	{
			MessageBoxA(null,cast(const(char)*)entitytable[i].f,"Test", MB_ICONINFORMATION | MB_OK);
		}
	}
}

test.d

import core.sys.windows.windef;
import core.runtime;
import core.sys.windows.wingdi;
import core.sys.windows.winuser;
import entity;
bool exit = false;
extern(Windows)
int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)	{
	int ret;
	try	{
		Runtime.initialize();
		ret = prog(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
		Runtime.terminate();
	}
	catch(Throwable o)	{
		MessageBoxA(null, "Error","Error", MB_OK | MB_ICONERROR);
	}
	debug MessageBoxA(null, "HERE!", "HERE!", MB_OK);
	return ret;
}

int prog(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int CmdShow)	{
	WNDCLASSW wndclass;
	wndclass.style = CS_HREDRAW | CS_VREDRAW;
	wndclass.lpfnWndProc = &WndProc;
	wndclass.cbClsExtra = 0;
	wndclass.cbWndExtra = 0;
	wndclass.hInstance = hInstance;
	wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndclass.hbrBackground = cast(HBRUSH)GetStockObject(GRAY_BRUSH);
	wndclass.lpszMenuName = NULL;
	wndclass.lpszClassName = cast(const(wchar)*)"Test"w;
	if(!RegisterClassW(&wndclass))	{
		throw new Exception("F");
	}
	HWND hwnd = CreateWindowW(cast(const(wchar)*)"Test"w, cast(const(wchar)*)"Test Program"w, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 900, 900, NULL, NULL, hInstance, NULL);
	ShowWindow(hwnd, CmdShow);
	UpdateWindow(hwnd);
	MSG msg;
	while((GetMessage(&msg,NULL,0,0)))	{
		if(exit)	{
			break;
		}
		TranslateMessage(&msg);
		DispatchMessage(&msg);
		debug MessageBoxA(null, "HERE!", "HERE!", MB_OK);
	}
	return msg.wParam;
}

extern(Windows)
LRESULT WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) nothrow	{
	switch(msg)	{
		case WM_CREATE:
			try	{
				entity.initialize();
				Skeleton pyrskel;
				Face[4] pyrfaces;
				Point[3][2] pyrpoints;
				pyrpoints[0][0] = Point(0, 0, 1);
				pyrpoints[0][1] = Point(-1, 0, -1);
				pyrpoints[0][2] = Point(1, 0, -1);
				pyrpoints[1][0] = Point(0, 0.75, -0.5);
				pyrpoints[1][1] = Point(-1, 0, 0.5);
				pyrpoints[1][2] = Point(1, 0, -0.5);
				for(uint i = 0;i < 3;i++)	{
					pyrfaces[i].points.length = 3;
					for(uint j = 0;j < 3;j++)	{
						pyrfaces[i].points[j] = pyrpoints[1][j];
					}
				}
				for(uint i = 0;i < 3;i++)	{
					pyrfaces[3].points.length = 3;
					pyrfaces[3].points[i] = pyrpoints[0][i];
				}
				pyrskel = Skeleton(pyrfaces);
				auto Pyramid = new Entity(entitytable[0].Id+1,"Pyramid"w, Point(0,0,0), pyrskel);
			}
			catch(Throwable e)	{
				MessageBoxA(hwnd,"ERROR", "ERROR", MB_OK | MB_ICONERROR);
				PostQuitMessage(1);
			}
			return 0;
			break;
		case WM_CLOSE:
			try	{
				entity.terminate();
				DestroyWindow(hwnd);
			}
			catch(Throwable e)	{
				MessageBoxA(hwnd, "ERROR", "ERROR", MB_OK | MB_ICONERROR);
			}
			DestroyWindow(hwnd);
			exit = true;
			break;
		case WM_DESTROY:
			exit = true;
			PostQuitMessage(0);
			break;
		default:
		return DefWindowProc(hwnd,msg,wparam,lparam);
	}
		return 0;
}

Sorry for the inconvenience and longness of this post.

P.S: The issue isn't GetMessage(), I can confirm that.

August 13, 2021

On Friday, 13 August 2021 at 16:18:06 UTC, Ruby The Roobster wrote:

>

Context for this: I am creating a module of my own, and this is a class contained in the module. You will notice that after calling this class' constructor anywhere in a Win32 API program, that the program doesn't close after the window is closed.

You're hanging in Runtime.terminate. That's because of your Entity destructor, specifically this line:

entitytable.length -= 1;

Comment it out and the program exits successfully.

You aren't supposed to be manipulating GC-managed memory via class destructors. You can not rely on that memory being valid at the time that it's accessed in the destructor---the object may already have been destroyed. Nondeterministic destruction is the price you pay for letting the GC manager your object memory.

Of course, in this case, the problem will only crop up at termination since the array is declared at module scope so will be live up until the GC shuts down. But still, not something you should be doing.

The runtime will help you by throwing an error if you do anything that directly triggers an allocation, like calling new or performing an array append. But it won't help you with anything else.

Someone more versed than I with the GC innards may be able to answer whether an error should be thrown here as well, or if this goes under the undefined behavior category.

August 13, 2021

On 8/13/21 3:59 PM, Mike Parker wrote:

>

On Friday, 13 August 2021 at 16:18:06 UTC, Ruby The Roobster wrote:

>

Context for this: I am creating a module of my  own, and this is a class contained in the module.  You will notice that after calling this class' constructor anywhere in a Win32 API program, that the program doesn't close after the window is closed.

You're hanging in Runtime.terminate. That's because of your Entity destructor, specifically this line:

    entitytable.length -= 1;

Comment it out and the program exits successfully.

You aren't supposed to be manipulating GC-managed memory via class destructors. You can not rely on that memory being valid at the time that it's accessed in the destructor---the object may already have been destroyed. Nondeterministic destruction is the price you pay for letting the GC manager your object memory.

Of course, in this case, the problem will only crop up at termination since the array is declared at module scope so will be live up until the GC shuts down. But still, not something you should be doing.

The runtime will help you by throwing an error if you do anything that directly triggers an allocation, like calling new or performing an array append. But it won't help you with anything else.

Someone more versed than I with the GC innards may be able to answer whether an error should be thrown here as well, or if this goes under the undefined behavior category.

Well, subtracting the length doesn't do much, you aren't actually accessing the array block, you are just changing the reference (which lives in thread-local storage). I kind of feel like the whole entity table thing is not correct anyway. Did you (Mike) also comment out the did call? Because that looks more suspicious to me. What it is doing is going through all the entities from the removed one on and setting their id to 1 less. HOWEVER, it's not actually moving the entities down in the array.

So for instance, if you have 3 entities [e(0), e(1), e(2)] then what happens when you remove e(1) is it changes their ids to [e(0), e(0), e(1)] and then resizes the array to strip off the last one, so you get the destroyed entity still in the table, and the one that used to be e(2) out of the table (though its id is set to 1 now). The result will be [e(0), e(0)], with the second one pointing to an invalid block.

However, you will NEVER have an entity be destroyed, because there is always a reference to it in the table! They will only get destroyed at the end, via terminate where things are destroyed deterministically.

I think you are right that he shouldn't be changing that entitytable thing in the dtor. I also think, the memory management being used there is super-sketchy. Without knowing why you need to keep the table around in the first place, I'm unsure how it should be fixed.

I suspect there is a memory access violation or some other issue that's causing it to crash rather than exit normally.

-Steve

August 13, 2021

On Friday, 13 August 2021 at 19:59:46 UTC, Mike Parker wrote:

>

You aren't supposed to be manipulating GC-managed memory via class destructors. You can not rely on that memory being valid at the time that it's accessed in the destructor---the object may already have been destroyed. Nondeterministic destruction is the price you pay for letting the GC manager your object memory.

Of course, in this case, the problem will only crop up at termination since the array is declared at module scope so will be live up until the GC shuts down. But still, not something you should be doing.
...

Thank you very much. The program runs successfully now.

August 13, 2021

On Friday, 13 August 2021 at 21:10:38 UTC, Steven Schveighoffer wrote:

>

On 8/13/21 3:59 PM, Mike Parker wrote:

>

On Friday, 13 August 2021 at 16:18:06 UTC, Ruby The Roobster wrote:
...

>

...
...

Well, subtracting the length doesn't do much, you aren't actually accessing the array block, you are just changing the reference (which lives in thread-local storage). I kind of feel like the whole entity table thing is not correct anyway. Did you (Mike) also comment out the did call? Because that looks more suspicious to me. What it is doing is going through all the entities from the removed one on and setting their id to 1 less. HOWEVER, it's not actually moving the entities down in the array.

However, you will NEVER have an entity be destroyed, because there is always a reference to it in the table! They will only get destroyed at the end, via terminate where things are destroyed deterministically.

>

-Steve

Okay. I removed the destructors and the 'did' function. Instead of those, I created a destruct(Entity op) function that works without causing an error. The entitytable array is for the purpose of it being easier to search by id, because the index used to access the array is the same as the id. It's also(IMO) easier on my end to have every Entity object in a single array.

« First   ‹ Prev
1 2