AnMaBaGiMa's Home

DirectX-Anwendungen mit C++ erstellen - Einführung in DirectDraw

DirectX ist eine Schnittstelle die Microsoft eingeführt hat um es den Programmieren zu ermöglichen hardwareunabhängige Software zu schreiben. Viele denken , wenn Sie DirectX höhren sofort an schnelle 3D-Computer-Spiele im Vollbildmodus - aber DirectX ist mehr.
Die einzelnen Komponenten werden wir hier Schritt für Schritt kennen lernen. Von DirectDraw über Direct3D zu DirectSound. Daneben existieren noch DirectInput, DirectPlay und DirectMusic die wir erst eimal nicht planen zu behandeln.

DirectX hält sich bei seinem Aufbau an den von Microsoft entwickelten COM-Standard. COM steht für Commen Object Modell. Mehr Infos über dieses Modell gibt es auf den Microsoft-Seiten.

In der C/C++-Umgebung werden uns über die DirectX-Header-Dateien Schnittstellen(Interfaces) definiert, die es uns ermöglichen auf diese COM-Objekte zuzugreifen.
Um jedoch DirectX-Anwendungen programmieren und testen zu können sind mehrere Vorrausetzungen zu erfüllen :
  • Betriebssystem Win9x/NT
  • DirectX-Treiber müssen installiert werden (Version 7 ist für dieses Tutorial erforderlich)
  • Header- und Lib-Files der aktuellen DirectX-Version (hier runterladen)
  • Die Library-Files für den Borland-Compiler befinden sich innerhalb des DX-SDK in einem seperaten Unterverzeichnis
  • Lcc-Win32 Benutzer können weder die MS-Libs noch die Borland-Libs verwenden. Passende Libs hier runterladen
  • NT-Benutzer benötigen das Service Pack 6/6a um die Funktionalität von DirectX 7 nutzen zu können

Unser erstes DirectX-Programm

Ein wohl guter Einstig in die DirectX-Programmierung ist eine "graphische" Anwendung. Denn hier sieht man was man Programmiert hat.
Unser Ziel wird es sein, in einem Fenster eine DirectDraw-Oberfläche zu erstellen in die wir dann beliebige Punkte, Figuren und sonstiges zeichnen können.
Unser Programm soll TestDDraw heissen. Legen Sie sich dazu mit VC++ bzw. lcc-Win32 ein neues Projekt an. Achten Sie darauf, dass es sich um ein Projekt handelt, dass eine Windows-Anwendung erstellt (keine Konsolenanwendung). Fügen Sie, je nach Entwicklungsumgebung, die Dateien ddraw.lib und dxguid.lib zu den zu linkenden Objekt-files hinzu.
Lcc-Win32-Benutzer benötigen die dxguid.lib nicht.

Da der Compiler von lcc-Win32 ein C-Compiler und kein C++-Compiler ist gehen wir kurz auf die Unterschiede in der Programmierung ein:
DirectX unterstützt sowohl den Zugriff über C als auch C++ Programme. Der grundlegende Unterschied liegt in der Verwaltung der Komponenten der DirectX-COM-Objekte.
lpDD sei ein Zeiger auf die Schnittstelle zu einem solchen Objekt. Dann werden die Memberfunktionen über lpDD->Memberfunktion(...) aufgerufen. Aber nur in C++. In C hingegen wird der Zugriff über eine virtuelle Tabelle gelöst. Das bedeutet, dass hier lpDD->lpVtbl->Memberfunktion(lpDD, .....) auf die gefragte Funktion zu greift. lpVtbl ist der Zeiger auf die virtuelle Tabelle der Objekte. Dabei erhält die Funktion an erster Stelle einen zusätzlichen Übergabeparameter, welcher das Objekt, von dem die Funktion aufgerufen werden soll, innerhalb der virtuellen Tabelle referenziert.

Die Quellcodes sind alle in C gehalten, da sie ohne Änderungen sowohl mit VC++ als auch mit lcc-Win32 compiliert werden können.

Wir legen in unserem Projekt eine Datei TestDDraw.c an und können sofort loslegen. Als erstes müssen wir neben dem Windowsheader den Header von DirectDraw einbinden.

/* * TestDDraw.c */ #include <windows.h> #define INITGUID // <- nur für lcc-win32 Compiler #include "ddraw.h"
Nach der DirectX-Architektur wird immer ein "Master"-Objekt angelegt, von dem dann alle anderen abgeleitet werden können. In unserem Beispiel (einer DirectDraw-Anwendung) bildet ein DirectDraw-Objekt diese Zentrale.
Von diesem kann dann z.Bsp. eine Zeichenfläche, in die wir "malen" wollen, abgeleitet werden. Ein solches Objekt wird als Surface bezeichnet. Dummerweise haben die DirectX-Entwickler die eigentliche Zeichenfläche und ihre Eigentschaften bzw. Beschreibung von einander getrennt. So wird die Beschreibung eines Surfaces in einer Struktur Namens DDSURFACEDESC abgelegt.
Zur Darstellung von Grafiken mit DirectX benötigen wir zwei dieser Zeichenflächen. Eine repräsentiert unseren Bildschirm, die andere unsere Arbeitsfläche in der wir wirklich "malen".

LPDIRECTDRAW7 lpDDraw7 = NULL;//Zeiger a. die Schnittstelle des DD-Obj. der Version 7 LPDIRECTDRAWSURFACE7 lpDSurf7 = NULL;//unser Bildschirmsurface DDSURFACEDESC2 DDSurfDesc2; //die Beschreibung unserer Bildschirmzeichenfläche LPDIRECTDRAWSURFACE7 lpDPaint7 = NULL;//die Arbeitsfläche DDSURFACEDESC2 DDPaintDesc2; //und deren Beschreibung BOOL IsDDraw = FALSE;//ist TRUE wenn DirectDraw initialisiert wurde
Da eine Windows-Anmwendung wie bekannt von einem Window aus gesteuert wird, ist die WinMain-Funktion ganz im API-Stil gehalten. Die Messagebehandlungsroutine ist hier nicht abgebildet.
ACHTUNG: MFC und DirectX arbeiten nicht fehlerfrei zusammen
Die Funktion WinMain definiert unser Anwendungsfenster und zeigt es :

BOOL APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { WNDCLASS wc; HWND hwnd; MSG msg; wc.lpszClassName = "TestDDraw"; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WindowProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND + 1); wc.lpszMenuName = NULL; RegisterClass(&wc); hwnd = CreateWindow ("TestDDraw", "Test DirectDraw Fenster", WS_OVERLAPPED, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); ShowWindow (hwnd, SW_SHOW); IsDDraw = InitDirectDraw(hwnd); //DirectDraw initailisieren UpdateWindow(hwnd); //erzwingen einer WM_PAINT-Message while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } ExitDirectDraw(); //DirectDraw wieder abbauen return msg.wParam; }
Der nächste Schritt wird die Initialisierung des DirectDraw-"Modus" sein. Dieser Schritt wird in einer seperaten Funktion Names InitDirectDraw abgehandelt die TRUE zurückliefert, wenn DirectDraw erfolgrich initialisiert werden konnte.
Aufrufe von DirectDraw-Methoden geben DD_OK zurück, wenn dabei kein Fehler aufgetreten ist.
Das Erzeugen des zentralen DirectDraw-Interfaces läuft in folgenden Schritten ab:
  • Anlegen eines "Ur"-DirectDraw-Objektes (das der DX-Version 1)
  • Dieses nach einer Schnittstelle zu einem Objekt höherer Version befragen
  • Das "Ur"-DirectDraw-Objekt wieder löschen
  • Mit dem DirectDraw-Interface der höheren Version kann nun "gearbeitet" werden
Der folgende Code zeigt die oben geschilderten Schritte:

