Text in DOS mit einem Mal auf dem Bildschirm ausgeben
-
Hallo,
die folgende Frage bezieht sich auf DOS, nicht auf die Windows-Konsole:Wie kann ich den kompletten Bildschirm mit einem Mal mit verschiedenfarbigen Zeichen füllen?
Statt also per Schleife, gotoxy, textcolor und cout/printf jedes Zeichen einzeln zu setzen, möchte ich gerne meine gesamten Ausgabedaten in ein Array packen und dieses Array mit einem Funktionsaufruf (memcpy oder eine Funktion, die möglicherweise speziell hierfür vorgesehen ist) in den Bildschirmspeicher schreiben.
-
Textmodus oder einer der Grafikmodi?
Erst mal solltest du wissen unter welcher Adresse deine Grafikkarte angesprochen werden will, wenn du die direkt adressieren möchtest.
Universell sollte es da auch noch über int10 etwas geben. Wenn du da mit der Geschwindigkeit klarkommst, sollte das dann auch auf fast jedem PC laufen.
Ist schon lange her das ich etwas damit gemacht hab - müsste ich selbst erst nachsehen.
Meine mich zu erinnern das die Compiler zur DOS-Zeit auch noch individuelle Quelltextvarianten wollten.
MfG f.-th.
-
Textmodus oder einer der Grafikmodi?
Erst mal solltest du wissen unter welcher Adresse deine Grafikkarte angesprochen werden will, wenn du die direkt adressieren möchtest.
Universell sollte es da auch noch über int10 etwas geben. Wenn du da mit der Geschwindigkeit klarkommst, sollte das dann auch auf fast jedem PC laufen.
Ist schon lange her das ich etwas damit gemacht hab - müsste ich selbst erst nachsehen.
Meine mich zu erinnern das die Compiler zur DOS-Zeit auch noch individuelle Quelltextvarianten wollten.
MfG f.-th.
-
f.-th. schrieb:
Textmodus oder einer der Grafikmodi?
In meinem Fall ist es der normale 80x25 Textmodus.
f.-th. schrieb:
Erst mal solltest du wissen unter welcher Adresse deine Grafikkarte angesprochen werden will, wenn du die direkt adressieren möchtest.
Ist das denn von der Grafikkarte abhängig? In Mode13h zum Beispiel konnte man allgemein schreiben:
unsigned char far *screen = MK_FP(0xA000, 0x0000);
und schon hatte man den Speicher.
f.-th. schrieb:
Universell sollte es da auch noch über int10 etwas geben. Wenn du da mit der Geschwindigkeit klarkommst, sollte das dann auch auf fast jedem PC laufen.
Ist int10 für sowas nicht zu langsam?
Ich erhoffe mir ja, dass das mit einem schönen Byte-Array geht: 80x25x2 Byte-Einträge, zwei Byte für jedes Zeichen (1. char-Wert, 2. Vorder- und Hintergrundfarbe in je vier Bit), und das dann mit memcpy in den Speicher.f.-th. schrieb:
Meine mich zu erinnern das die Compiler zur DOS-Zeit auch noch individuelle Quelltextvarianten wollten.
Also zumindest bei Mode13h hat es gleichermaßen auf Visual C++ und Turbo/Borland C++ funktioniert.
-
Hier mal ein wenig zum Adressbereich:
http://lb.landw.uni-halle.de/vitrine/grafik/history.htmMfG f.-th.
-
Der Link hilft mir nicht wirklich weiter. Wenn ich versuche, per Zeiger auf A0000 zuzugreifen, kommt nicht das raus, was ich tatsächlich vorher auf den Bildschirm geschrieben habe. Und das mit der Adresse A0000 ist das einzige, was da für mein Problem von Relevanz sein könnte, ansonsten ist der Artikel nicht gerade geeignet, wenn man ein Programmierproblem hat.
Ich brauche wirklich mal ein konkretes praktisches Beispiel: Wenn ich in Mode13h den Pixel oben links im Bild weiß zeichnen will, dann mach ich das so:
unsigned char far *screen = MK_FP(0xA000, 0x0000); screen[0] = 15;
Wie ist jetzt die Vorgehensweise, um im Textmodus (Mode 3) an Position 7,5 das Zeichen 'A' mit in gelber Farbe und grünem Hintergrund zu setzen, ohne printf oder cout zu benutzen, sondern indem man direkt den Speicher anspricht (damit man auch den ganzen Bildschirm mit einem memcpy füllen kann)?
Da muss es doch irgendwas geben. Oder will man mir sagen, dass zum Beispiel diese Rogue-like Spiele alle mit
for (int y = 1; y < 25; y++) { for (int x = 1; x <= 80; x++) { gotoxy(x, y); textcolor(...); cprintf(...); } }
programmiert wurden?
-
ist schon eine Zeitlang her.
Versuche statt A0000 mal B0000.Ohne Gewähr:
A0000 war die Startadresse für die Pixel im Grafikmodus.
Der Textmodus beginnt bei B0000 monocrom.
und bei B8000 in Farbe.Jedenfall bei einem Editor Anfang der 90er.
Stand in den Link nicht ähnlichesMfG f.-th.
-
http://www.forum-3dcenter.org/vbulletin/archive/index.php/t-375952.html
Die nehmen da B8000.
-
Ja, ich bin gestern beim Durchsuchen des Internets auch zufällig darauf gestoßen. Aber trotzdem danke.
Die korrekte Verwendung in C und C++ wäre dann übrigens
MK_FP(0xB800, 0x0000)
.
-
Weißt du zufällig auch, wie man die Codepage ändert und den Cursor unsichtbar und sichtbar machen kann? Für den Cursor haben die Borland Compiler in ihrer conio.h was, aber ich würde gerne etwas nehmen, das in jedem DOS-Compiler benutzt werden kann.
-
Hallo Harper,
wenn du Quellcode Beispiele für das Füllen des Bildschirms mit Hilfe der direkten Adressierung suchst, kann ich dir das Program "Laughing Dog" empfehlen. Damit kannst du im Textmodus "zeichnen" und es anschließend beispielsweise für Microsoft C und Turbo C generieren lassen.
Zum Cursor:
Dazu kannst du die Funktion 0x01 vom Int10 verwenden. Das ist die Funktion zum ändern der Cursorgröße.
Ich kann dir hier leider nur ein Beispiel für Turbo C geben, aber das Prinzip ist immer das selbe.Mit diesem Aufruf kannst du den Cursor ausschalten:
( Code jeweils in getrennten Programmen angegeben zur besseren Übersichtlichkeit )#include <dos.h> int main() { union REGS regs; regs.h.ah=0x01; // Aufruf der Funktion 'Cursor-Size' regs.h.ch=0x01; regs.h.cl=0x00; int86(0x10, ®s, ®s); // Die Werte werden an Int10 übergeben }
Und mit diesem Aufruf kannst du den Cursor wieder einblenden:
#include <dos.h> int main() { union REGS regs; regs.h.ah=0x01; // Aufruf der Funktion 'Cursor-Size' regs.h.ch=0x05; regs.h.cl=0x05; int86(0x10, ®s, ®s); }
Eine Wichtige Anmerkung habe ich dazu gleich noch
Beim Einblenden des Cursors können die Werte von CH und CL verschieden sein. Wenn du zum Beispiel CH=0x06 und CL=0x01 setzt, dann hast du keinen Unterstrich wie gewöhnlich sondern einen Block. Du solltest vielleicht ein wenig damit rumspielen.
Einen Tipp kann ich dir noch zu den Ints geben!
Wenn du magst dann zieh dir doch folgendes Buch
http://www.borncity.de/Library/DOSProgHB.PDFAnfangs könnte es ein wenig kompliziert werden aber ich kann es eigentlich nur empfehlen.
So aber jetzt genug Text von meiner Seite
Ich hoffe ich konnte ein bisschen helfen.
-
Aus den bisherigen Äusserungen gehe ich davon aus, das es sich um eine CGA/EGA/VGA-Karte (oder höher) mit Farbmonitor handelt. Laut PC-Intern würden sich die Adressen bei einem Monochrome-Monitor auch bei einer VGA-Karte verschieben!!
Ich denke aber, das man sich den Test heutzutage eigentlich sparen koennte ...
Also bei 80x25, 16 Farben (Videomodus 03h mit CGA,EGA, VGA oder höher) würde man das Videosegment bei B800:0000 (damals das selbe wie B000:8000) finden.
Allerdings gibt es die Möglichkeit verschiedene Bildschirmseiten zu nutzen. Die Erste also bei B800:0000 die zweite bei B800:1000 u.s.w. (VGA unterstützt bis zu 8 Seiten).
Ein sichtbares Zeichen besteht im Speicher aus zwei Byte. Das erste Byte ist das Zeichen im zweiten ist das Attribut. Das Attribut besteht im unteren Nibble (4 Bit) aus der Zeichenfarbe, dann 3Bit Hintergrundfarbe.
Um den Grafikspeicher schnell zu kopieren eignet sich memcpy am besten.
Um flackern während des kopierens zu verhindern könnte man zunächst eine nicht sichtbare Seite (mit memcpy) füllen und dann aktivieren.
Übrigens: Der Grafikmodus nutzt den VRAM ab A000:0000 - hier war aber Textmode gefragt.
-
Harper schrieb:
Ich brauche wirklich mal ein konkretes praktisches Beispiel:
Wie ist jetzt die Vorgehensweise, um im Textmodus (Mode 3) an Position 7,5 das Zeichen 'A'
mit in gelber Farbe und grünem Hintergrund zu setzen, ohne printf ..War ja im Ansatz schon da (habs jetzt aber nicht selbst getestet!)
unsigned char far *screen = MK_FP(0xB800, 0x0000); int x = 7; int y = 5; int xypos = (x*2) + (y*80*2); screen[xypos] = `A`; /* Zeichen */ screen[xypos+1] = 0x2e; /* 0x02=grün, 0x0e=gelb */
-
Erst einmal vielen Dank für eure Hilfe. Das DOS-Buch ist besonders informativ.
Aber folgende Dinge würden mich noch interessieren:
1. Was muss ich machen, wenn ich Werte nicht setzen, sondern ermitteln will?
Der Bildschirmmodus wird zum Beispiel folgendermaßen gesetzt: AH = 0, AL = Modus.
Wenn man ihn aber ermitteln will, benutzt man: AH = 15, Modus = AL.Was muss ich einstellen, wenn ich zum Beispiel die Cursorattribute auslesen will? Gibt es da überhaupt eine int86-Funktion oder muss man das auf andere Weise ermitteln?
2. Man kann mit AH = 0 und AL zwar den Bildschirmmodus setzen. Aber sowohl der Textmodus mit 80 x 25 Zeichen (8 x 16 Pixel pro Zeichen) als auch der mit 80 x 50 Zeichen (8 x 8 Pixel pro Zeichen) wird vom Programm als Mode 3 interpretiert. Beim Setzen von Mode 3 nimmt er dann 80 x 25 Zeichen.
Wie kann ich 80 x 50 Zeichen einstellen?
-
Evtl. hilft das Programm aus dem Fundus.
/********************************************************************/ /* Programm : VIDMODE */ /* ---------------------------------------------------------------- */ /* Zweck : Setzt den Videomodus neu */ /* ---------------------------------------------------------------- */ /* Autor : */ /* erstellt : 1990 */ /* ---------------------------------------------------------------- */ /* Compile : QC2 Compiler */ /* Link : */ /* ---------------------------------------------------------------- */ /* Aufruf : VIDMOD modus | -mxx | ? [-c[x]] [-lxxx] [-zxx,y] */ /* modus : MONO setzt Modus 7 (MDA) */ /* CO80 / CO40 setzt Modus 3 / 1 */ /* BW80 / BW40 setzt Modus 2 / 0 */ /* */ /* -m : Expliziete Angabe des Modus */ /* xx = Modus als Dezimal oder Hexzahl */ /* -c : Bildschirm x = 0 : nicht löschen */ /* 1 : 1mal nicht löschen */ /* 2 : löschen */ /* */ /* -l : Zeilenzahl setzen xxx = 200 (CGA) */ /* = 350 (EGA) */ /* = 400 (VGA) */ /* */ /* -z : Zeichensatz 8*xx und Tabelle y aktivieren */ /* xx = 16 */ /* 14 */ /* 8 */ /* y = 0 oder 1 */ /* */ /* ? : Hilfetext ausgeben */ /* */ /* ---------------------------------------------------------------- */ /* Sonstiges : Funktioniert nur mit VGA-Karte */ /********************************************************************/ #include <stdio.h> /* Bildschirmmodi */ #define BW40 0 #define CO40 1 #define BW80 2 #define CO80 3 #define MONO 7 #define CO13243 0x54 #define CO13225 0x55 #define MO13243 0x56 #define MO13225 0x57 struct mods { char *modtxt; int modnr; } static struct mods modes[] = { "BW40",BW40, "CO40",CO40, "BW80",BW80, "CO80",CO80, "MONO",MONO, "CO13243",CO13243, "CO13225",CO13225, "MO13243",MO13243, "MO13225",MO13225, NULL,0 }; /* Bildschirmzeilen */ #define SL200 0 #define SL350 1 #define SL400 2 /* Zeichensätze */ #define CS8 0x12 #define CS14 0x11 #define CS16 0x14 /* err Codes */ #define NOSWITCH -11 #define INVALIDCOMMAND -12 #define LINES -13 #define ZEICHEN -14 #define OK 0 int modus_n = -1, modus_b, modus_a, lines_n = -1, lines_a, lines_aa, zeich_n = -1, zeich_a, table = 0, loesch_a, loesch_n = -1, err = OK, rows,coll, crsr_x,crsr_y,seite, videoinfo,videoinfo1,videoinfo2,videoinfo3; char buff[80]; void initvideo(char vidmode) { _asm { mov al,vidmode mov ah,00h ; Videomodus setzten int 10h } } void setscanlines(char lines) { _asm { mov bl,30h ; Bildschirmzeilen setzten mov al,lines mov ah,12h int 10h } } void setcharakterset(char set,char table) { _asm { mov al,set mov bl,table mov ah,11h ; Bildschirmzeilen setzten int 10h } } void set_BIOSvar_10h(char flag) { _asm { mov ax,0040h mov es,ax mov cl,4 mov bl,2 add bl,flag shl bl,cl mov ah,es:10h mov al,ah and ah,0cfh or ah,bl mov es:10h,ah } } int peekb(int segm,int offs) { _asm { mov ax,segm mov es,ax mov di,offs mov al,es:[di] mov ah,0 } } int peekw(int segm,int offs) { _asm { mov ax,segm mov es,ax mov di,offs mov ax,es:[di] } } void pokeb(int segm,int offs,int bwert) { _asm { mov ax,segm mov es,ax mov di,offs mov ax,bwert mov es:[di],al } } void setcrsr(int x,int y,int p) { _asm { mov dx,x mov bx,y mov dh,bl mov ah,02 mov bx,seite mov bh,bl int 10h } } int setmodus() { int i; for(i=0;modes[i].modtxt!=NULL;i++) if(!(strcmp(modes[i].modtxt,buff))) { modus_n = modes[i].modnr; return(OK);} return(INVALIDCOMMAND); } void get_old_mode() { int i,j; char c; i = peekb(0x40,0x10); i &= 0x30; i >>= 4; switch(i) { case 0 : modus_b = -1;break; case 1 : modus_b = 1;break; case 2 : modus_b = 3;break; case 3 : modus_b = 7;break; default: modus_b = -2;break; } modus_a = peekb(0x40,0x49);/* & 0x7f; */ coll = peekw(0x40,0x4a); rows = peekb(0x40,0x84) + 1; zeich_a = peekw(0x40,0x85); lines_a = rows * zeich_a; lines_aa = lines_a; if (lines_a <= 480) lines_aa = 480; if (lines_a <= 400) lines_aa = 400; if (lines_a <= 350) lines_aa = 350; if (lines_a <= 200) lines_aa = 200; videoinfo = peekb(0x40,0x87); videoinfo1 = peekb(0x40,0x88); videoinfo2 = peekb(0x40,0x89); videoinfo3 = peekb(0x40,0x8a); seite = peekb(0x40,0x62); crsr_x = peekb(0x40,0x50+2*seite); crsr_y = peekb(0x40,0x51+2*seite); } int testit() { int i,j; char c; printf("Videomode beim Einschalten : "); switch(modus_b) { case 0 : printf("Keine Video-Karte\n");break; case 1 : printf("40x25 CGA\n");break; case 3 : printf("80x25 CGA\n");break; case 7 : printf("80x25 MDA\n");break; default: printf("%i (Fehler)\n",modus_b);break; } printf("Aktueller Videomode : "); for(i=0;modes[i].modtxt!=NULL;i++) if(modes[i].modnr == modus_a) {printf("%s",modes[i].modtxt); break;} printf(" (%i)\n",modus_a); printf("Spalten: %i ",coll); printf("Zeilen: %i ",rows); printf("Zeichenhöhe: %i\n",zeich_a); printf("Scanlines: %i ",lines_a); printf("von: %i\n",lines_aa); if(( videoinfo & 0x80) == 0x80) printf("Bildschirm nicht löschen\n"); printf("Videoadapter ist "); if(( videoinfo & 0x08) == 0) printf("MCGA, EGA oder VGA\n"); else printf("MDA oder CGA\n"); if(( videoinfo & 0x02) == 2) printf("mit Monochrom-Monitor\n"); /*if(( videoinfo & 0x01) == 0) printf("als CGA-Emulation\n");*/ i = videoinfo & 0x60; i >>= 5; i = (i+1)*64; printf("Videospeicher : %d KByte\n",i); if(( videoinfo2 & 0x04) == 4) printf("VGA Monochrom-Monitor angeschlossen\n"); else printf("VGA Farb-Monitor angeschlossen\n"); printf("Videoinfo : $%-2x ",videoinfo); printf("Videoinfo1 : $%-2x \n",videoinfo1); printf("Videoinfo2 : $%-2x ",videoinfo2); printf("Videoinfo3 : $%-2x \n",videoinfo3); printf("zeile: %i spalte: %i seite: %i",crsr_y,crsr_x,seite); } int schalter() { char c; char *b; c = buff[1]; switch (c) { case 'L' : sscanf(buff+2,"%i",&lines_n); break; case 'M' : sscanf(buff+2,"%i",&modus_n); break; case 'C' : sscanf(buff+2,"%i",&loesch_n); break; case 'Z' : sscanf(buff+2,"%i",&zeich_n); b = strchr(buff,44); if (b) sscanf(b+1,"%i",&table); break; default : return(NOSWITCH);break; } return(OK); } int help() { puts("Help:"); puts(" modus "); puts(" -cx : Bildschirm nicht löschen"); puts(" x = 0: nie, 1: Einmal nicht, 2: immer löschen"); puts(" -lxxx : Bildschirmlinien"); puts(" -zxx,y : Zeichensatz"); puts(" -mxx : Modus als Zahl"); return(0);} int main(argc,argv) int argc; char *argv[]; { int i; char *bb, c ; get_old_mode(); if (argc < 2) testit(); else { for(i=1; i<argc; i++) { strcpy(buff,argv[i]); strupr(buff); c = buff[0]; switch (c) { case '?' : err = help();return(0); break; case '-' : case '/' : err = schalter(); break; default : err = setmodus(); break; } } if (lines_n>0) switch (lines_n) { case 200 : setscanlines((char)SL200); break; case 350 : setscanlines((char)SL350); break; case 400 : setscanlines((char)SL400); break; default : err = LINES; } if (modus_n < 0) modus_n = modus_a; if (modus_n == MONO || modus_n == MO13225 || modus_n == MO13243) set_BIOSvar_10h('\1'); else set_BIOSvar_10h('\0'); if (loesch_n == 0 || loesch_n == 1) modus_n |= 0x80; initvideo(modus_n); if (loesch_n == 1) { i = peekb(0x40,0x87); i = i & 0x7f; pokeb(0x40,0x87,i); } if (loesch_n == 0 || loesch_n == 1) { crsr_x = (crsr_x >= coll) ? coll : crsr_x; crsr_y = (crsr_y >= rows) ? rows : crsr_y; setcrsr(crsr_x,crsr_y,seite); } if (zeich_n>0 && table < 2) switch (zeich_n) { case 8 : setcharakterset((char)CS8,(char)table); break; case 14 : setcharakterset((char)CS14,(char)table); break; case 16 : setcharakterset((char)CS16,(char)table); break; default : err = ZEICHEN; } } return(err); }
Keine Lästerein über den Stil, das ist über 20 Jahre her
-
Laut PC-Intern bekommt man den aktuellen Videomodus mit
_asm { mov ah, 0fh ;Videomode auslesen int 10h } Ergebnis: al: Videomode ah: Anzahl der Zeichen pro Zeile bh: aktuelle Bildschirmseite
Harper schrieb:
Textmodus mit 80 x 25 Zeichen (8 x 16 Pixel pro Zeichen) [oder] 80 x 50 Zeichen
(8 x 8 Pixel pro Zeichen) wird vom Programm als Mode 3 interpretiert.Mode 3 sagt erstmal nur das es ein Textmode ist.
Was auf den Sceen passt, ist abhängig vom eingestellten
ROM-Zeichensatz._asm { mov ax, 1111h ;8x14-ROM-Zeichensatz auswählen mov bl, 0h int 10h }
oder
_asm { mov ax, 1112h ;8x8-ROM-Zeichensatz auswählen mov bl, 0h int 10h }
-
Danke. Das mit dem Videomodus hat jetzt auch geklappt.
Ich hab nun versucht, die Codepage einzustellen. Auf http://poli.cs.vsb.cz/misc/rbint/text/2112.html steht es folgendermaßen:
INT 21 - DOS 3.3+ - GET GLOBAL CODE PAGE TABLE AX = 6601h Return: CF set on error AX = error code (see #01680 at AH=59h/BX=0000h) CF clear if successful BX = active code page (see #01757) DX = system code page (see #01757) SeeAlso: AX=6602h ---------- INT 21 - DOS 3.3+ - SET GLOBAL CODE PAGE TABLE AX = 6602h BX = active code page (see #01757) DX = system code page (active page at boot time) Return: CF set on error AX = error code (see #01680 at AH=59h/BX=0000h) CF clear if successful AX = EB41h (Novell NWDOS v7.0 when NLSFUNC not installed and request was for previously-active code page) SeeAlso: AX=6601h,INT 2F/AX=14FFh
Mein Code sieht so aus:
#include <stdio.h> #include <dos.h> void get() { union REGS in, out; in.x.ax = 0x6601; int86(0x21, &in, &out); printf("Active code page: %i\n", out.x.bx); printf("System code page: %i\n", out.x.dx); } void set() { union REGS in, out; in.x.ax = 0x6602; in.x.bx = 850; in.x.dx = 850; int86(0x21, &in, &out); printf("Code pages were set.\n"); } int main() { int i; for (i = 128; i < 256; i++) printf("%c", i); printf("\n\n"); get(); set(); get(); return 0; }
Das Ermitteln der Codepages geht. System-Codepage ist 437, active ist 850. Doch wenn ich dann beide auf 850 setze und sie mir danach noch mal ermitteln lasse, stehen sie immer noch auf 437 und 850. Das Setzen hat also nicht geklappt. Was mache ich falsch?
-
Wie dort steht, ist die "system code page" die code page, die beim Booten aktiv ist. Vermutlich die im BIOS eingestellte code page. Die kannst du iaR. nicht ueber diese Funktion umstellen. ZB. die Doku von DR-DOS fuehrt diese Moeglichkeit daher konsequenterweise gar nicht erst auf.
-
Ja, das macht Sinn. Aber auch wenn ich Zeile 21 auskommentiere (und in Zeile 20 dann 437 hinschreibe, weil 850 ja schon gesetzt ist), bleibt die Active Code Page so wie sie war.
-
Sehe da sonst gerade keinen groben Fehler...
Checke die Fehlercodes (CF und AX) und stelle sicher, dass die ganzen Geschichten mit country.sys und das mode select Zeugs in deinem DOS korrekt eingerichtet sind, bzw. die Funktion ueberhaupt unterstuetzt wird.