Skip to main content

Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
TagsGame Engines

What's the Watcom syntax for a custom interrupt, in a COM file?

A topic by Mindbleach created Oct 02, 2023 Views: 165 Replies: 4
Viewing posts 1 to 2
Submitted

I have a fully-working DOS game, in C. Open Watcom (version does not appear to matter) will take "void __interrupt __far play_music()" and "_dos_setvect( 0x1C, play_music )" and then call that function several times per second... for an EXE. Setting "-bcl=com" in the Makefile, or doing "wlink system com" manually, will instead crash the first time the function is called.

The linker does throw several "segment relocation" warnings, but Open Watcom's error documentation just says segment relocation is not allowed in the COM format. There is no indication of what does that, or how to avoid it.

Here is an incomplete list of things that don't solve the problem:

Prototyping "void __interrupt play_music()" has no effect whatsoever. 

Prototyping "void play_music()"  sees _dos_setvect throw type errors. Casting is either impossible, or locked behind a syntax different from the dozen ways I have tried coercing the one it friggin' asked for. 

Laundering play_music or &play_music through a "void interrupt (*old_interrupt)(void) = NULL" pointer has no effect, even though "old_interrupt = _dos_getvect( 0x1C )" and "_dos_setvect( 0x1C, old_interrupt )" work fine.

"_chain_intr( old_interrupt )" from play_music makes no difference. It's not an issue of being 0x1C. The actual function is never called. 

Manually writing addresses to the interrupt vector list (page zero) also never calls the function, even if it's just "void play_music()".  It's possible I'm casting the address wrong, but I assure you, it is not for lack of trying.

I could just check time() more often and call the function from the main loop, but then it sounds like garbage on anything slower than a 386, when it should run fine on an 8088. That's why it's in the name.

I have exhausted my patience for convincing search engines that I do in fact mean MS-DOS and Open Watcom, instead of whatever nonsense they'd like to substitute that has so many more results I didn't ask for. I have gone half-mad plowing through archaic technical documents that are always the worst combination of hand-wavy and inscrutable. I have vicious criticism for every website about those documents which are somehow even more archaic and fanatically devoted to making Ctrl+F useless.

I give up. What combination of magic words will let a one-segment program call a goddamn function so I can make with the bleep-bloops?

Submitted (3 edits)

I had the same problem. Nuclear on Discord linked me to his timer code: https://github.com/jtsiomb/termtris/blob/master/src/dos/timer.asm

So in short, it doesn't seem to be possible (without hacking the OBJ) to make an interrupt function in C using Open Watcom when linking a COM.

Submitted

Aggravating. CC65 has similar forced diversions into 6502 ASM, but CC65 is a glorified assembler macro. (Which does make it lightning-fast.) Watcom is old enough that this was relevant and Open Watcom is active enough that bugs still get fixed, and here it is tripping over itself. 

... nevermind, I solved it.

INT 21h will accept a normal function name in DX, for AX=0x251C (set interrupt vector 0x1C). The function will crash. But you can set it, and it will run. Once. Even _asm{ iret } won't return properly. The problem is, interrupts only push / pop the bare minimum of state. A function takes more setup and leaves a different stack.

But you can pad the function with _asm{ nop nop nop ... } and jump somewhere in the middle of those. Eight seems to work fine and I have no reason to push for fewer. The bare minimum presumably differs with the complexity of the function itself. So in the interest of doing things as sensibly as this jank allows - I wrote a wrapper function that only calls play_music normally and then does _asm{ iret }.

TLDR:

AX = 0x251C, DX = your_function_wrapper, DX += 8, INT 21h / int86 0x21. DS probably has to be zero as well.

void your_function_wrapper() {

_asm{ nop (8x, each one goes on a new line) }

your_function(); 

_asm{ iret }

}

Submitted

Naturally this doesn't work in 86box. It runs - but it's audibly not running correctly. Back to tweaking and guesswork.

Submitted

Found another way to do it, with one block of inline ASM tricking the processor into giving up the Instruction Pointer (because apparently Intel thinks it's supposed to be a secret!) and pointing earlier in the block of ASM to some custom code that definitely has no function / subroutine / procedure hang-ups. 86box don't care. 86box just does not like my sound function... when it is called by a COM file. An EXE works fine. So, screw it, this compo's getting a COM version specific to DosBox-X.