Cannot write to screen memory in C
I am very new to C, it's my second high-level programming language after Java. I have gotten most of the basics down, but for whatever reason I am unable to write a single character to screen memory.
This program is compiled using Turbo C for DOS on an Am486-DX4-100 running at 120mhz. The graphics card is a very standard VLB Diamond Multimedia Stealth SE using a Trio32 chip.
For an OS I am running PC-DOS 2000 with an ISO codepage loaded. I am running in standard MDA/CGA/EGA/VGA style 80 column text mode with colour.
Here is the program as I have it written:
#include <stdio.h>
int main(void) {
unsigned short int *Video = (unsigned short int *)0xB8000;
*Video = 0x0402;
getchar();
return 0;
}
As I stated, I am very new to C, so I apologize if my error seems obvious, I was unable to find a solid source on how to do this that I could understand.
To my knowledge, in real mode on the x86 platform, the screen memory for text mode starts at 0xB8000. Each character is stored in two bytes, one for the character, and one for the background/foreground. The idea is to write the value 0x0402 (which should be a red smiling face) to 0xB8000. This should put it at the top left of the screen.
I have taken into account the possibility that the screen may be scrolling, and thus immediately removing my character upon execution in two ways. To resolve this issue, I have tried:
- Repeatedly write this value using a loop
- Write it a bit further down.
I can read and print the value I wrote to memory, so it's obviously still somewhere in memory, but for whatever reason I do not get anything onscreen. I'm obviously doing something wrong, however I do not know what could be the issue. If any other details are needed, please ask. Thank you for any possible help you can give.
Solution 1:
In real mode to address the first full 1MiB of memory a mechanism called 20-bit segment:offset addressing is used. 0xb8000 is a physical memory address. You need to use something called a far
pointer that allows you to address memory with real mode segmentation. The different types of pointers are described in this Stackoverflow Answer
0xb8000 can be represented as a segment of 0xb800 and an offset of 0x0000. The calculation to get physical address is segment*16+offset. 0xb800*16+0x0000=0xb8000. With this in mind you can include dos.h
and use the MK_FP
C macro to initialize a far
pointer to such an address given segment and offset.
From the documentation MK_FP is defined as:
MK_FP() Make a Far Pointer
#include <dos.h> void far *MK_FP(seg,off); unsigned seg; Segment unsigned off; Offset
MK_FP() is a macro that makes a far pointer from its component segment 'seg' and offset 'off' parts.
Returns: A far pointer.
Your code could be written like this:
#include <stdio.h>
#include <dos.h>
int main(void) {
unsigned short int far *Video = (unsigned short int far *)MK_FP(0xB800,0x0000);
*Video = 0x0402;
getchar();
return 0;
}
Solution 2:
The memory segment adress depends on the video mode used:
0xA0000 for EGA/VGA graphics modes (64 KB)
0xB0000 for monochrome text mode (32 KB)
0xB8000 for color text mode and CGA-compatible graphics modes (32 KB)
To directly access vram you need a 32 bit-pointer to hold segement and offset address otherwise you would mess up your heap. This usually leads to undefined behaviour.
char far *Video = (char far *)0xb8000000;
See also: What are near, far and huge pointers?
Solution 3:
As @stacker pointed-out, in the 16-bit environment you need to assign the pointer carefully. AFAIK you need to put FAR
keyword (my gosh, what a nostalgia).
Also make sure you don't compile in so-called "Huge" memory model. It's incompatible with far addressing, because every 32-bit pointer is automatically "normalized" to 20 bits. Try selecting "Large" memory model.