/* * Borland ObjectWindows callback bootstrap reconstructed from TDKPIN.EXE. * * Target: a9022f1894e3e6e21fc42e8f6c932f7c549ca77f63aaa0c488bb9d55d9d0174c * Functions: 1018:0045 and 1018:018b (NE export ordinals 208 and 211) */ #include "tdkpin_object_windows.h" #include "tdkpin_borland_runtime.h" enum { OBJECT_WINDOW_VMT_OFFSET = 0, OBJECT_WINDOW_HANDLE_OFFSET = 4, OBJECT_WINDOW_BOUND_PROC_OFFSET = 0x12, WIN16_GWL_WNDPROC = -4, DGROUP_OBJECT_SELECTOR_PROPERTY = 0x067a, /* "OW1" */ DGROUP_OBJECT_OFFSET_PROPERTY = 0x067e, /* "OW2" */ }; enum { VMT_MESSAGE_TABLE_OFFSET = 4, VMT_DEFAULT_MESSAGE_SLOT = 0x0c, MESSAGE_TABLE_PARENT_OFFSET = 0, MESSAGE_TABLE_CACHE_ID_OFFSET = 2, MESSAGE_TABLE_CACHE_SLOT_OFFSET = 4, MESSAGE_TABLE_COUNT_OFFSET = 6, MESSAGE_TABLE_IDS_OFFSET = 8, }; /* DGROUP 1028:0676/0678; valid only while CreateWindow16 is in progress. */ extern Win16FarPtr g_object_windows_creating_object; LRESULT16 SetWindowLong16(HWND16 window, INT16 index, int32_t value); BOOL16 SetProp16(HWND16 window, Win16FarPtr name, Win16Handle value); Win16Handle RemoveProp16(HWND16 window, Win16FarPtr name); Win16Handle GetProp16(HWND16 window, Win16FarPtr name); BOOL16 IsWindow16(HWND16 window); int32_t GetWindowLong16(HWND16 window, INT16 index); /* * 1018:0045 — associate a far ObjectWindows object with one HWND. * * Win16 SetProp stores only a 16-bit HANDLE, so ObjectWindows deliberately * splits the far pointer into two properties. OW1 holds the selector and OW2 * holds the offset; the inverse lookup routine recombines them. */ void object_windows_bind_window(HWND16 window, Win16FarPtr object) { (void)SetProp16( window, win16_dgroup_pointer(DGROUP_OBJECT_SELECTOR_PROPERTY), win16_far_selector(object)); (void)SetProp16( window, win16_dgroup_pointer(DGROUP_OBJECT_OFFSET_PROPERTY), win16_far_offset(object)); } /* 1018:0071 — remove both halves of the HWND/object association. */ void object_windows_unbind_window(HWND16 window) { (void)RemoveProp16( window, win16_dgroup_pointer(DGROUP_OBJECT_SELECTOR_PROPERTY)); (void)RemoveProp16( window, win16_dgroup_pointer(DGROUP_OBJECT_OFFSET_PROPERTY)); } /* * 1018:0097 — recover the bound ObjectWindows object for one HWND. * Generated thunks carry the object at +3/+5; subclassed/native procedures use * the OW1 selector and OW2 offset properties installed by 1018:0045. */ Win16FarPtr object_windows_object_from_window(HWND16 window) { if (!IsWindow16(window)) { return 0; } Win16FarPtr procedure = (Win16FarPtr)GetWindowLong16(window, WIN16_GWL_WNDPROC); uint16_t procedure_offset = win16_far_offset(procedure); bool generated_thunk = win16_read_u8(procedure) == 0xe8 && win16_read_u16(win16_far_add_offset(procedure, 1)) == (uint16_t)(0xffff - procedure_offset) && win16_read_u16(win16_make_far_pointer( win16_far_selector(procedure), 2)) == 0x2e5b; if (generated_thunk) { return win16_read_far_pointer(procedure, 3); } uint16_t selector = GetProp16( window, win16_dgroup_pointer(DGROUP_OBJECT_SELECTOR_PROPERTY)); uint16_t offset = GetProp16( window, win16_dgroup_pointer(DGROUP_OBJECT_OFFSET_PROPERTY)); return win16_make_far_pointer(selector, offset); } /* * 1018:018b — class bootstrap WNDPROC16. * * The initial class procedure is used for the first message only. It replaces * itself with the object's generated 7-byte bound thunk, publishes the HWND to * object association, then tail-dispatches the unchanged 10-byte WNDPROC frame. * AX:DX from that direct far call is returned unchanged. */ LRESULT16 object_window_bootstrap_proc( HWND16 window, UINT16 message, WPARAM16 wparam, LPARAM16 lparam) { Win16FarPtr object = g_object_windows_creating_object; Win16FarPtr bound_proc = win16_read_far_pointer(object, OBJECT_WINDOW_BOUND_PROC_OFFSET); (void)SetWindowLong16(window, WIN16_GWL_WNDPROC, (int32_t)bound_proc); object_windows_bind_window(window, object); return win16_call_window_proc(bound_proc, window, message, wparam, lparam); } /* * 1018:0002 — resolve a nonnegative ObjectWindows message id. * * The most-derived VMT points at a linked dynamic-message table. Every node * contains a parent pointer, ids, and far handler slots in the same compact * layout as Borland dynamic methods. Unlike 1020:16e6, a miss selects a caller- * supplied VMT fallback slot, and a parent hit is cached in the most-derived * table rather than in the node where it was found. */ Win16FarPtr object_windows_resolve_message_handler( uint16_t vmt, uint16_t message, uint16_t fallback_slot) { uint16_t root_table = win16_read_u16(win16_dgroup_pointer( (uint16_t)(vmt + VMT_MESSAGE_TABLE_OFFSET))); if (root_table == 0) { return win16_dgroup_pointer((uint16_t)(vmt + fallback_slot)); } Win16FarPtr root = win16_dgroup_pointer(root_table); if (message == win16_read_u16(win16_far_add_offset( root, MESSAGE_TABLE_CACHE_ID_OFFSET))) { uint16_t cached_slot = win16_read_u16(win16_far_add_offset( root, MESSAGE_TABLE_CACHE_SLOT_OFFSET)); return win16_dgroup_pointer(cached_slot); } uint16_t table = root_table; while (table != 0) { Win16FarPtr node = win16_dgroup_pointer(table); uint16_t count = win16_read_u16(win16_far_add_offset( node, MESSAGE_TABLE_COUNT_OFFSET)); for (uint16_t index = 0; index < count; index++) { uint16_t id = win16_read_u16(win16_far_add_offset( node, (uint16_t)(MESSAGE_TABLE_IDS_OFFSET + index * 2u))); if (id != message) { continue; } uint16_t slot = (uint16_t)( table + MESSAGE_TABLE_IDS_OFFSET + count * 2u + index * 4u); win16_write_u16( root, MESSAGE_TABLE_CACHE_ID_OFFSET, message); win16_write_u16( root, MESSAGE_TABLE_CACHE_SLOT_OFFSET, slot); return win16_dgroup_pointer(slot); } table = win16_read_u16(win16_far_add_offset( node, MESSAGE_TABLE_PARENT_OFFSET)); } return win16_dgroup_pointer((uint16_t)(vmt + fallback_slot)); } /* 1018:0103 — invoke the optional process-wide message instrumentation hook. */ void object_windows_call_message_hook( Win16FarPtr object, Win16FarPtr handler, const ObjectWindowsMessage *message) { win16_call_object_message_hook( g_object_windows_message_hook, object, handler, message->window, message->message, message->wparam, message->lparam); } /* * 1018:0133 — shared WNDPROC16/DLGPROC16 reached by every generated thunk. * The thunk supplies ES:BX = object. The Pascal Win16 argument order places * HWND at BP+0e and LPARAM at BP+06/+08; the explicit C signature restores the * logical API order while preserving all original field widths. */ LRESULT16 object_dispatch_window_or_dialog_proc( Win16FarPtr object, HWND16 window, UINT16 message, WPARAM16 wparam, LPARAM16 lparam) { win16_write_u16(object, OBJECT_WINDOW_HANDLE_OFFSET, window); ObjectWindowsMessage record = { .window = window, .message = message, .wparam = wparam, .lparam = lparam, .result = 1, }; uint16_t vmt = win16_read_u16(win16_far_add_offset( object, OBJECT_WINDOW_VMT_OFFSET)); Win16FarPtr handler_slot; if ((int16_t)message < 0) { handler_slot = win16_dgroup_pointer( (uint16_t)(vmt + VMT_DEFAULT_MESSAGE_SLOT)); } else { handler_slot = object_windows_resolve_message_handler( vmt, message, VMT_DEFAULT_MESSAGE_SLOT); } Win16FarPtr handler = win16_read_far_pointer(handler_slot, 0); if (win16_far_selector(g_object_windows_message_hook) != 0) { object_windows_call_message_hook(object, handler, &record); } win16_call_object_message_handler(handler, object, &record); return record.result; } /* * 1018:08c3 — generic ObjectWindows event dispatch used by command/focus/menu * paths. DX supplies the fallback VMT slot and AX the dynamic method id. The * existing far event record is passed unchanged to the selected handler. */ void object_windows_dispatch_event( uint16_t fallback_slot, uint16_t method_id, Win16FarPtr event, Win16FarPtr object) { uint16_t vmt = win16_read_u16(object); Win16FarPtr handler_slot = object_windows_resolve_message_handler( vmt, method_id, fallback_slot); Win16FarPtr handler = win16_read_far_pointer(handler_slot, 0); if (win16_far_selector(g_object_windows_message_hook) != 0) { ObjectWindowsMessage hook_record = { .window = win16_read_u16(event), .message = win16_read_u16(win16_far_add_offset(event, 2)), .wparam = win16_read_u16(win16_far_add_offset(event, 4)), .lparam = (LPARAM16)(uint32_t)( win16_read_u16(win16_far_add_offset(event, 6)) | ((uint32_t)win16_read_u16( win16_far_add_offset(event, 8)) << 16)), .result = 0, }; object_windows_call_message_hook(object, handler, &hook_record); } win16_call_object_event_handler(handler, object, event); } enum { BOUND_THUNK_POOL_BYTES = 0x0100, BOUND_THUNK_SHARED_CODE_OFFSET = 2, BOUND_THUNK_SHARED_CODE_BYTES = 5, BOUND_THUNK_DISPATCH_PROC_OFFSET = 7, BOUND_THUNK_FIRST_OFFSET = 0x000b, BOUND_THUNK_BYTES = 7, BOUND_THUNK_OBJECT_OFFSET = 3, DGROUP_BOUND_THUNK_TEMPLATE = 0x0686, }; static void write_far_pointer( Win16FarPtr destination, uint16_t byte_offset, Win16FarPtr value) { win16_write_u16(destination, byte_offset, win16_far_offset(value)); win16_write_u16( destination, (uint16_t)(byte_offset + 2), win16_far_selector(value)); } /* * Build one 256-byte executable block. Offset 2 contains the shared sequence * `POP BX; CS: LES BX,[BX]; JMP FAR dispatch_proc`. Each seven-byte thunk is * `CALL block:0002` followed by a mutable far object/free-list pointer. The * near displacement -1-offset makes every CALL land at block offset 2. */ static void allocate_bound_thunk_pool(void) { HGLOBAL16 allocation = GlobalAlloc16(0, BOUND_THUNK_POOL_BYTES); Win16FarPtr block = GlobalLock16(allocation); uint16_t selector = win16_far_selector(block); win16_write_u16(block, 0, g_object_windows_thunk_code_selector); borland_move_bytes( win16_far_add_offset(block, BOUND_THUNK_SHARED_CODE_OFFSET), win16_dgroup_pointer(DGROUP_BOUND_THUNK_TEMPLATE), BOUND_THUNK_SHARED_CODE_BYTES); write_far_pointer( block, BOUND_THUNK_DISPATCH_PROC_OFFSET, g_object_windows_dispatch_proc); uint16_t offset = BOUND_THUNK_FIRST_OFFSET; do { Win16FarPtr thunk = win16_make_far_pointer(selector, offset); win16_write_u8(thunk, 0xe8); win16_write_u16( thunk, 1, (uint16_t)(0xffffu - offset)); write_far_pointer( thunk, BOUND_THUNK_OBJECT_OFFSET, g_object_windows_free_thunk); g_object_windows_free_thunk = thunk; offset = (uint16_t)(offset + BOUND_THUNK_BYTES); } while (offset != BOUND_THUNK_POOL_BYTES); g_object_windows_thunk_code_selector = selector; (void)PrestoChangoSelector16(selector, selector); } /* * 1018:01dc — pop a generated WNDPROC thunk and bind its embedded far object. * Executable selectors are modified only through a temporary CS-to-DS alias. */ Win16FarPtr object_windows_allocate_bound_thunk(Win16FarPtr object) { if (g_object_windows_free_thunk == 0) { allocate_bound_thunk_pool(); } Win16FarPtr thunk = g_object_windows_free_thunk; uint16_t alias = AllocCStoDSAlias16(win16_far_selector(thunk)); Win16FarPtr writable = win16_make_far_pointer( alias, win16_far_offset(thunk)); g_object_windows_free_thunk = win16_read_far_pointer(writable, BOUND_THUNK_OBJECT_OFFSET); write_far_pointer(writable, BOUND_THUNK_OBJECT_OFFSET, object); (void)FreeSelector16(alias); return thunk; } /* 1018:02fa — push one bound WNDPROC thunk back onto the global free list. */ void object_windows_release_bound_thunk(Win16FarPtr thunk) { uint16_t alias = AllocCStoDSAlias16(win16_far_selector(thunk)); Win16FarPtr writable = win16_make_far_pointer( alias, win16_far_offset(thunk)); write_far_pointer( writable, BOUND_THUNK_OBJECT_OFFSET, g_object_windows_free_thunk); (void)FreeSelector16(alias); g_object_windows_free_thunk = thunk; }