🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

That "other" project

Published February 07, 2009
Advertisement
This weekend I dug out the code for the Epoch language and started hacking around again. Several small things have been done, none of which are all that interesting; and one major thing is now functional.

I've completed the marshalling layer that allows external code to call back into Epoch code. Combined with the already existing ability to call external APIs, this gives us all the pieces we need to write a full-fledged Windows program in Epoch.

My final goal is to write the "scribble" application as a proof of concept. The major stepping-stone along the way was implementing a test of the callback system, which you can see here:

//// CALLBACK.EPOCH//// Demonstration of calling back into Epoch code from external code//structure messagetype :(	integer(hwnd),	integer(message),	integer(wparam),	integer(lparam),	integer(time),	integer(pointx),	integer(pointy))external "user32.dll" EnumWindows : (function callback : (integer, integer)->(boolean), integer(lparam)) -> (boolean)external "user32.dll" MessageBoxW : (integer(windowhandle), string(message), string(caption), integer(typeflags)) -> (integer)external "user32.dll" GetWindowTextW : (integer(hwnd), string ref(buffer), integer(maxlength)) -> (integer)external "user32.dll" GetMessageW : (messagetype ref(msg), integer(hwnd), integer(filtermin), integer(filtermax)) -> (boolean)external "user32.dll" TranslateMessage : (messagetype(msg)) -> (boolean)external "user32.dll" DispatchMessageW : (messagetype(msg)) -> (integer)external "user32.dll" SetTimer : (integer(hwnd), integer(eventid), integer(time), function callback : (integer, integer, integer, integer) -> ()) -> (integer)external "user32.dll" KillTimer : (integer(hwnd), integer(id)) -> (boolean)external "user32.dll" PostQuitMessage : (integer(exitcode)) -> ()entrypoint : () -> (){	EnumWindows(enumcallback, 42)	SetTimer(0, 42, 2000, timercallback)	messagetype(msg, 0, 0, 0, 0, 0, 0, 0)	while(GetMessageW(msg, 0, 0, 0))	{		TranslateMessage(msg)		DispatchMessageW(msg)	}}enumcallback : (integer(hwnd), integer(lparam)) -> (boolean(ret, true)){	string(hackish_buffer, "                                                  ")	integer(result, GetWindowTextW(hwnd, hackish_buffer, 29))	if(greater(result, 0))	{		debugwritestring(concat("Callback invoked! ", hackish_buffer))	}}timercallback : (integer(hwnd), integer(msg), integer(eventid), integer(time)) -> (){	KillTimer(0, eventid)	string(message, "Callback successfully invoked!")	string(caption, "Epoch Language Interop")	MessageBoxW(0, message, caption, 0)	PostQuitMessage(0)}


This demonstrates two types of callback: one from a standard function that blocks until the callbacks are completed; and one from a message-based callback that fires only when the application's message pump is running.

Also, this is officially the first Epoch program to feature a Windows message pump. It's been pretty straightforward so far, so with any luck that Scribble demo should be done soon.


And now for the interesting bit
I'm really quite proud of this system - it took a lot to get it working. So here's a quick overview of how it works.

We already had first-class function support implemented; it was simple to add a bridge that lets you pass an Epoch function to an external API. The problem is, how does the external API know which Epoch code to invoke?

The secret sauce is a little bit of dynamic code generation. First, we allocate a blob of memory. Then, we fill that buffer with a set of machine code instructions which store a pointer to the Epoch code that should be executed. Finally, we jump into a routine that retrieves the Epoch code pointer from the CPU registers, then invokes the code via the VM.

This generated code basically looks like this:
mov ecx, shim_address
mov edx, epoch_code_address
jmp ecx


The shim function is also pretty simple:
mov CallbackFunction, edx
mov ESPSave, esp
jmp CallbackInvoke


CallbackFunction and ESPSave are global variables that are used by the CallbackInvoke routine to know which Epoch function to invoke, and where to find the function parameters, respectively.


The net result is that we can pass Epoch functions to external APIs and have them seamlessly call back into the VM. The only limitation on doing weird things is stack space.

So all things considered, this is a pretty major leap for Epoch. Next up, nontrivial Windows apps [smile]
0 likes 1 comments

Comments

Telastyn
oooh.
February 08, 2009 03:16 PM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement