能否在.lib库中添加TSR?MS-DOS平台C语言项目技术咨询
Great questions—let’s break this down step by step since TSRs and MS-DOS multitasking are such fun (and tricky!) retro computing topics.
Can You Add a TSR to a .lib Static Library?
Short answer: Yes, technically you can—but with important caveats. A .lib file is just a collection of compiled object (.obj) files, so you absolutely can package your TSR code into one. Here’s how and what to watch out for:
Step 1: Isolate your TSR logic
Split your TSR code into two parts: the initialization/installation code (which sets up the TSR, hooks interrupts, and triggers memory residency) and the resident service code (the part that stays in memory and handles interrupts). Only the resident part needs to live in the.lib—though you can include the init code too if you want to reuse it across projects.Step 2: Compile to .obj, then add to .lib
Use your C compiler (like Turbo C or Watcom C) to compile the TSR code to an object file. Then use the library manager tool (e.g.,libin Turbo C) to add the.objto your.lib:lib my_custom_lib.lib + tsr_resident_code.objCritical Caveats
- TSRs rely on being loaded into memory and staying there—when you link the
.libinto an executable, the TSR code won’t automatically驻留 unless you explicitly call the initialization function that triggers_dos_keep()(or equivalent) at runtime. - Don’t accidentally link the TSR init code into programs that don’t need to install the TSR—this could cause unexpected memory bloat or unwanted residency.
- TSRs rely on being loaded into memory and staying there—when you link the
MS-DOS Multitasking with TSRs in C
MS-DOS is a single-tasking OS, but you can implement pseudomultitasking using TSRs that hook system interrupts (most commonly the 18.2Hz clock interrupt, INT 08h). Here’s a practical, actionable breakdown:
Core Concepts
Your TSR will:
- Save the original interrupt vector for
INT 08h(or another interrupt you want to use). - Replace the vector with your custom interrupt handler.
- In the handler, switch between different task contexts (registers, stack pointers, etc.) on each clock tick.
- Use
_dos_keep()(Turbo C) orkeep()(other compilers) to keep the TSR resident in memory after the initial program exits.
Example Framework (Turbo C)
1. TSR Installation & Residency Code
#include <dos.h> #include <stdlib.h> /* Function prototypes */ void interrupt (*old_int08)(void); void interrupt new_int08(void); void task1(void); void task2(void); /* Task context storage */ typedef struct { unsigned int ax, bx, cx, dx, si, di, bp, sp; unsigned int cs, ip, flags; } TaskContext; TaskContext task1_ctx, task2_ctx; int current_task = 0; char msg1[] = "Task 1 running\r\n$"; char msg2[] = "Task 2 running\r\n$"; void main() { /* Save original INT 08h vector */ old_int08 = getvect(0x08); setvect(0x08, new_int08); /* Initialize task contexts (simplified example) */ task1_ctx.cs = FP_SEG(task1); task1_ctx.ip = FP_OFF(task1); task2_ctx.cs = FP_SEG(task2); task2_ctx.ip = FP_OFF(task2); /* Keep TSR resident: 0 = exit code, size = memory to keep (in paragraphs) */ _dos_keep(0, (_DS + (_SS + (_SP/16) - _DS)) / 16 + 1); }
2. Interrupt Handler & Task Switching
void interrupt new_int08(void) { /* Call original INT 08h first to maintain system clock functionality */ (*old_int08)(); /* Save current task's context */ if (current_task == 0) { asm { mov task1_ctx.ax, ax mov task1_ctx.bx, bx mov task1_ctx.cx, cx mov task1_ctx.dx, dx mov task1_ctx.si, si mov task1_ctx.di, di mov task1_ctx.bp, bp mov task1_ctx.sp, sp pushf pop task1_ctx.flags } } else { asm { mov task2_ctx.ax, ax mov task2_ctx.bx, bx mov task2_ctx.cx, cx mov task2_ctx.dx, dx mov task2_ctx.si, si mov task2_ctx.di, di mov task2_ctx.bp, bp mov task2_ctx.sp, sp pushf pop task2_ctx.flags } } /* Switch to the other task */ current_task = !current_task; if (current_task == 0) { asm { mov ax, task1_ctx.ax mov bx, task1_ctx.bx mov cx, task1_ctx.cx mov dx, task1_ctx.dx mov si, task1_ctx.si mov di, task1_ctx.di mov bp, task1_ctx.bp mov sp, task1_ctx.sp push task1_ctx.flags popf jmp far [task1_ctx.cs:task1_ctx.ip] } } else { asm { mov ax, task2_ctx.ax mov bx, task2_ctx.bx mov cx, task2_ctx.cx mov dx, task2_ctx.dx mov si, task2_ctx.si mov di, task2_ctx.di mov bp, task2_ctx.bp mov sp, task2_ctx.sp push task2_ctx.flags popf jmp far [task2_ctx.cs:task2_ctx.ip] } } }
3. Sample Tasks
void task1(void) { while(1) { // Write to screen (simple task example) asm { mov ah, 0x09; mov dx, offset msg1; int 0x21 } // Yield control—interrupt handler will switch tasks on next tick } } void task2(void) { while(1) { asm { mov ah, 0x09; mov dx, offset msg2; int 0x21 } } }
Key Tips for Success
- Keep interrupt handlers short: DOS is not reentrant, so avoid calling DOS interrupts (
INT 21h) inside your handler unless you’re using a reentrant DOS extender. - Calculate residency size correctly: If you get this wrong, your TSR might overwrite itself or other memory. Use the
MEMcommand in DOS to verify residency after installation. - Test with simple tasks first: Get basic task switching working before adding complex logic—debugging TSRs in MS-DOS is tricky, so start small!
- Use a compatible compiler: Turbo C 2.0/3.0 is a great choice for MS-DOS TSR development, as it has built-in functions like
_dos_keep()and native interrupt support.
内容的提问来源于stack exchange,提问作者Jaymin Patel