BOOL InitDirectDraw(HWND hwnd) { LPDIRECTDRAW lpDDraw = NULL; // anlegen des Objektes: lpDDraw zeigt danach auf die Schnittstelle zu diesem Objekt if (DirectDrawCreate(NULL, &lpDDraw, NULL)!= DD_OK) { MessageBox(hwnd, "Konnte DirectDraw-Objekt nicht erzeugen.", NULL, MB_OK); return FALSE; } //das "Ur"-DD-Objekt nach einer Schnittstelle zu einem DD-Objekt der Version 7 fragen //wenn diese Schnittstelle existiert, dann wird ein Objekt der Version 7 angelegt //und ein Zeiger auf die Schnittstelle zu diesem Objekt in lpDDraw7 gespeichert if (lpDDraw->lpVtbl->QueryInterface(lpDDraw, &IID_IDirectDraw7, (void **)&lpDDraw7)!=DD_OK) { MessageBox(hwnd, "Keine Schnittstelle zu DirectDraw7 vorhanden", NULL, MB_OK); return FALSE; } //das "Ur"-Objekt wieder freigeben, da wir es nicht länger brauchen lpDDraw->lpVtbl->Release(lpDDraw);
Nun kommt auch schon die Entscheidung ob die Anwendung im Vollbildmodus laufen soll oder nicht.
Wir wollen diese Frage als erstes verneinen. Aber die Tatsache Vollbild- oder Fenstermodus legt die Member- Funktion SetCooperativeLevel des DirectDraw-Objektes fest und verknüpft dieses Objekt irgendwie mit dem Fenster unserer Anwendung.

if (lpDDraw7->lpVtbl->SetCooperativeLevel(lpDDraw7, hwnd, DDSCL_NORMAL) != DD_OK) { MessageBox(hwnd, "Konnte Kooperationslevel nicht setzen", NULL, MB_OK); return FALSE; } Nun haben wir den ersten grossen Schritt getan - doch sehen werden wir davon noch nichts.
Es gilt nun eine Zeichenfläche anzulegen in die wir dann bunte Punkte "malen" können. Wir erinnern uns - Zeichenfläche war ein Surface und alle anderen Objekte neben dem Zentralen DirectDraw-Objekt müssen von diesem "abgeleitet" sein. Also benutzen wir zum erzeugen unserer Zeichenfläche die CreateSurface-Funktion des DirectDraw-Objektes. Dieser Funktion müssen wir eine Beschreibung der Zeichenfläche in Form eines zeigers auf eine DDSURFACEDESC-Struktur übergeben. Sie muss als erstes komplett mit 0 befüllt werden und muss nach DirectX-Festlegung in der Struktur-Variablen dwSize die Grösse der Struktur enthalten.
Merke: Bei DirectX muss in allen Strukturen vor der Übergabe an eine Funktionen mindesten das Feld dwSize mit der entsprechenden Strukturgrösse belegt worden sein.
//zum Anlegen einer Zeichenfläche die Beschreibung erstmal mit 0 initialisieren memset(&DDSurfDesc2, 0, sizeof(DDSURFACEDESC2)); //DirectX erwartet in solchen Strukturen grundsätzlich eine Grössenangabe der Struktur DDSurfDesc2.dwSize = sizeof(DDSURFACEDESC2); //dieses Flag steuert welche Daten für die Anlage der Zeichenfläche genutzt werden sollen DDSurfDesc2.dwFlags = DDSD_CAPS; //Struktur ddsCaps füllen DDSurfDesc2.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |//primäre Zeichenfläche (sichtbar) DDSCAPS_VIDEOMEMORY;//Surface im Grafikkartenspeicher anlegen DDSurfDesc2.ddsCaps.dwCaps2 = 0; //erzeugen der Zeichenfläche, lpDSurf ist dann der Zeiger auf die Schnittstelle if (lpDDraw7->lpVtbl->CreateSurface(lpDDraw7, &DDSurfDesc2, &lpDSurf7, NULL) != DD_OK) { MessageBox(hwnd, "Konnte primäre Zeichenfläche nicht anlegen", NULL, MB_OK); return FALSE; } //es folgt unsere "Arbeitsfläche" memset(&DDPaintDesc2, 0, sizeof(DDSURFACEDESC2)); DDPaintDesc2.dwSize = sizeof(DDSURFACEDESC2); DDPaintDesc2.dwFlags = DDSD_CAPS | DDSCAPS_HEIGHT | DDSCAPS_WIDTH; DDPaintDesc2.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN |//normale Zeichenfl.("unsichtbar") DDSCAPS_SYSTEMMEMORY;//Surface im Hauptspeicher anlegen DDPaintDesc2.ddsCaps.dwCaps2 = 0; DDPaintDesc2.dwHeight = 200; //die Zeichenfläche ist 200 Bildpunkte hoch DDPaintDesc2.dwWidth = 200; //und 200 Bildpunkte breit if (lpDDraw7->lpVtbl->CreateSurface(lpDDraw7, &DDPaintDesc2, &lpDPaint7, NULL) != DD_OK) { MessageBox(hwnd, "Konnte normale Zeichenfläche nicht anlegen", NULL, MB_OK); return FALSE; } return TRUE; } Nun sind wir so weit, dass wir sagen können -> DirectDraw erfolgreich initialisiert. Wir können nun in unser Surface zeichnen. Wie das geht ?? Noch ein wenig Gedult, denn wer so schön DirectX initialsiert hat, der sollte es auch wieder sauber aufräumen !! Das ist an sich nicht viel Aufwand und findet in der Funktion ExitDirectDraw statt, die weder Übergabeparameter erhält noch einen Wert zurückgibt.
Es werden hier nun alle Instanzen von DirectDraw-Objekten wieder freigegeben. Dazu überlegen wir zu erst, welche Instanzen nach der Initialisierung im Speicher vorhanden sind.
Das Ergebnis: Unsere beiden Zeichenflächen und das zentrale DirectDraw-Objekt.
Für einige bestimmt klar - aber dennoch eine Überlegung wert - in welcher Reihenfolge die Objekte abgebaut werden müssen. Und hier ganz klar zuerst die Objekte die zuletzt angelegt wurden und ganz am Ende das zentrale DirectDraw-Objekt. Freigegeben werden die Objekte über die Schnittstellenfunktion Release.
void ExitDirectDraw(void) { lpDPaint7->lpVtbl->Release(lpDPaint7); lpDSurf7->lpVtbl->Release(lpDSurf7); lpDDraw7->lpVtbl->Release(lpDDraw7); }
Wenn wir uns nun die Funtion InitDirectDraw noch einmal genauer ansehen, dann werden wir feststellen, dass nicht in jedem Fall alle Objekte existieren. Wenn die Initialisierung Fehlgeschlagen ist, kann zum einen nur das zentrale DirectDraw- Objekt existieren oder dieses und nur eine der beiden Zeichenflächen. Aus diesem Grunde ist es sinnvoll diese Fälle abzufangen, in dem man fragt, ob die Zeiger auf das Interface vielleicht ins Nirvana (NULL) zeigen, denn in diesem Falle wurde das Objekt nicht angelegt und ein Release würde zu einem Programmfehler führen. Also ist es besser, wenn ExitDirectDraw so aussieht:

void ExitDirectDraw(void) { if (lpDPaint7 != NULL) lpDSurf7->lpVtbl->Release(lpDPaint7); if (lpDSurf7 != NULL) lpDSurf7->lpVtbl->Release(lpDSurf7); if (lpDDraw7 != NULL) lpDDraw7->lpVtbl->Release(lpDDraw7); }
Nun haben wir denke ich ein gutes Fundament geschaffen um zu der eigentlichen Darstellung eines Bildpunktes kommen zu können. In DirectX sind die Bilddaten irgendwo in einem Surface abgelegt. Um diese nun manipulieren zu können, wäre ein Zeiger auf diese Daten wohl eine gute Lösung. Genau diese Zeiger wird beim Aufruf der Funktion Lock einer Zeichenfläche in die Variable lpSurface der DDSURFACEDESC - Struktur geschrieben.
Dieser Zeiger ist jedoch vom Typ VOID - was bei der Manipulation des Bildspeichers beachtet werden sollte.
Der Aufruf dieser Funktion hat allerdings einen kleinen Haken: Er sperrt das gesamte GDI bis zum Aufruf des Gegenstückes UnLock.
Will heissen - zwischen diesen beiden Funktionsaufrufen niemals einen Breakpoint setzen und auch keine MessageBox'en anzeigen. Dies kann unter Umständen zu einem klitze kleinen Systemabsturz führen.
Die nächste Funktion die wir codieren soll also das PaintSurface sperren , einige Manipulationen an den Bilddaten vornehmen und es dann wieder entsperren. Wir nennen diese Funktion einfach ManipulatePaintSurface. Sie wird innerhalb von WinMain direkt nach InitDirectDraw aufgerufen, wenn IsDDraw den Wert TRUE hat.

void ManipulatePaintSurface(void) { BYTE *lpByteScreen; int i; if (lpDPaint7->lpVtbl->Lock(lpDPaint7, NULL, &DDPaintDesc2, DDLOCK_WAIT, NULL) == DD_OK) { //hier niemals einen Breakpoint platzieren oder mit dem Debugger hinein springen //einen byte-Pointer auf die Adresse der Bilddaten setzen lpByteScreen = DDPaintDesc2.lpSurface; //die ersten 50 Byte im Screen mit 255 belegen: for (i=0;i<50;i++) { *lpByteScreen = 255; lpByteScreen++; } //Surface entsperren lpDPaint7->lpVtbl->Unlock(lpDPaint7, NULL); } else MessageBox(NULL, "Konnte Zeichenfläche nicht sperren", NULL, MB_OK); }
Nun haben wir zwar in unserer Zeichenfläche irgendwelche Bildpunkte manipuliert, werden aber auf dem Monitor nichts der gleichen sehen. Wir haben da ja noch die zweite Zeichenfläche die ja den Bildschirm "repräsentiert".
Es gilt also die geänderte Zeichenfläche irgendwie auf den Bildschirm bzw. in unser Anwendungsfenster zu "zaubern". Das ganze soll immer dann geschehen, wenn das Anwendungsfenster dargestellt wird. Also während der WM_PAINT-Message.
Zum bessere Verständnis hier einmal die Ursprüngliche Messagebehandlungsroutine als Quelltext:

LRESULT WINAPI WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT ps; switch (msg) { case WM_PAINT: //das sollte ja bekannt sein BeginPaint(hwnd, &ps); //hier wird dann die DX-Zeichenfläche geswitcht EndPaint(hwnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); return TRUE; default: return DefWindowProc(hwnd, msg, wParam, lParam); } return FALSE; }
Wie oben angedeutet wird sich die Darstellung der Zeichenfläche zwischen BeginPaint und EndPaint abspielen. Diese beiden Befehle - die immer dieses Paar bilden - sorgen dafür, dass die WM_PAINT-Message vom Messagequeue entfernt wird. Normalerweise wird die WM_PAINT-Message ununterbrochen an ein Fenster gesendet, aber wenn es weder die Position, noch die Grösse ändert braucht es ja nicht neu gezeichnet werden.
Wie funktioniert nun aber dieser Switch der Zeichenflächen ?
Die Antwort ist recht einfach. Die Memberfunktion Blt der Zielzeichenfläche vermag es die Bilddaten einer beliebiegen Quellzeichenfläche auf sich selbst zu kopieren (auch blitten genannt). Dabei wird jeweils ein rechteckiger Ausschnitt der Quelle in einen rechteckigen Ausschnitt des Zieles kopiert.
In WindowProc werden zu diesem Zweck zwei Rechtecke vom Typ RECT definiert. Die Quelle sei SRect (SourceRect) und das Ziel sei DRect (DestinationRect).
Da nun die Zielzeichenfläche den gesammten Bildschirm repräsentiert, ist deren Koordinatensystem auch so ausgelegt. Das heisst, dass ein Rechteck mit den Punkten ((0,0),(200,200)) oben links auf dem Bildschirm wäre und nicht oben links in unserem Fenster. Da kommt uns die GDI-Funktion MapWindowPoints gerade recht. Sie vermag es Koordinaten die sich auf ein Fenster beziehen auf Koordinaten die sich auf den gesammten Bildschirm beziehen umzurechnen.
Das Anzeigen der Arbeitszeichenfläche auf dem Bildschirm sieht im ganzen dann wie folgt aus:

//nur wenn DirectDraw erfolgreich initialisiert wurde if (IsDDraw == TRUE) { //die Quelle soll ja die gesammte Arbeits-Zeichenfläche sein //diese haben wir mit einer Höhe und Breite von 200 erstellt!! SRect.top = 0; SRect.left = 0; SRect.right = 199; SRect.bottom = 199; //das Zielrechteck soll die selben Masse haben. //dadurch kommt es zu keiner Verzerrung. DRect = SRect; //die DRect-Koordinaten beziehen sich auf unser Anwendungsfenster //sie werden auf Bildschirmkoordinaten umgerechnet //der letzte Parameter 2 gibt an wieviele Punkte in DRect zu ändern sind MapWindowPoints(hwnd, NULL, (LPPOINT)&DRect, 2); //Der Blitter kopiert die Bilddaten //das Flag DDBLT_WAIT gibt an, dass dieser Vorgang wartet bis der Blitter //frei ist um ihn auszuführen lpDSurf7->lpVtbl->Blt(lpDSurf7, &DRect, lpDPaint7, &SRect, DDBLT_WAIT, NULL); }
Einer kurzen Erläuterung bedarf mit sicherheit das Flag DDBLT_WAIT. Das kopieren der Bilddaten wird vom sogenannten Blitter übernommen. Dieser Blitter kopiert aber auch die Bilddaten aller anderen Anwendungen hinund her. Das kann dazu führen, dass er zu dem Zeitpunkt zu dem wir die Zeichenfläche blitten wollen, gerade beschäftigt ist. Nun würde der Blt-Befehl mit der Fehlermeldung, dass der Blitter gerade "belegt" ist seine Artbeit verweigern. Das hilft uns aber nicht weiter, da wir unbedingt die Bilddaten kopieren wollen. An dieser Stelle kommt dieses Flag zum Zuge, was dem Blt-Befehl sagt, dass er warten soll bis der Blitter frei ist und dann die Zeichenflächen kopieren soll.
An dieser Stelle sind wir nun soweit, dass wir das Projekt compilieren, linken und testen können.

Für alle diejenigen, die den Quellcode nicht hier abtippen möchten, gibts den Quelltext (zip-File) in elektronischer Form

Nachdem das Projekt erstellt wurde, können wir das Programm starten. Unser Anwendungsfenster sollte dann wie hier rechts dargestellt aussehen. Oben links im Fenster sollte sich ein dünner wiesser Strich befinden.
waagerechte weisse Linie

Nachdem nun sichergestellt ist, dass wir die Manipulationen die wir in der Arbeitszeichenfläche vornehmen auch auf unserem Bildschirm erscheinen, werden wir uns etwas intensiver mit der manipualtionsfunktion ManipulatePaintSurface auseinandersetzen.
In jedem SChleifendurchlauf erhöhen wir den Zeiger auf unsere Bilddaten um ein Byte und erhalten somit eine waagerechte Linie. Eine "gute" Manipulation lässt aber auch die Darstellung einer Senkrechten Linie zu.
Vom theoretischen Ansatz ist das eigentlich ganz leicht: Da im Speicher alle Zeilen der Bilddaten direkt hinter einander liegen bräuchte man den Zeiger nur immer weiter um ein Byte erhöhen bis man irgendwann in der nächsten Zeile landet. Da es recht schwierig zu ermitteln ist wie viele Bytes eine Zeile hat, stellt uns diese Information die Funktion Lock in der Variablen lPitch der DDSURFACEDESC-Struktur zur Verfügung.
Folgende Änderung der Funktion ManipulatePaintSurface hat eine senkrechte Linie zur Folge.

void ManipulatePaintSurface(void) { BYTE *lpByteScreen; int i; if (lpDPaint7->lpVtbl->Lock(lpDPaint7, NULL, &DDPaintDesc2, DDLOCK_WAIT, NULL)==DD_OK) { //hier niemals einen Breakpoint platzieren oder mit dem Debugger hinein springen //einen byte-Pointer auf die Adresse der Bilddaten setzen lpByteScreen = DDPaintDesc2.lpSurface; //in den ersten 50 Zeilen jeweils das erste Byte mit 255 belegen for (i=0;i<50;i++) { *lpByteScreen = 255; //Zeiger um die Länge einer Zeile in bytes erhöhen lpByteScreen += DDPaintDesc2.lPitch; } //Surface entsperren lpDPaint7->lpVtbl->Unlock(lpDPaint7, NULL); } else MessageBox(NULL, "Konnte Zeichenfläche nicht sperren", NULL, MB_OK); }
Das Ergebnis dieser Änderung sehen wir rechts. Erstaunlicherweise ist die Linie nun nicht mehr weiss sondern blau !
senkrechte blaue Linie

Wenn man nun diese beiden Methoden der Manipultion verknüpft kann man recht einfach die senkrechte Linie weiter in die Mitte rücken oder die waagerechte Linie mehr nach unten zeichnen. Sogar schräge Linien sind möglich.
An dieser Stelle wünschen wir viel Freude beim experimentieren.
Weiter geht es dann im zweiten Teil.