• Please review our updated Terms and Rules here

Creating your own QBASIC programs and using them within a BBS

maelstrm

Experienced Member
Joined
Jun 6, 2022
Messages
98
Location
Raleigh, NC
Hello Federation,

I'm learning about BBSes (and I even have one online). I want to know if it's possible to create QBASIC applications and have them run interactively as part of my BBS system.

I have:
  • Waffle BBS
  • A Lantronix UDS10
  • A working BBS accessible via telnet


I've already worked out how to call EXE's and batch files using the BBS software. However, programs written in QBASIC do not work correctly over telnet (though they do work correctly when called from the BBS if you are in front of the PC). It's as if the output needs to be told to redirect back to the serial port. This "works" (aka works across telnet too) if I have a qbasic program with a simple "Hello World" that redirects to COM1, like this:

C:\myprogs\prog1.exe > COM1

So I know in theory it should work. But redirecting with ">" isn't going to help with getting input from a user. What I think I need to be able to do is interact directly with COM1 inside of a QBASIC program.

My first attempts were something like this, but of course did not work across a telnet session:

INPUT "What is your name"; name$

So I tried interacting with the COM port, but still can't seem to get it. Here's my current code:

PRINT "HELLO WORLD"
PRINT "NAME?"
REM Open an input stream with COM1, name it #1:
OPEN "COM1:9600,N,8,1,CD0,CS0,DS0,OP0,RS,TB2048,RB2048" FOR INPUT AS #1
REM Capture the input from #1 and store in the variable name$
INPUT #1, name$
REM Terminate the stream from #1
CLOSE #1
REM Open an output stream with COM1, name it #2:
OPEN "COM1:9600,N,8,1,CD0,CS0,DS0,OP0,RS,TB2048,RB2048" FOR OUTPUT AS #2
REM Print the name$ variable to stream 2:
PRINT #2, name$
When I don't redirect this with >, I don't see any text come across the telnet session, not even the first two PRINT statements (though it does appear on the local screen/console). The telnet user can't see any of the QBASIC program, nor are they able to enter anything. The console user is also unable to enter anything. The system effectively is unable to process any more keyboard input until after a reboot.

Note: 9600,N,8,1 are the correct parameters for the serial port on the Lantronix UDS10.

I think this is something simple, just can't figure it out. Any help would be appreciated!
 
Right...so I tried this, essentially the same but with longer delay times and smaller T/R buffers:
OPEN "COM1:9600,N,8,1,CD50,CS50,DS50,OP50,RS,TB256,RB256" FOR OUTPUT AS #1
PRINT #1, "HELLO WORLD"
PRINT #1, "NAME?"
CLOSE #1
REM OPEN "COM1:9600,N,8,1,CD0,CS0,DS0,OP0,RS,TB2048,RB2048" FOR OUTPUT AS #2
REM INPUT #2, name$
REM CLOSE #2
END
And I get the first 'H' across telnet, and nothing else. No further input possible form the telnet side. Console side could not break out of the BBS software, only CTRL+ALT+DEL would work.

I also changed the first line to this:
OPEN "COM1:9600,N,8,1" FOR OUTPUT AS #1
And got the same result, only the first 'H' across telnet.
 
I believe that OPEN COM can be done in RANDOM mode so it won't be necessary to close the serial port and then change it from OUTPUT to INPUT. That doesn't fix the problem here but will make the overall code simpler.

I must be forgetting something obvious because the PRINT #1 lines look like they should work correctly.

On the INPUT side, the sample comm software for QBASIC uses LOC and INPUT$ in a loop grabbing each character as it comes in.
 
Ok, gonna try it with random. At least the code will be cleaner.

Verified that MODE.COM parameters are set correctly. It's so odd that DOS's "> COM1" works but not the exe. I even called TEST.EXE from a batch file and got the same result. Only the "H" appears from 'HELLO, WORLD". Then no further keyboard input is possible from telnet or console.
 
Progress. All PRINT statements are happening now. But I'm not able to capture any keyboard output. The program just runs on through as if I'm laying on the Enter key. Sleep 10 is useful, and the sleep is occurring over Telnet too. This may help in troubleshooting. It's also passing the arg command$ correctly. So yay (so far). \o/

OPEN "COM1" FOR RANDOM AS #1
PRINT #1, "HELLO WORLD!"
PRINT #1, "NAME?"
PRINT #1, "SLEEPING 10 SECONDS..."
SLEEP 10
INPUT #1, n$
PRINT #1, n$
PRINT #1, command$ //I'm passing in the username of the signed in BBS user. This line is working correctly.
CLOSE #1
END
Things I might try next:
  • Doubling up on the INPUT #1, n$ statement to "capture" any gratuitous \n or LF's, thus (potentially) allowing something to be typed in at a prompt.
  • Going back to using INPUT and OUTPUT as the mode instead of RANDOM.
Any thoughts would be appreciated!!
 
The example in the QuickBASIC manual uses the following in a loop for received characters.

