VOGONS

Common searches


First post, by Peter Swinkels

User metadata
Rank Oldbie
Rank
Oldbie

I am trying to use int 21h, 4bh to launch programs from a Quick Basic program. It only works from inside a compiled program for me. When executed from the run-time environment the function always claims there isn't enough memory (ax = 8h). I know for a fact that in most cases this can't be true, because when I use the SHELL statement instead it usually works fine.

My code:

DEFINT A-Z

TYPE RegTypeX
ax AS INTEGER
bx AS INTEGER
cx AS INTEGER
dx AS INTEGER
bp AS INTEGER
si AS INTEGER
di AS INTEGER
flags AS INTEGER
ds AS INTEGER
es AS INTEGER
END TYPE

DECLARE SUB INTERRUPTX (intnum AS INTEGER, inreg AS RegTypeX, outreg AS RegTypeX)

DIM Program AS STRING
DIM Registers AS RegTypeX

CLS
Program = "z:\mem.com" + CHR$(0)

Registers.ax = &H4B00
Registers.ds = VARSEG(Program)
Registers.dx = SADD(Program)
INTERRUPTX &H21, Registers, Registers
PRINT "Return code: "; Registers.ax

Does anyone know what I am missing in order to get it to work consistently?

EDIT:
Just to be clear: I did remember to load the qb.qlb library before trying to call INTERRUPTX.

Last edited by Peter Swinkels on 2023-07-05, 14:59. Edited 2 times in total.

Do not read if you don't like attention seeking self-advertisements!

Did you read it anyway? Well, you can find all sorts of stuff I made using various programming languages over here:
https://github.com/peterswinkels

Reply 1 of 39, by Jo22

User metadata
Rank l33t++
Rank
l33t++

Hi there. Did you try QB /L already?

Edit: Or QB /L QB.QLB or QB /L QB.LIB ?
Edit: Just checked. *.QLB seems to be for the IDE, *.LIB for the compiler.
Some programs also need '$INCLUDE:'QB.BI' in the header section.
See http://petesqbsite.com/sections/tutorials/tut … i_mousetut.html

"Time, it seems, doesn't flow. For some it's fast, for some it's slow.
In what to one race is no time at all, another race can rise and fall..." - The Minstrel

//My video channel//

Reply 2 of 39, by Peter Swinkels

User metadata
Rank Oldbie
Rank
Oldbie

Yes, I had the library loaded, if I hadn't I wouldn't be able to compile my program to confirm it works when compiled. Also, I am getting an "out of memory" return code in the AX register which means the function is being succesfully called but fails. If I had forgotten to load the qb.qlb library I would get a "subprogram not defined" error message, which I don't.

Do not read if you don't like attention seeking self-advertisements!

Did you read it anyway? Well, you can find all sorts of stuff I made using various programming languages over here:
https://github.com/peterswinkels

Reply 3 of 39, by Jo22

User metadata
Rank l33t++
Rank
l33t++

Hm. Beats me, I don't see any mistakes so far.
Hm. I'll try to replicate this issue on my PC. I'll write back soon.

"Time, it seems, doesn't flow. For some it's fast, for some it's slow.
In what to one race is no time at all, another race can rise and fall..." - The Minstrel

//My video channel//

Reply 4 of 39, by Jo22

User metadata
Rank l33t++
Rank
l33t++

Hm. Update. Are you sure the code works that way ? 🤷
The code works with QB45 in DOSBox on command prompt, but I get weird results from within Norton Commander. MS-DOS Shell works, but uses swapping mechanism.
It also doesn't work at all in my MS-DOS 6.22 VM - maybe due to QEMM and all the drivers. I used SUBST, so no alteration of the code was needed.

Edit: I've also tried Power Basic and QBX. Power Basic complains about syntax, QBX gives back an return code all the time (both IDE and compiled EXE).

Attachments

  • dosswap.png
    Filename
    dosswap.png
    File size
    5.5 KiB
    Views
    1318 views
    File comment
    Run from within MS-DOS Shell 6
    File license
    Fair use/fair dealing exception
  • nc.png
    Filename
    nc.png
    File size
    3.52 KiB
    Views
    1318 views
    File comment
    Run from within NC.EXE (no NCSMALL loader, NC 2)
    File license
    Fair use/fair dealing exception
  • dos622_vpc2007.png
    Filename
    dos622_vpc2007.png
    File size
    2.84 KiB
    Views
    1318 views
    File comment
    MS-DOS 6.22 VM, prompt
    File license
    Fair use/fair dealing exception

"Time, it seems, doesn't flow. For some it's fast, for some it's slow.
In what to one race is no time at all, another race can rise and fall..." - The Minstrel

//My video channel//

Reply 5 of 39, by pan069

User metadata
Rank Oldbie
Rank
Oldbie

I tried with QB45 and I get the same results. It's the same behaviour when run from within the IDE and compiled as a standalone EXE...

Then I figured, let's test if it works directly from Assembler and initially I got the same result. Insufficient Memory (AX = 08h).

But then I remembered that an EXE by default with allocate all available memory to itself unless at the start of your program you resize the memory used by the EXE to what it actually needs. Another option is to modify the PSP (first 256 bytes of the EXE to indicate the required/minimum memory size) but you need some utility to change the PSP.

When I add the memory init/resize to my assembler version, it works as expected:

.model small
.stack

.data

program db "c:\dos\mem.exe",0
params db "",0

.code

start:
; - init/resize memory required by exe.
mov bx,ss
mov ax,es
sub bx,ax
mov ax,sp
mov cl,4
shr ax,cl
add bx,ax
inc bx
mov ah,4ah
int 21h

;- execute external exe
mov ax,@data
mov ds,ax
mov dx,offset program
mov es,ax
mov bx,offset params
mov ah,4bh
mov al,00h
int 21h

mov ax,4c00h
int 21h

end start

Compile instructions:

tasm prog.asm
tlink prog.obj

Not sure if this is useful in any way or how to accomplish this in QB as I assume that a QB program also allocates the full heap by default.

Reply 6 of 39, by doshea

User metadata
Rank Member
Rank
Member

https://github.com/geneb/PDQ/blob/master/PDQ/MAKEPDQ.BAS includes a FUNCTION LDEXEC which appears to attempt something similar. I have no idea if it works any better within the IDE, but perhaps it's worth a try. It seems to rely on the PDQ library which that is the repository for the source of; you might prefer to just grab the library from the link in the top-level readme.md.

I don't see any sign of it doing anything to free memory though, so maybe that's useless.

I wonder if the SETMEM function would be helpful?

The SETMEM function facilitates mixed-language programming by allowing you to decrease the amount of dynamic memory allocated by […]
Show full quote

The SETMEM function facilitates mixed-language
programming by allowing you to decrease the
amount of dynamic memory allocated by BASIC so
it can be used by procedures in other languages.

https://msarchive.pcjs.org/kb/Q31308/ seems to have some more information, and it sounds like it does release memory back to DOS, which would seem to be what you need. The "procedures in other languages" phrase almost makes it sound like the memory may remain allocated to QB but QB internally allows some other libraries which have been loaded to use the memory, but it seems like they didn't bother to document it very well, and hopefully the KB article is more useful (I didn't read it fully)!

Reply 7 of 39, by pan069

User metadata
Rank Oldbie
Rank
Oldbie
doshea wrote on 2023-07-06, 11:23:

https://msarchive.pcjs.org/kb/Q31308/ seems to have some more information, and it sounds like it does release memory back to DOS, which would seem to be what you need. The "procedures in other languages" phrase almost makes it sound like the memory may remain allocated to QB but QB internally allows some other libraries which have been loaded to use the memory, but it seems like they didn't bother to document it very well, and hopefully the KB article is more useful (I didn't read it fully)!

Interesting find. Some more hacking to do... 😀

From that article:

2. The EXEMOD.EXE utility provided with the Microsoft Macro Assembler allows you to modify the header of an .EXE to shorten t […]
Show full quote

2. The EXEMOD.EXE utility provided with the Microsoft Macro Assembler
allows you to modify the header of an .EXE to shorten the maximum
upper load address of a program. By default, BASIC .EXE programs
assume that all of RAM is available. If you make the load address
smaller, you must make sure that there is enough room for the
BASIC program's code and data. Microsoft does not encourage using
EXEMOD with compiled BASIC programs. The SETMEM function should be
used instead.

That is the utility I was referring to, it modifies the header of the EXE (i.e. the PSP, Program Segment Prefix [1]).

[1] https://stanislavs.org/helppc/program_segment_prefix.html

Reply 8 of 39, by pan069

User metadata
Rank Oldbie
Rank
Oldbie

Okay, I have some success.

Use the SETMEM as pointed out by doshea. So, e.g. something like this:

CLS
PRINT SETMEM(-65535)
.
.
.

Also. use a different EXE than MEM.EXE as I think that MEM.EXE does some specific memory stuff. Use C:\DOS\DEBUG.EXE instead. This works for me from within the QB IDE.

Reply 9 of 39, by Peter Swinkels

User metadata
Rank Oldbie
Rank
Oldbie

Weird, using SETMEM doesn't work at all for me. And it doesn't matter what I try to run, it always fails from inside Quick Basic's IDE.

Also, why does using the SHELL statement work?

Do not read if you don't like attention seeking self-advertisements!

Did you read it anyway? Well, you can find all sorts of stuff I made using various programming languages over here:
https://github.com/peterswinkels

Reply 10 of 39, by Jo22

User metadata
Rank l33t++
Rank
l33t++
Peter Swinkels wrote on 2023-07-06, 15:05:

Also, why does using the SHELL statement work?

I believe it passes things to the command line interpreter, usually command.com.
Int21h is deeper within DOS.

"Time, it seems, doesn't flow. For some it's fast, for some it's slow.
In what to one race is no time at all, another race can rise and fall..." - The Minstrel

//My video channel//

Reply 11 of 39, by pan069

User metadata
Rank Oldbie
Rank
Oldbie
Peter Swinkels wrote on 2023-07-06, 15:05:

Weird, using SETMEM doesn't work at all for me. And it doesn't matter what I try to run, it always fails from inside Quick Basic's IDE.

It fails with the same error code 8 (Insufficient Memory) or with code 2 (File Not Found)?

Did you try to execute another EXE, or are you still trying to execute MEM.EXE? I think there might be something special about MEM.EXE in regards to memory so it might not be a great candidate to execute from QB, although it works fine with the assembler example I gave earlier. QB has a much larger memory footprint, so there might be a conflict there.

Peter Swinkels wrote on 2023-07-06, 15:05:

Also, why does using the SHELL statement work?

Doing a SHELL in the IDE returns control back to DOS and therefore the memory allocated by the IDE. DOS controls the PSPs allocated by various programs. When you exit out of the SHELL back into the IDE, DOS and probably the IDE itself will reallocate it's required memory.

Reply 12 of 39, by doshea

User metadata
Rank Member
Rank
Member
pan069 wrote on 2023-07-06, 20:15:

Doing a SHELL in the IDE returns control back to DOS and therefore the memory allocated by the IDE. DOS controls the PSPs allocated by various programs. When you exit out of the SHELL back into the IDE, DOS and probably the IDE itself will reallocate it's required memory.

I think I'd put it this way: if you use SHELL, QB knows you want to run another program which needs some free memory, so it frees some memory, then probably allocates it again when the program returns, but if you invoke some arbitrary interrupt, it has no idea what you're doing, so you have to tell it to free the memory yourself.

It concerns me that OP's code isn't setting ES:BX to a pointer to one of these parameter blocks:

Table 01590
Format of EXEC parameter block for AL=00h,01h,04h:
00h WORD segment of environment to copy for child process (copy caller's
environment if 0000h)
02h DWORD pointer to command tail to be copied into child's PSP
06h DWORD pointer to first FCB to be copied into child's PSP
0Ah DWORD pointer to second FCB to be copied into child's PSP
0Eh DWORD (AL=01h) will hold subprogram's initial SS:SP on return
12h DWORD (AL=01h) will hold entry point (CS:IP) on return
SeeAlso: *Note 01591::,*Note 01592::

The code I linked to in PDQ does set ES:BX, and pan069's code above does too.

I had a bit of play with OP's code with pan069's suggested addition, and with MS-DOS 6.22 running under Bochs, I could get it to run COMMAND.COM instead of Z:\MEM.COM and then exit back to the IDE the first time, but if I tried a second time it'd crash. Although COMMAND ran, its environment was messed up, probably because ES:BX wasn't set:

C>set
PATH=
COMSPEC=>:\COMMAND.COM

I tried MEM.EXE and it gave garbage output.

Reply 13 of 39, by doshea

User metadata
Rank Member
Rank
Member

In case anyone is interested, PDQ contains this too:

NEW SHELL ROUTINE SWAPS THE PROGRAM TO DISK =========================================== […]
Show full quote

NEW SHELL ROUTINE SWAPS THE PROGRAM TO DISK
===========================================

Shell2Disk is a new function that lets you execute a program similar to the
way SHELL works, but with two unique advantages: The main program is swapped
to disk during the shell, leaving only a small (less than 2400 bytes) kernel
resident. Also, the shelled program's DOS errorlevel return code is returned
to the calling program. A complete explanation is given in the SHELDISK.BAS
demonstration program, along with instructions for executing batch files and
DOS commands, and also a note for VB/DOS users.

The note for Visual Basic for DOS users just says you can't pass string literals.

PDQ and other associated libraries were formerly commercial or shareware, but were open-sourced (actually public domain I think) about 5 years ago.

Reply 14 of 39, by Peter Swinkels

User metadata
Rank Oldbie
Rank
Oldbie

I wasn't aware ES:BX always needed to be set. Can anyone explain what it needs to be set to?

EDIT:
As far as I remember I always get error code 8 when a program fails to be launched.

Last edited by Peter Swinkels on 2023-07-08, 07:05. Edited 1 time in total.

Do not read if you don't like attention seeking self-advertisements!

Did you read it anyway? Well, you can find all sorts of stuff I made using various programming languages over here:
https://github.com/peterswinkels

Reply 15 of 39, by pan069

User metadata
Rank Oldbie
Rank
Oldbie
Peter Swinkels wrote on 2023-07-07, 18:19:

I wasn't aware ES:BX always needed to be set. Can any explain what it needs to be set to?

ES:BX point to a string that contain the parameters that should be passed to the program being executed. In my Assembler example I just made it an empty string. So, something like:

Program = "z:\mem.com" + CHR$(0)
Params = "" + CHR$(0)

Registers.ax = &H4B00
Registers.ds = VARSEG(Program)
Registers.dx = SADD(Program)
Registers.es = VARSEG(Params)
Registers.bx = SADD(Params)
INTERRUPTX &H21, Registers, Registers

If you don't do this, then ES:BX will point to some random memory location and INT 21h/4Bh will think that that random memory are the parameters required by the program you're trying to execute.

PS: I might tinker with this a bit more over the weekend if I can find the time, energy and a good idea or two to try out... 😀

Reply 16 of 39, by doshea

User metadata
Rank Member
Rank
Member
pan069 wrote on 2023-07-07, 21:26:

ES:BX point to a string that contain the parameters that should be passed to the program being executed. In my Assembler example I just made it an empty string. So, something like:

I'm not sure about that, http://www.delorie.com/djgpp/doc/rbinter/id/51/29.html says ES:BX should point to a whole parameter block of things as I quoted above, not just the command line parameters.

https://github.com/geneb/PDQ/blob/master/PDQ/ … AKEPDQ.BAS#L896 does this:

First it makes the command tail (the command-line parameters), which I presume is meant to start with a length byte:

K = LEN(PARAMETER$)                     'Get length of command string
PARAMETER$ = CHR$(K) + PARAMETER$ + CHR$(13)'Prepare parameter string for processing

Then it initialises a parameter block with the "segment of environment to copy for child process" set to 0000h ("copy caller's environment"):

C$ = CHR$(0) + CHR$(0) + "            " 'This is the parm block for this int

Then it fills in the "pointer to command tail to be copied into child's PSP" DWORD at offset 02h in the parameter block:

K1 = VARSEG(PARAMETER$)                 'Get segment of parm string
K2 = SADD(PARAMETER$) 'Get offset of parm string
DEF SEG = VARSEG(C$) 'Set seg of parm block
K = SADD(C$) 'Get offset of parm block
PDQPOKE2 K + 2, K2 'Put offset into parm block, bytes 3/4
PDQPOKE2 K + 4, K1 'Put segment into parm block, bytes 5/6
DEF SEG 'Restore basic segment

(the PDQ manual says "PDQPeek2 and PDQPoke2 let your programs PEEK and POKE words (two bytes) in one operation, resulting in much less code than using BASIC commands alone.")

Then it sets registers to point to the variables:

REGS.DX = SADD(FILENAME$)               'Offset of pgm name into DX
REGS.ES = VARSEG(C$) 'Segment of parm block into ES
REGS.BX = SADD(C$) 'Offset of parm block into BX

Reply 17 of 39, by pan069

User metadata
Rank Oldbie
Rank
Oldbie

Good catch! I was just quickly scanning this source [1] about this interrupt, which clearly says "block" but I must have read it as "string". Thanks for pointing it out.

[1] https://stanislavs.org/helppc/int_21-4b.html

Reply 18 of 39, by pan069

User metadata
Rank Oldbie
Rank
Oldbie

Okay. I did a bit more tinkering. You can actually set ES:BX to zero which makes INT 21h/4Dh ignore the parameter block entirely (it seems).

This is the full source I use to execute C:\DOS\MEM.EXE which seems to work fine with ES:BX set to zero:

DEFINT A-Z

TYPE RegTypeX
ax AS INTEGER
bx AS INTEGER
cx AS INTEGER
dx AS INTEGER
bp AS INTEGER
si AS INTEGER
di AS INTEGER
flags AS INTEGER
ds AS INTEGER
es AS INTEGER
END TYPE

DECLARE SUB INTERRUPTX (intnum AS INTEGER, intreg AS RegTypeX, outreg AS RegTypeX)

DIM Program AS STRING
DIM Regs AS RegTypeX

CLS

Program$ = "c:\dos\mem.exe" + CHR$(0)

Regs.ax = &H4B00
Regs.ds = VARSEG(Program$)
Regs.dx = SADD(Program$)
Regs.es = 0
Regs.bx = 0
INTERRUPTX &H21, Regs, Regs
'PRINT Regs.ax

If you need to pass parameters then I strongly suggest to follow doshea instructions and sources he has pointed out.

Reply 19 of 39, by doshea

User metadata
Rank Member
Rank
Member
pan069 wrote on 2023-07-08, 03:45:

Okay. I did a bit more tinkering. You can actually set ES:BX to zero which makes INT 21h/4Dh ignore the parameter block entirely (it seems).

Maybe it just results in the interrupt vector table being used as the parameter block? 😁

I checked the MS-DOS Programmer's Reference manuals for versions 2.0 and 6.0 and neither of them said you can set it to zero, so I would be hesitant to rely on this. I also had a look at the FreeDOS kernel source code, and I may have been looking in the wrong place (I looked at the DosExec() function and its call in int21_service()), but it seemed to me that it didn't check the pointer before copying from it into a temporary buffer, so it looks like it just assumes it's valid, and hence this presumably isn't portable to FreeDOS, except if the interrupt vector table (i.e. what is at 0000:0000) has values in it that make everything happy enough.

Thanks for trying this stuff out, I didn't have time to do all the work!