Code:
IF NOT EOF(1) THEN
' L0C(1) gives the number of characters waiting:
ModemInput$ = INPUT$(LOC(1), #1)
Filter ModemInput$ ' Filter out line feeds and
PRINT ModemInput$; ' backspaces, then print.
END IF

and
Code:
SUB Filter (InStringS) STATIC
' Look for backspace characters and recode
' them to CHR$(29) (the LEFT cursor key):
DO
Backspace = INSTR(InStringS, CHR$(8))
IF Backspace THEN
MIDS (InStringS, Backspace) = CHRS(29)
END IF
LOOP WHILE Backspace
' Look for line-feed characters and
' remove any found:
DO
LnFd = INSTR(InStringS, CHRS (10))
IF LnFd THEN
InString$=LEFT$(InStringS,LnFd-1)+MIDS(InStringS,LnFd+1)
END IF
LOOP WHILE LnFd
END SUB

Note that some prefer a slightly different loop that uses INPUT$(1,1) to grab one character at a time from the input buffer. LOC(1) would be greater than 0 whenever there are characters waiting in the buffer.
 
AFAIK QB doesn't have a built-in method for reading from stdin. But you can achieve this with interrupts:

Code:
DEFINT A-Z
'$INCLUDE: 'qb.bi'

DECLARE SUB stdout (s$)
DECLARE FUNCTION stdin$ ()

stdout "What is your name? "
n$ = stdin$
stdout "Hello, " + n$ + CHR$(13)

FUNCTION stdin$

  DIM regs AS RegType

  DO
    regs.ax = &H100
    Interrupt &H21, regs, regs
  
    c$ = CHR$(regs.ax AND &HFF)
    IF c$ = CHR$(13) THEN EXIT DO
    s$ = s$ + c$
 
  LOOP

  stdin$ = s$

END FUNCTION

SUB stdout (s$)

  DIM regs AS RegType

  FOR i = 1 TO LEN(s$)
    regs.ax = &H200
    regs.dx = ASC(MID$(s$, i, 1))
    Interrupt &H21, regs, regs
  NEXT

END SUB

If you compile this, then you should be able to run:

Code:
ctty com1
program.exe
 
When the BBS software shells out to another program such as your QBASIC or any other program, does it still have the serial port open or otherwise attempt to interact with it? Such as an IRQ 4 interrupt handler? If so it seems like any attempt to go around it and mess with the serial port behind its back is going to be fraught with problems.

Plasma's approach of using stdio instead would avoid that so long as the BBS software has it redirected.
 
The example in the QuickBASIC manual uses the following in a loop for received characters.

Code:
IF NOT EOF(1) THEN
' L0C(1) gives the number of characters waiting:
ModemInput$ = INPUT$(LOC(1), #1)
Filter ModemInput$ ' Filter out line feeds and
PRINT ModemInput$; ' backspaces, then print.
END IF

and
Code:
SUB Filter (InStringS) STATIC
' Look for backspace characters and recode
' them to CHR$(29) (the LEFT cursor key):
DO
Backspace = INSTR(InStringS, CHR$(8))
IF Backspace THEN
MIDS (InStringS, Backspace) = CHRS(29)
END IF
LOOP WHILE Backspace
' Look for line-feed characters and
' remove any found:
DO
LnFd = INSTR(InStringS, CHRS (10))
IF LnFd THEN
InString$=LEFT$(InStringS,LnFd-1)+MIDS(InStringS,LnFd+1)
END IF
LOOP WHILE LnFd
END SUB

Note that some prefer a slightly different loop that uses INPUT$(1,1) to grab one character at a time from the input buffer. LOC(1) would be greater than 0 whenever there are characters waiting in the buffer.
I tried this, but I'm getting a type mismatch error ("Parameter type mismatch") in QBASIC. I tried with both INPUT$(LOC(1), #1) and INPUT$(1,1). Tried changing the parameter name in the SUB declaration (InStringS to MyString) and even tried "pre-setting" ModemInput$ to a string (" ", and "Working Title") just before the line ModemInput$ = INPUT$(LOC(1), #1). No dice.
 
AFAIK QB doesn't have a built-in method for reading from stdin. But you can achieve this with interrupts:

Code:
DEFINT A-Z
'$INCLUDE: 'qb.bi'

DECLARE SUB stdout (s$)
DECLARE FUNCTION stdin$ ()

stdout "What is your name? "
n$ = stdin$
stdout "Hello, " + n$ + CHR$(13)

FUNCTION stdin$

  DIM regs AS RegType

  DO
    regs.ax = &H100
    Interrupt &H21, regs, regs
 
    c$ = CHR$(regs.ax AND &HFF)
    IF c$ = CHR$(13) THEN EXIT DO
    s$ = s$ + c$
 
  LOOP

  stdin$ = s$

END FUNCTION

SUB stdout (s$)

  DIM regs AS RegType

  FOR i = 1 TO LEN(s$)
    regs.ax = &H200
    regs.dx = ASC(MID$(s$, i, 1))
    Interrupt &H21, regs, regs
  NEXT

END SUB

If you compile this, then you should be able to run:

Code:
ctty com1
program.exe
This is something I will try if I can't get the current code to work.
 
When the BBS software shells out to another program such as your QBASIC or any other program, does it still have the serial port open or otherwise attempt to interact with it? Such as an IRQ 4 interrupt handler? If so it seems like any attempt to go around it and mess with the serial port behind its back is going to be fraught with problems.

Plasma's approach of using stdio instead would avoid that so long as the BBS software has it redirected.
You're right in that the BBS software may interfere. But I'm making the assumption that it's able to work because the docs do mention that the software allows for "external programs" to run. There's no specific documentation on whether those external programs are going to be able to use the COM port though. So this may be a fool's errand. But the fact that I can make a QBASIC EXE with PRINT statements that can travel across the serial port and over telnet gives me hope!
 
Another proof of concept. I'm able to use a simple text-driven menu in BATCH using CHOICE.COM over Telnet. I can move around the menu at will, and the text from the menu appears across Telnet.

So this strongly suggests the COM port is available bi-directionally while the BBS software is running. But at this time I can't get it to work with QBASIC executables.

Thank you so much. Any guidance is GREATLY appreciated!
 
Did you try using stdin/stdout? Like Makefile said, if your BBS software is already redirecting stdio to the COM port, you will have issues trying to use the port directly.
 
Did you try using stdin/stdout? Like Makefile said, if your BBS software is already redirecting stdio to the COM port, you will have issues trying to use the port directly.
No not yet. I'm able to fully interact using BATCH. I'll give it a go.
 
When the BBS software shells out to another program such as your QBASIC or any other program, does it still have the serial port open or otherwise attempt to interact with it? Such as an IRQ 4 interrupt handler? If so it seems like any attempt to go around it and mess with the serial port behind its back is going to be fraught with problems.

Plasma's approach of using stdio instead would avoid that so long as the BBS software has it redirected.
The convention used to be a "dropfile" such as DOOR.SYS that would tell the 3rd party app about the user, com port etc.
 
AFAIK QB doesn't have a built-in method for reading from stdin. But you can achieve this with interrupts:

Code:
DEFINT A-Z
'$INCLUDE: 'qb.bi'

DECLARE SUB stdout (s$)
DECLARE FUNCTION stdin$ ()

stdout "What is your name? "
n$ = stdin$
stdout "Hello, " + n$ + CHR$(13)

FUNCTION stdin$

  DIM regs AS RegType

  DO
    regs.ax = &H100
    Interrupt &H21, regs, regs
 
    c$ = CHR$(regs.ax AND &HFF)
    IF c$ = CHR$(13) THEN EXIT DO
    s$ = s$ + c$
 
  LOOP

  stdin$ = s$

END FUNCTION

SUB stdout (s$)

  DIM regs AS RegType

  FOR i = 1 TO LEN(s$)
    regs.ax = &H200
    regs.dx = ASC(MID$(s$, i, 1))
    Interrupt &H21, regs, regs
  NEXT

END SUB

If you compile this, then you should be able to run:

Code:
ctty com1
program.exe
THIS is the answer. I can capture text across Telnet and through the COM port (using a Lantronix UDS10 as Waffle is not TCP/IP aware).

In case anyone sees this in the future, the above code works but there's one caveat. You need to load a library when you start QBasic.

C:\QB45\QB.EXE /L QB.QLB

QBASIC can't find QB.LIB at compile time? On my installation it was not unpacked. I had to copy it to C:\QB45.

COPY A:\QB.LI$ C:\QB45
C:\QB45\UNPACK.EXE QB.LI$ QB.LIB

Thank you for your help!!!!
 
One more question, Plasma, if you will.

I can't seem to get new line feeds or CR's to work. All text is happening on the same line on the telnet end, though new lines appear correctly on the console end.

I've tried placing a CHR$(13) inside the stdin and stdout functions before the loop. I've also tried placing CHR$(13) just before calling stdout in the main function. I've also tried something like this too: SHELL "ECHO OFF & ECHO 1 > temp & ECHO ON".

But everything I've tried still has all text (input and output) happen on the same line on the Telnet side. Console side works correctly.

Thanks!
 
You probably need to use CR+LF which would be CHR$(13) + CHR$(10)
 
@Plasma - Thank you, that was exactly it. Now I'm left with an ever-increasing number issues. So I think I'm going to switch to using C++.

The issues I face with QBasic are not going to be easily surmountable:

  • Need to scan for special escape sequences, like backspace, etc.
  • Colors aren't working in the current sdout method
  • Keeping track of cursor position when using arrow keys, which might involve figuring out *where* the cursor is at to begin with.
  • Any other security concerns that someone might try to use to sneak in code that should not be ran (I know, it's QBASIC and Telnet, so security is already gone)

This has been interesting though, and I would still would like to learn (just for the sake of learning) how to make the method more complete so as to be able to scan for escape sequences and handle them appropriately. But this is re-inventing the wheel when it's not necessary for this hobby project. Borland C++ 2.0 will suffice.
 
Back
Top