Die DeviceCapabilities()-Funktion
Die DeviceCapabilities-Funktion ermittelt für Sie die möglichen Einstellungen Ihres geladenen Drucker Device-Treibers. Genau wie die ExtDeviceMode-Funktion, wird auch die DeviceCapabilities-Funktion vom Druckertreiber selbst exportiert. Aus diesem Grund gilt hier in allen Punkten das bereits Gesagte aus dem vorausgegangenen Kapitel und muß nicht wiederholt werden.
Wenn Sie z.B. wissen wollen, aus welchem Schacht der Drucker derzeit lt. Treibereinstellung sein Papier einzieht, fragen Sie diese Information per ExtDeviceMode-Funktion (Flag: DM_IN_BUFFER + DM_OUT_BUFFER) ab und werten das Member <dmDefaultSource> der outDEVMODE-Struktur aus. Wenn Sie aber nun in Erfahrung bringen wollen, welche anderen Papierschächte noch als Alternative zur Verfügung stehen, müssen Sie die im Folgenden näher erläuterte DeviceCapabilities-Funktion bemühen.
Hier die "API"-Syntax der DeviceCapabilities() - Funktion:
DWORD DeviceCapabilities
(lpszDevice, lpszPort, fwCapability, lpszOutput, lpdm)
1.) LPSTR lpszDevice; /* Zeiger auf Device-Name */
2.) LPSTR lpszPort; /* Zeiger auf Port-Name */
3.) WORD fwCapability; /* Device-Fähigkeit, die abgefragt werden soll */
4.) LPSTR lpszOutput; /* Zeiger auf Ausgabebereich */
5.) LPDEVMODE lpdm; /* Zeiger auf DEVMODE-Struktur */
Je nach dem, ob die Funktion erfolgreich ausgeführt werden konnte, gibt diese einen Wert in Abhängigkeit zum "Fragewert" in <fwCapability> zurück, oder -1. <-1> signalisiert dann einen Fehler.
Die Parameter 1.) und 2.) sind Ihnen bereits aus den vorherigen Funktionsbesprechungen her bekannt.
Im 3. Parameter wird die zu erfragende Fähigkeit des Treibers nummerisch kodiert übergeben. Bitte studieren Sie dazu die Tabelle im Anschluß.
Im 4. Parameter wird ein Zeiger auf den Resultatsbereich übergeben, in den die Funktion Ihr Ergebnis hineinkopieren soll. An dieser Stelle wird das Handling der Funktion dann wieder einmal recht komplex:
Da die Funktion nur einen Wert vom Typ DWORD direkt "returnen" kann (reicht nur für große Zahlenwerte), werden längere Resultate (in Form von textlichen Aufzählungen) in einem von Ihnen reservierten, getrennten Ausgabebereich zurückgegeben. Der von mir so genannte "Resultatsbereich" ist in der C-Programmiersprache einfach ein "array of bytes", was wir unter FoxPro als einen String realisieren können. Der Zeiger auf diesen String muß (da im API "LPSTR") unter FoxPro während der Funktionsregistrierung als "@C"-Typ vereinbart werden. Die Länge des Resultats-Strings ist aber immer variabel - Sie können ja schließlich noch gar nicht wissen, wieviele z.B. Papiereinzugsmöglichkeiten denn nun gleich vom Device-Treiber in Form einer Auflistung zurückkommen. Aus diesem Grund besteht als Sonderfall die Möglichkeit, in dem Parameter anstelle eines korrekten Zeigers auf einen String, den NULL-Wert zu übergeben. Damit erreichen Sie, daß die Funktion ihnen als Rückgabewert die genaue Größe des für Ihre Anfrage notwendigen Speicherbereichs (Strings) liefert. Ok, den NULL-Wert "simulieren" Sie in FoxPro durch Übergabe der (nummerischen) 0. Dies können Sie aber nur dann ausnahmsweise tun, wenn Sie den Parameter als einfachen "C"-String vereinbart haben. Sie sehen also, daß Sie die Funktion DeviceCapabilities() zweimal mit verschiedenen Typenvereinbarungen für den 4. Parameter registrieren werden müssen, um die NULL-Zeigerübergaber realisieren zu können. - Und noch ein weiteres Mal, um verschiedene Return-Werttypen zu vereinbaren (dazu später mehr).
Im 5. Parameter kann ein Zeiger auf eine von Ihnen vorbelegte DEVMODE-Struktur übergeben werden. Wir lassen in unserem Fall den Zeiger aber mit "NULL" belegen, wodurch die Funktion die momentanen Default-Initialisierungswerte für den angegebenen Device-Treiber zurückgibt.
Einige der Abfragewerte liefern die gleichen Informationen, die Sie auch aus den korrespondierenden Members einer DEVMODE-Struktrur für den aktuellen Device-Treiber auslesen könnten. Diese Übereinstimmungen sind in der nachfolgende Tabelle entsprechend vermerkt und werden anschließenden nicht mehr weiter erläutert.
Tabelle: DeviceCapabilities Abfragenummern
Mnemonic Wert entspricht DEVMODE-Member
#DEFINE DC_FIELDS 1 dmFields
#DEFINE DC_PAPERS 2 -
#DEFINE DC_PAPERSIZE 3 -
#DEFINE DC_MINEXTENT 4 -
#DEFINE DC_MAXEXTENT 5 -
#DEFINE DC_BINS 6 -
#DEFINE DC_DUPLEX 7 -
#DEFINE DC_SIZE 8 dmSize
#DEFINE DC_EXTRA 9 dmDriverExtra
#DEFINE DC_VERSION 10 dmSpecVersion
#DEFINE DC_DRIVER 11 dmDriverVersion
#DEFINE DC_BINNAMES 12 -
#DEFINE DC_ENUMRESOLUTIONS 13 -
#DEFINE DC_FILEDEPENDENCIES 14 -
#DEFINE DC_TRUETYPE 15 -
#DEFINE DC_PAPERNAMES 16 -
#DEFINE DC_ORIENTATION 17 -
#DEFINE DC_COPIES 18 -
Die Nummer <DC_PAPERS> (2) erzeugt die Ausgabe einer (Standard-)Papiernummernliste aller unterstützten Papierformate. Diese Integer-Zahlenwerte entsprechen den Einträgen der Tabelle "Die gebräuchlichsten Papiergrößen-Nummern" weiter vorn. Sie müssen Ihren Resultat-String immer "2 Byte-weise" im Lowbyte-Highbyte - Format auswerten. Wenn im <lpszOutput>-Parameter der NULL-Zeiger übergeben wird, erhalten Sie von der Funktion als Rückgabewert die Anzahl der unterstützten Papiergrößen-Nummern.
Die Nummer <DC_PAPERSIZE> (3) erzeugt eine Liste von POINT-Strukturen. Diese Strukturen enthalten immer paarweise die X/Y-Dimensionen (Breite/Länge) aller vom Device-Treiber unterstützten Papiergrößen in 1/10tel Millimetern. Dabei wird die Benennung der Werte für ein gedachtes Hoch-Format (Portrait) ausgegeben. Eine POINT-Struktur ist in der C-Programmierung 4 Bytes lang und besteht aus zwei Integer-Werten im oben bereits erwähnten Low-/High-Byte Format.
Die Nummer <DC_MINEXTENT> (4) gibt eine POINT-Struktur direkt zurück! Diese Struktur enthält die kleinsten Größen, die in den DEVMODE-Members <dmPaperLength> und <dmPaperWidth> eingetragen werden dürfen. Achtung: Im Fall "DC_MINEXTENT" und im noch folgenden Fall "DC_MAXEXTENT" wird ein "echter" DWORD-Wert direkt zurückgegeben! Dieser muß dann als POINT-Struktur interpretiert werden. Wie das? Zum besseren Verständnis hier nochmals die genaue API-Definition einer POINT-Struktur:
typedef struct tagPOINT { /* pt */
int x;
int y;
} POINT;
Die POINT-Struktur definiert die x- und y-Koordinaten eines Punktes.
Member Beschreibung
x X-Koordinate des Punktes (in unserem Fall: Papier-Länge)
y Y-Koordinate des Punktes (in unserem Fall: Papier-Breite)
Die beiden Integer-Werte <int x> und <int y> liegen in der Struktur direkt hintereinander im Speicher und werden auch in dieser Reihenfolge als "DWORD" zurückgegeben. Angenommen die kleinste Papierlänge (x) beträgt 2,56 cm und die kleinste Papierbreite (y) 10,24 cm, dann wären die Werte der POINT-Struktur
int x = 256 und int y = 1024, da ja in 1/10tel Millimeteren zurückgegeben wird. Zerlegen wir nun einmal die INT-Werte in das Low- und High-Byte, so ergibt sich: xLow = 0, xHigh = 1, yLow = 0, yHigh = 4
Die Byte-Folge der Struktur sähe im Speicher dementsprechend aus: --- 0104 --- und würde auch genauso an FoxPro zurückgegeben. Da dies ein 4-Byte langer Wert ist, müssen wir den Rückgabeparameter beim Registrieren der Funktion unter FoxPro als "L"-Typ (LONG) vereinbaren!
Der Rückgabewert als solcher, den FoxPro aus den 4 Bytes "interpretiert", interessiert uns nicht. Wir brauchen jeweils nur die zwei "Teilwerte", die in ihm "enthalten" sind. Ich persönlich benutze in solchen Aufgaben immer zwei einfache Bit-Operationen (die entsprechenden Funktionen sind im StartUp-Programm der Demo enthalten). Die 4 Bytes sehen in unserem Fall für die Bit-Operationsfunktionen wie folgt aus:
32 <-----------------------------------------------1 Bit
00000000 00000001 00000000 00000100
xLow xHigh yLow yHigh
Papierlänge Papierbreite
wobei das linke der 32 Bit das höchstwertige und das ganz rechte das niederwertigste (Bit #1) für die Bit-Operationen darstellt.
Um den X-Wert (Papierlänge) als "brauchbaren INT-Wert" aus der DOUBLE-Variablen zu erhalten, wird eine C-ähnliche Bit-Schiebeoperation benutzt. Wir "schieben" die 32 Bits 16 mal "nach rechts". Die rechts "herausfallenden" Bits verschwinden und links werden 0-Bits "aufgefüllt", sodaß nach erfolgter Schiebeoperation die Bitfolge wie folgt aussieht:
00000000 00000000 00000000 00000001
xLow xHigh yLow yHigh
Dies wiederum interpretiert FoxPro als den (richtigen) Wert 256. Auf der anderen Seite sollen nur die Bytes des Y-Wertes "stehen bleiben". Dies erreichen Sie, wenn Sie den Rückgabewert mit dem Wert 65535 bit-weise "AND"-verknüpfen:
32 <-----------------------------------------------1 Bit
00000000 00000001 00000000 00000100 <- Rückgabewert
AND 00000000 00000000 11111111 11111111 <- 65535
-----------------------------------------------------------------
ergibt 00000000 00000000 00000000 00000100 <- 1024
xLow xHigh yLow yHigh
Auf diese Weise können Sie zwei Grenzwerte aus nur einem Funktions-Rückgabewert ermitteln. Dies ist, zugegebener Maßen etwas umständlich in FoxPro zu realisieren, aber es funktioniert, wie Sie leicht im Demoprogramm erkennen können.
Die Nummer <DC_MAXEXTENT> (5) gibt ebenfalls eine POINT-Struktur zurück! Diese Struktur enthält die maximalen Größen, die in den DEVMODE-Members <dmPaperLength> und <dmPaperWidth> eingetragen werden dürfen. Ansonsten gilt komplett das unter "DC_MINEXTENT" Gesagte.
Die Nummer <DC_BINS> (6) erzeugt eine Liste von Schacht-ID Nummern im WORD-Format. Diese 2-bytigen Werte werden im <lpzOutput> Rückgabe-String abgelegt. Wird der NULL-Zeiger auf diesen String beim Funktionsaufruf übergeben, ist der Returnwert der Funktion die Anzahl der unterstützten Papier-schächte. Die zurückgegebenen Papierschachtnummern finden später Verwendung im <dm_DefaultSource>-Member Ihrer DEVMODE-Strukturen.
Die Nummer <DC_DUPLEX> (7) gibt die Fähigkeit des "beidseitigen Ausdrucks" = Duplex-Support zurück. Ist der Return-Wert der Funktion 1, ist der Druckertreiber dazu fähig, bei einer 0 nicht.
Die Nummer <DC_BINNAMES> (12) erzeugt eine Liste aller Papierschacht-Namen. Wie bei "DC_BINS" können Sie durch NULL-Zeigerübergabe die Anzahl der Schachtnamen ermitteln. Ein Schachtname ist immer genau 24 Zeichen lang. Für die Ergebnislisten "DC_BINS" und "DC_BINNAMES" brauchen Sie übrigens nur einmal die Anzahl der möglichen Bins ermitteln, da beide Funktionsaufrufe korrespondierende Bin-Nummern/Namen zurückliefern. Im Demo-Programm werden diese Informationen dann auch paarweise in einem (2-dimensionalen) Array verwaltet.
Die Nummer <DC_ENUMRESOLUTIONS> (13) erzeugt eine Liste aller möglichen Druck-Auflösungen (dpi). Mit dem NULL-Zeiger holen Sie sich auch bei dieser Funktions-Variante die maximale Anzahl aller möglichen Auflösungen als Return-Wert ab. Die Liste ist als Paare von LONG-Integers organisiert, die jeweils die horizontale und vertikale Auflösung in dpi wiedergeben. Eine Variable vom Typ "LONG int" ist 4 Bytes lang. Ein Paar belegt also insgesamt 8 Bytes. Eine Auswertungstoutine finden Sie im Init-Code des DEMO-Screens "devcapas.spr".
Die Nummer <DC_FIELDDEPENDENCIES> (14) erzeugt eine Liste von Dateinamen. Die dort zurückgegebenen Dateien müssen alle vom Device-Treiber (DRV-Datei) nachgeladen werden. Jeder Dateinamenseintrag in der Liste muß mit 64 Bytes reserviert werden. Die Gesamtanzahl nachzuladender Dateien erhalten Sie (wie üblich) durch NULL-Zeiger Übergabe beim Aufruf. Jeder Dateiname wird durch CHR(0) beendet.
Die Nummer <DC_TRUETYPE> (15) gibt als Return-Wert die TrueType-Fähigkeiten des Druckertreibers zurück. Die möglichen Rückgabewerte entnehmen Sie der folgenden Tabelle:
Bit-Feld des Return-Werts (DWORD) für DC_TRUETYPE
#DEFINE DCTT_BITMAP 1 && Device kann TrueType-Fonts als Grafik
drucken (Nadeldrucker)
#DEFINE DCTT_DOWNLOAD 2 && Device kann TrueType-Fonts "downloaden"
(z.B. PostScript, PCL)
#DEFINE DCTT_SUBDEV 4 && Device kann TrueType-Fonts durch eigene
ersetzen (z.B. PostScript)
Die Nummer <DC_PAPERNAMES> (16) erzeugt eine Liste von unterstützten Papiernamen wie z.B. "Letter" oder "Legal". Jeder zurückgegebene Name ist genau 64 Zeichen lang und wird durch ein CHR(0) abgeschlossen. Die Anzahl kann wieder durch unseren NULL-Zeigertrick herausgefunden werden und entsprich genau der Anzahl von <DC_PAPERS>.
Die Nummer <DC_ORIENTATION> (17) gibt als Return-Wert die Gradzahl an, um die ein Ausdruck gegen den Uhrzeigersinn vom Treiber gedreht wird, um "Landscape-Druck" zu realisieren. So "kippen" einige Druckertreiber das Blatt nach links (<DC_ORIENTATION> = 90), andere nach rechts (<DC_ORIENTATION> = 270). Ist kein Landscape-Druck möglich, wird < 0 > zurückgegeben.
Die Nummer <DC_COPIES> (18) gibt als Return-Wert die Anzahl der Kopien zurück, die der Drucker maximal von einem Blatt ausgeben kann.
Abschließend will ich Ihnen ein kurzes Listing präsentieren, an dem Sie die verschiedenen Registrierungsvarianten der DeviceCapabilities-Funktion studieren können:
* Registrierung für:
* INT_ret = DEVICECAPABILITIES(DeviceName, PortName, Item, NULL,NULL)
m.LnDevCap1 = REGFN("DEVICECAPABILITIES","@C@CICC","I",lcDrvFileName)
* <lcDrvFileName> ist der DOS-Name des Device-Treibers, der die Funktion
* exportiert damit das Auswerten des Rückgabewerts einfach bleibt, wird
* dieser fürs erste als int-Wert vereinbart, denn alle Aufrufe mit NULL-
* Zeiger lieferen Zahlen im Wertebereich bis max. 65535 zurück.
*
* Registrierung für:
* INT_ret = DEVICECAPABILITIES(DeviceName, PortName, Item, output ,NULL)
m.LnDevCap2 = REGFN("DEVICECAPABILITIES","@C@CI@CC","I",lcDrvFileName)
* in den meisten Fällen reicht der Wertebereich der Integer-Variablen für
* die Rückgabe aus.
* Registrierung für:
* DBL_ret = DEVICECAPABILITIES(DeviceName, PortName, Item, output ,NULL)
m.LnDevCap3 = REGFN("DEVICECAPABILITIES","@C@CI@CC","L",lcDrvFileName)
* für den Spezialfall, daß der Rückgabewert 32Bits lang ist
* (siehe POINT-Struktur)
So, nun halten Sie das Rüstzeug um Druckertreiber unter FoxPro zu laden und abzufragen fast komplett in Händen. Weiterhin sollten Sie jetzt in der Lage sein, sich die Frage: "Wie kann ich feststellen, ob die in meinen Reporten benutzten Druckertreiber auf dem Zielsystem verfügbar sind?" selbst zu beantworten. Wenn nicht, studieren Sie im Anschluß an diese Lektüre am besten den Quellcode des Demo-Programms.
Bevor wir uns nun aber wieder FoxPro und seinen Reports zuwenden, werden Sie von mir noch mit einer API-Struktur des Windows-GDI bekanntgemacht, die (wie sie bald sehen werden) auch von FoxPro direkt in den Reporten verwendet wird. Es handelt sich dabei um die s.g. DEVNAMES-Struktur.
Die DEVNAMES-Struktur
Die DEVNAMES-Struktur wird in der Windows-Programmierung dazu verwendet, bestimmte Elemente der Print-Dialogbox zu initialisieren. Diese Dialogbox ist übrigens eine Windows Standard-Dialogbox, die in der Lage ist zwei Dialog-Fenster auszugeben. Diese beiden Fenster tragen die Titel "Druckereinrichtung" (mit Unterfenster "Optionen") und "Drucken".
Sie können unter FoxPro ab Version 2.6 das "Druckereinrichtung"-Fenster direkt mit der SYS(1037)-Funktion aktivieren. Das andere Fenster "Drucken" wird mit dem Befehl SET PRINTER ON PROMPT aktiviert. Beide Dialogboxen unter FoxPro bauen auf der Windows-Funktion PrintDlg() auf. Die PrintDlg()-Funktion benötigt wieder eine sehr komplexe Struktur mit Namen PRINTDLG. Die Beschreibung der Funktion und der Initialisierungsstruktur würde den Rahmen an dieser Stelle sprengen. Sie sollten zum besseren Verständnis nur wissen, daß ein Member der PRINTDLG-Struktur ein Zeiger eben auf oben genannte DEVNAMES-Struktur enthält.
Die DEVNAMES-Struktur hat, wie im SDK beschrieben, folgenden Aufbau:
typedef struct tagDEVNAMES { /* dn */
UINT wDriverOffset;
UINT wDeviceOffset;
UINT wOutputOffset;
UINT wDefault;
/* optional data may appear here */
} DEVNAMES;
Im Member <wDriverOffset> wird der Offset auf den DOS-Treibernamen abgelegt.
Im Member <wDeviceOffset> wird der Offset auf den Device-Namen abgelegt.
Im Member <wOutputOffset> wird der Offset auf den Port-Namen abgelegt.
Das Member <wDefault> ist ein Flag, das angibt, ob es sich bei dem Druckertreiber um den Default-Drucker handelt.
Wie eingangs erwähnt wird diese Struktur einerseits zur Initialisierung der PrintDlg-Funktion und deren Dialogfenster verwendet, sie enthält aber andererseits auch beim Beenden der Dialoge (gesetzt den Fall der Anwender hat den OK-Knopf gedrückt) die vom Anwender gemachte Druckertreiber-Auswahl, bzw. die Änderungen der Druckertreiber-Einstellungen. Wichtig beim Auswerten der Struktur ist das Flag <wDefault>. Wurde nämlich das Dialogfenster "Druckereinrichtung" geöffnet und der Anwender selektierte eben nicht den Standard-Drucker, ist das Flag nicht gesetzt. Abschließend hier ein Screenshot zur Veranschaulichung:
Druckjob-Abwicklung unter FoxPro
Bravo, bislang haben Sie sich gut gehalten. Kommen wir nun langsam zu unserem "roten Faden" zurück. Ich stellte ganz zu Anfang meiner Ausführungen im Kurzabriß der Funktionsabläufe unter Punkt 4.) fest:
"Anhand der Anwendervorgabe oder der reportinternen Werte wird nun via DeviceContext und entsprechend initialisierten DEVMODE-Strukturen der Druckertreiber für das gewünschte Druckresultat eingestellt....."
Tag und Tag2 entschlüsselt
Da mich ursprünglich das Problem trieb, FoxPro's Reports zu manipulieren, nahm ich mir irgendwann auch einen Report "genauer unter die Lupe", sprich: ich lud einen Report (FRX-Datei) als ganz normale Tabelle in einen Arbeitsbereich und stellte die Feldinhalte in einem BROWSE-Fenster dar. Dabei traf ich dann bei allen FoxPro-Reporten auf zwei Memo-Spalten, die immer in Satz #1 merkwürdigen "Sondermüll" in Form von fast unlesbaren Steuerzeichen besaßen. Die beiden Spalten heißen TAG und TAG2. Sie sehen hier einen Screenshot eines als Tabelle geöffneten Reports im Browse-Fenster:
Das spätere Wissen um den Aufbau und die Funktion der DEVNAMES-Struktur ließ dann irgendwann bei mir den Groschen fallen. Lassen Sie sich in einem ähnlich "Testaufbau" an Ihrem PC einmal mit folgenden Eingaben im Befehlsfenster die einzelnen Zeichenwerte der ersten Bytes im TAG-Fenster des Browse ausgeben:
? ASC(SUBSTR(tag,1,1)) ergibt: 32 (Bemerkung: ist in o.Abb.
nicht zu sehen, da ASC(32) = Leerzeichen)
? ASC(SUBSTR(tag,2,1)) ergibt: 0
? ASC(SUBSTR(tag,3,1)) ergibt: 8
? ASC(SUBSTR(tag,4,1)) ergibt: 0
? ASC(SUBSTR(tag,5,1)) ergibt: 37
? ASC(SUBSTR(tag,6,1)) ergibt: 0
In dem hier abgebildeten Beispiel ergeben die ersten 6 Bytes paarweise im Low- High-Byte Format ausgewertet jeweils die Offsets auf den (DOS)Treiber-Namen, den Device-Namen und den Port-Namen. Dies können Sie direkt am Bildschirm nachzählen! Der siebte und achte Eintrag im TAG-Memofeld spiegelt dann den Flagwert (Default-Druckertreiber: Ja/Nein?) wieder:
? ASC(SUBSTR(tag,7,1)) ergibt: 1
? ASC(SUBSTR(tag,8,1)) ergibt: 0
In meiner Abbildung oben war der gewählte Devicetreiber für den "Canon Bubble-Jet BJ-300" zur Zeit der Report-Erstellung der aktuelle Default-Drucker und somit das Flag-Member (Bytes #7+#8) als Integerwert=1 "gesetzt". Es folgen die CHR(0)-terminierten Namen im Klartext.
Damit stand fest:
Im Memofeld TAG und dort nur im ersten Satz eines jeden Reports speichert FoxPro die DEVNAMES-Stuktur nebst anhängigen Namen als 1. Teil seiner aktuellen Drucker-Umgebung ab. |
Dies können Sie leicht nachvollziehen, indem Sie den Report mit MODIFY FORM.... zur Bearbeitung einladen und einmal einen anderen Druckertreiber voreinstellen - erneut im Browse-Fenster geladen erscheinen dann die Namen des neuen Druckertreibers.
Mit diesem Erfolg im Rücken fiel mir die nächste Entschlüsselung bereits leichter, da ich nun eine Ahnung bekommen hatte, wonach ich im Memofeld TAG2 suchen sollte. Wie Sie sich schon richtig denken können (warum hätte ich sonst im Theorieteil soviel Aufhebens darum machen sollen) handelt es sich um eine DEVMODE-Struktur!
Die ersten 32 Bytes beinhalten dort den Device-Namen. Allerdings müssen nicht alle Bytes von diesem belegt sein. Vielmehr beendet das Auftreten des ersten CHR(0) den Device-Namen, der Rest; bis einschließlich Byte #32) sind dann "undefiniert" und können durchaus "seltsame Zeichen" enthalten. Darauf folgen dann bis zum Byte #68 die Members des feststehenden Teils der DEVMODE-Struktur. Schauen wir uns einmal für das oben abgebildete Beispiel die Gesamtlänge des Memofeldes an:
?LEN(tag2) ergibt: 160
und danach den ausgerechneten Wert der <dmSize>+<dmDriverExtra> Members im Memofeld:
? ASC(SUBSTR(tag2,37,1)) ergibt: 68
? ASC(SUBSTR(tag2,38,1)) ergibt: 0
? ASC(SUBSTR(tag2,39,1)) ergibt: 76
? ASC(SUBSTR(tag2,40,1)) ergibt: 0
der die tatsächliche Größe der abgelegten DEVMODE-Strucktur, nämlich 68 + 76 = 144 ergibt, so besteht eine Differenz von 16 Bytes, die (nach meinem Stand der Ermittlungen) derzeit von FoxPro mit unsinnigen Einträgen belegt werden. Leider findet keinerlei Speicherung von Einstellungen statt, die über das Optionen-Fenster möglich wären (beispielsweise: Farbmischung, Druckqualität, Druckdichte oder Randeinstellungen)! Ein Manko, das Sie aber mit dem hier angeeigneten Wissen ohne Probleme umgehen können - dazu später mehr. Merken Sie sich bislang:
Im Memofeld TAG2 und dort nur im ersten Satz eines jeden Reports speichert FoxPro die DEVMODES-Stuktur komplett mit den Driver-spezifischen Daten als 2. Teil seiner aktuellen Drucker-Umgebung ab. |
Nun wurde mir auch klar, warum ich so manches Mal in CompuServe als Ratschlag der MS-Hotline bei Reportproblemen las, einfach probeweise die Memospalten TAG und TAG2 komplett zu löschen - ganz ohne weitere Erklärungen! Das hört sich mit meinem heutigen Wissen wie die altbewährte "Holzhammermethode" an (man weiß zwar nicht genau warum, aber bei einigen Patienten soll es schon geholfen haben)...
Ihnen wird nun hoffentlich auch klar sein, warum das Löschen aller TAG- und TAG2- Felder u.U. eine Lösung bei "abstürzenden" Reporten sein kann:
Gibt man die Anweisung DELETE TAG ALL und DELETE TAG2 ALL kann man wenigstens nicht den ersten Datensatz vergessen. Löschen müssen Sie indes wirklich nur der ersten Satz und damit auch FoxPro's "Erinnerungsvermögen" an den hinterlegten Drucker. Ist die gespeichert DEVMODE-Struktur im TAG2-Momofeld nämlich wirklich einmal korrupt, so kann das beim Öffnen des Reports zur Ausdruckvorbereitung FoxPro-intern wirklich im schlimmsten Falle bis zum Vollabsturz führen! Findet FoxPro hingegen keinerlei Device-Informationen in den TAG-Feldern, werden die Voreinstellung des mit z.B. der SYS(1037)-Funktion eingestellten, oder des per PROMPT abgefragten Druckers verwendet und zur Initialisierung des Ausdrucks benutzt.
Eingriff am offenen Herzen"
Kommen wir nun zu der Frage: An welchen Stellen kann ich programmtechnisch in FoxPro's Reportabwicklung eingreifen?" und Wie komme ich an FoxPros aktuelle Druckereinstellung bei laufendem Druckjob heran"
Beide Fragestellungen und die von mir gefundenen Antworten sind das Resultat einer Spezialaufgabe, deren Lösung ich mir zum Ziel gesetzt hatte:
Es sollte doch möglich sein, den laufenden Ausdruck unter FoxPro so zu manipulieren, daß z.B. die erste Seite aus Schacht #1 eines Druckers und alle Folgeseiten aus Schacht #2 eines Druckers einziehbar sind. |
Aus meinen bisherigen Ausführungen ist Ihnen nun bekannt, daß ein Druckjob immer über einen DeviceContext abgewickelt wird. Leider, und dies ist im Augenblick das ziemlich größte Problem bei den anderen Windows-Plattformen, gibt es im WIN-API keinerlei Funktionen, die die bereits im System bestehenden DCs entweder aufzählen, oder zuordnen können. Da man aber FoxPro gestatten muß, wenigstens seinen Druckjob anzufangen", sprich man muß FoxPro einen DC für die aktuelle Reportausgabe erzeugen lassen, fehlte mir anfangs eine native API-Funktion, die ein Handle auf FoxPros aktuell benutzten DC zurückliefern konnte.
Die Erleuchtung" kam erst sehr viel später nach nicht enden wollenden Recherchen innerhalb der WIN-API Dokumentationen. Hier ein Abriß in aller Kürze:
1.) Um an einen aktivenDruck DC herankommen zu können, bedarf es folgender API-Funktion:
IsGDIObject = REGFN("IsGDIObject","I","I") && auf mögliches GDI-Objekt
m.GetDeviceCaps = REGFN("GetDeviceCaps","II","I")&& Fähigkeiten ermitteln
Wenn man nun alle (unter einem 16bit-Windows) möglichen DCs mit Hilfe der IsGdiObject()-Funktion abtestet, bekommt man bei bestimmten Testwerten einen Wert > 0 zurückgeliefert. In der Dokumentation zum WIN-API heißt es aber zu besagter Funktion weiterhin sinngemäß: Ein Rückgabewert <= 0 besagt allein eindeutig, daß es sich bei dem betrachteten Handle NICHT um ein GDI-Objekt handelt. Andersherum formuliert bedeutet dies, daß ein positiver Rückgabewert allein nur vermuten läßt, daß es sich bei dem untersuchten Handle um ein GDI-Objekt handelt !
Also muß eine weitere Funktion her, mit der dann der letzte Zweifel ausgeräumt werden kann, dies ist die GetDeviceCaps()-Funktion.
#DEFINE DT_RASPRINTER 2
#DEFINE TECHNOLOGY 2
*
WAIT WINDOW NOWAIT "Ich forsche nach FoxPro-internem DDC..."
FOR i = LnStartHdl TO 65535
IF CALLFN(IsGDIObject,i) > 0 && zeigt mögliches GDI-Objekt an
IF CALLFN(GetDeviceCaps,i,TECHNOLOGY) = DT_RASPRINTER
EXIT && Wir haben ein GDI-Handle erwischt!
ENDIF
ENDIF
*
NEXT
* Scan abgeschlossen
*
* Auswertung
IF i >= 65535
WAIT WINDOW NOWAIT "KEIN FoxPro DruckDeviceContext-Handle gefunden!"
i = -1 && Handle-Wert als Flag setzen
ENDIF
* Handle-Wert zurückgeben
RETURN i
So, wenn jetzt ein Handle auf ein GDI-Objekt (und zwar speziell auf einen Druck-DC) zur Verfügung steht, müssen wir nur noch ermitteln, ob dies auch wirklich ein FoxPro-Handle ist. Es kann ja in einer Multitasking-Umgebung durchaus weitere Anwendungen geben, die gerade parallel zu FoxPro an Druckausgaben arbeiten und somit ebenfalls gültige GDI-Handles auf Druck-DCs etabliert haben.
Dazu benutze ich einen Referenzlauf der Handle-Suchroutine", in der vorm eigentlichen Start von FoxPros Ausdruck alle aktiven Druck-DC in einem Array gelistet werden. Unmittelbar nachfolgend wird nach gestartetem Ausdruck nach dem ersten neuen DC-Handle gesucht, welches dann zwangsläufiger Weise das gesuchte FoxPro-Druck-DC Handle sein muß!
Die Schachtansteuerung
Mit dem nun gültigen Handle kann man (theoretisch) mit Hilfe der Windows-eigenen ESCAPE()-Funktion alle nur erdenklichen Vorgänge beeinflussen.
* Registriere "Escape"-Funktion
m.FnEscape = RegFn("Escape","IIICC","I")
IF FnEscape >-1
* OK
ELSE
FnEscape = -1
* Status>> Fehler - Funktion nicht angemeldet!
ENDIF
Allerdings hat die Manipulation der aktuellen Druck-DC-Inhalte in den meisten Fällen auch negative Begleiterscheinungen! So gerät die von FoxPro intern kontrollierte Seitenlänge gern aus dem Gleichgewicht und läßt dann keinerlei geordneten Seitenvorschub mehr zu. Dies liegt in der Tatsache begründet, daß ein Umdefinieren der allermeisten Druck-DC-Eigenschaften nur "zwischen" den einzelnen Seiten durchgeführt werden kann. Beendet man dazu die gerade von FoxPro "angedruckte" Seite in einer eigenen Prozedur mit der API-Funktion
m.EndPage = REGFN("EndPage","I","I"),
damit man nun z.B. einen anderen Schacht anwählen kann und initialisiert dann eine neue Seite mit der API-Funktion
m.StartPage = REGFN("StartPage","I","I"),
so kommt es zu dem bereits angesprochenen verunglückten Seitenvorschub, denn FoxPro hat ja intern nichts von dieser neuen Seite mitbekommen und fährt mit der internen Zeilenzählung fort, als wäre nichts geschehen!
Allerdings, und dies ist eine der wenigen Ausnahmen, kann gerade die veraltete Funktion GETSETPAPERBINS() jederzeit abgesetzt werden (auch zwischen StartPage() und EndPage())! Damit ist es möglich einen neuen Schacht während des Ausdrucks vorzuwählen, Nach dem Auswurf der Seite, zieht FoxPro brav aus dem neuen Schacht ein!
* Auszug aus den Schachtansteuerungsroutinen des Demoprogramms
* hier wird viel Aufhebens um verschiedene Übergabeparameter gemacht!
*
PRIVATE LcBinInfo, m.ok1
*das ist die leere "Grundstruktur" >> LcBinInfo = REPLICATE(CHR(0),12)
m.p6 = MIN(m.p6,m.LnParmCount-6) && <m.p6> "zeigt" auf einen der nachfolgenden restlichen Parameter
m.p6 = MAX(m.p6,1) && Wertebereich von [1 bis PARAMETERS()-6] jetzt eingegrenzt
m.LcParaName = "m.p"+ALLTRIM(STR(m.p6+6,2,0))
* EVAL(m.LcParaName) ist die zu setzende Binnummer
LcBinInfo = Int2Chr(EVALUATE(m.LcParaName))+REPLICATE(CHR(0),10) && LpInData - BinInfo-String erzeugen
* jetzt ESCAPE() aktivieren
m.ok1 = CallFn(m.p5, m.p2,GETSETPAPERBINS,LEN(LcBinInfo),LcBinInfo,0)
IF m.ok1>0
WAIT WINDOW TIMEOUT 0.5 "Schachtansteuerung für Typ #"+;
STR(EVALUATE(m.LcParaName),4,0)+" erfolgreich..."
ELSE
WAIT WINDOW TIMEOUT 5 "Schachtansteuerung für Typ "+;
STR(EVALUATE(m.LcParaName),4,0)+" Fehler: "+STR(m.ok1,5,0)
ENDIF
* und fertig...
Ein weiterer gravierender Nachteil beim "heimlichen" Manipulieren des FoxPro-Druck-DCs ist der "Verlußt" einer von FP addressierten CallBack-Routine, die es dem Anwender ermöglicht den laufenden Ausdruck abzubrechen, wodurch der Report bis "zum bitteren Ende" abgearbeitet werden muß. Dieser Verlußt kann aber glücklicherweise durch die Implementation einer dementsprechenden eigenen Routine ausgeglichen werden.
Weitere wissenswerte Details
Längst nicht alle Informationen zu den Druckertreibern können nur direkt über FLL-Calls abgerufen werden!
Beachten Sie z.B. die (ab FPW2.6 verfügbare) PRTINFO()-Funktion:
Die Funktion PRTINFO(<AusdrN>) ermöglicht die Abfrage der aktuellen Druckereinstellungen von FoxPro für Windows, die im FoxPro-Dialogfeld Druckereinrichtung festgelegt werden. Zur Anzeige des Dialogfeldes "Druckereinrichtung" wählen Sie entweder den Befehl <Druckereinrichtung...> aus dem Menü Datei, oder geben Sie im Befehlsfenster den Befehl SYS(1037) ein. Hier können Sie alle Informationen zu
<AusdrN>=1 (Formatlage z.B. Hoch oder Quer)
<AusdrN>=2 (Seitenformat: z.B. A4 oder German Standard Fanfold)
<AusdrN>=3 (Seitenlänge) Seitenlänge (in Zehntelmillimetern)
<AusdrN>=4 (Seitenbreite) Seitenbreite (in Zehntelmillimetern)
abrufen.
Mit der Funktion SYS(1037) können Sie bekanntlich den Anwender Voreinstellungen tätigen lassen, diese können danach mit der PRTINFO()-Funktion ausgewertet werden, um sie dann abschließend mit den erforderlichen API-Spezialfunktionen in die Binärform der TAG- u. TAG2-Felder zu überführen. Dieses auf den ersten Blick doch recht umständliche Unternehmen erhält dann einen Sinn, wenn man bedenkt, daß ein Report nur dann auch die mit der SYS(1037) voreingestellten Parameter berücksichtigt, wenn die beiden gerade genannten Binärfelder entweder a.) leer sind, oder b.) genau den vom Anwender eingestellten Angaben entsprechen. Anderenfalls (TAG- u. TAG2-Felder enthalten abweichende Einstellungen) erfolgt der Ausdruck immer zu den "TAG"-Vorgaben, was oftmals schon als eine Art Fehler interpretiert worden ist, da selbst MS diesen Umstand nicht eindeutig zu erläutern weiß (oder wenigstens wußte)!
Als Merkregel gilt:
TAG- und TAG2- Memo-Felder in Satz #1 der Report-Tabelle leer
- > SYS(1037)-Vorgaben aktiv!
TAG- und TAG2- Memo-Felder in Satz #1 der Report-Tabelle nicht leer
- > Nix anderes gilt als das TAG- bzw. Das TAG2-Info
Das Geheimnis der verschwindenden Systemresourcen
Dieses Rätsel ist bislang ungelöst, und zwar von Microsoft Deutschland (kleiner Scherz)!
Allerdings weniger lustig ist der Umstand, daß in der deutschen FPW2.6-Version die Systemresourcen einbrechen können, wenn grafische Elemente wie Striche o.ä. im Layout enthalten sind. Bis das Rätsel von MS Deutschland endlich gelöst werden wird (deutsches Update der ESL), empfiehlt es sich, Linien (besonders im Detail-Bereich bei langen Listings) durch die altbekannten Minus-, Gleichheitszeichen oder Unterstriche zu ersetzen!
Neuerungen bei VFP
Die PRTINFO()-Funktion wurde in VFP stark erweitert und kann nun auch zur Ermittelung von gerade nicht aktiven (aber im System installierten) Drucken verwendet werden! Die Funktion kann nun wie folgt aufgerufen werden:
m.LnErgebnis = PRTINFO(<AusdrN:Abfrageindex>,<AusdrZ:Druckername>)
Für die Abfrage-Indexnummer sind nun Werte von 1 - 13 möglich und für den <AusdrZ:Druckername> alle derzeit im System installierte Druckernamen. Weitere Details entnehmen Sie der VFP-Online Hilfe.
Neue in VFP-Funktion dient die Funktion GETPRINTER() dazu das Windows-Dialogfeld Druckereinrichtung anzuzeigen den Namen des Druckers zurückzugeben, den Sie dort auswählen.
Weiterhin neu in VFP ist die Funktion APRINTERS(), die alle derzeit installierten Drucker nebst ihren Ports in ein übergebenes Array schreibt, wie das folgende kurze Listing veranschaulichen soll.
IF APRINTERS(gaPrinters) > 0 && Wenn Druckertreiber installiert
DISPLAY MEMORY LIKE
ELSE
WAIT WINDOW 'Es sind keine Druckertreiber installiert.'
ENDIF
Dieses Array ist derzeit leider nur zweispaltig, ich habe aber bereits angeregt, daß in einer nächsten VFP-Version eine dritte Spalte zugefügt werden sollte, die dann auch noch den DOS-DRV-Namen des Druckers enthalten könnte. Damit wäre dann das Abfragen von Druckerinformationen komplett aus VFP ohne der Einsatz weiterer API-Calls möglich! Die Kombination von Windows-Druckerbezeichnung, Ausgabeport (Anschluß) und DOS-Treibername wird ja bekanntlich von sehr vielen weiterführenden API-Funktionen benötigt, wie Sie heute kennengelernt haben.
Techniken einer reinen FoxPro-Lösung
(Ein Denkanstoß für Diskussionen)
Wenn es bislang darum ging Engpässe und Unzulänglichkeiten des bisherigen FPW-Reportmoduls zu umgehen, wurden durchweg Standard-FoxPro-Programme entwickelt. Auch das FreeWare-Programm von Markus Egger "GenRepoX" gehört zu dieser Familie und sollte hinlänglich bekannt sein. Alles was bislang auf dem Gebiet der Erweiterungen und Verbesserungen von uns Endanwendern entwickelt wurde, lag und liegt zum einen in der Tatsache begründet, daß der bestehende Reportgenerator noch längst nicht das bestmögliche zur erreichende Produkt ist, zum anderen ganz banale Fehlfunktionen enthält, die eine praxistaugliche Rundumeinsatzfähigkeit verhindern. So ist in der FPW2.6-Version der Ausdruck in eine Datei via Anwender-Dialog nicht möglich. Es fehlen auch ganz einfach einige realisierbare Funktionen - daß die möglich sind, macht uns MS aber bei ihren anderen Produkten wie Winword und Excel vor!
Nun kann alles, was bis zur Auslieferung DES Superreportgenerators an Workarounds entwickelt wird, eigentlich auch wirklich nur als Zwischenlösung angesehen werden. Die Frage, die sich mir dabei als Entwickler aufdrängen muß ist: "Wie lange hat meine Lösung Bestand? Und wie lange kann ich aus der dafür notwendigen Investition Nutzen ziehen (damit Geld verdienen)?"
Nun, vielleicht wird ja an einem dieser Konferenztage ein kompeteneter Mann von MS zu diesem Thema ein paar verbindliche Anmerkungen fallen lassen können? Solange aber eine verbesserte Version des FPW/VFP-Reportgenerators nicht in Sicht ist, heißt es, ein paar nette Zwischenlösungen zu finden. Die in diesem Vortrag vorgestellte systemnahe Realisation stößt beim Einsatz auf einer der 32bit-Plattformen an ihre Grenzen.
Eine Alternative zur Lösung der einzelnen Probleme, und zwar unter allen FP-Versionen und auf allen Plattformen(!), sehe ich nur in der Schaffung einer übergeordneten Report-Steuerung, ganz ähnlich dem bereits jetzt unter VFP 3.0 existierenden DataDictionarys.
Session D-PRN / D-FXTO
Programmiertechniken
(Anhang)
Burkhard Stiller
ECHTZEIT DV-Systeme und Engineering
Auf den nächsten Seiten möchte ich Ihnen einige interessante Details näherbringen, die Sie für den effektiven Einsatz der REGFN()- und CALLFN()- Funktionen der Foxtools.Fll - Library kennen sollten. Weiterhin stelle ich Änderungen und Ergänzungen unter VFP 3.0 vor und gehe auf Probleme der Multi-Platform-Entwicklung ein.
Wie die meisten unter Ihnen sicherlich wissen, erlauben diese beiden Funktionen die Registrierung (Einbindung) von (externen) WIN-API Funktionen innerhalb von FoxProW. Eine WIN-API Funktion wird mit Hilfe REGFN()-Funktion innerhalb von FoxPro bekannt gemacht. Dabei wird festgelegt, welche Anzahl und Typen von Parametern die externe Funktion bei deren Aufruf benötigt und welchen Datentyp sie bei Beendigung an FoxPro zurückgeben kann. Weiterhin kann noch angegeben werden, in welcher ladbaren Windows-Bibliothek die Funktion zu suchen ist, falls sie nicht in einer der Standard-Bibliotheken enthalten sein sollte, oder bereits geladen wurde.
Eine (beipielhafte) Windows-Funktion mit Namen "MyWinFunction()" benötigt beim Aufruf z.B. einen Parameter vom Typ Text (String) und gibt als Rückgabewert die Anzahl der Buchstaben in diesem String als Integerwert zurück. Die korrekte Registrierung könnte wie folgt aussehen, vorausgesetzt, die Funktion wäre in einer der Standard-Bibliotheken enthalten (user.exe, krnl386.exe und gdi.exe) oder bereits von Windows geladen:
a.) m.LnMyWinFn = REGFN("MyWinFunction","C","I")
In vielen Fällen wird in WIN-API Funktionen aber ein Zeiger auf den auszuwertenden String übergeben. Dies realisieren Sie in FoxProW mit folgender Schreibweise:
b.) m.LnMyWinFn = REGFN("MyWinFunction","@C","I")
hier wird nun vorgeschrieben, daß der Funktionsparameter später unbedingt mit dem vorangestellten @-Zeichen (also als Referenz) übergeben werden muß.
Um bestimmten "Sonderfällen" in den WIN-API Funktionen Rechnung zu tragen, läßt die CALLFN()-Funktion den Wert nummerisch 0 als "Ausnahmewert" in Parametern zu, die mit der REGFN()-Funktion eigentlich als String-Typ ("C") vereinbart worden sind. Mit dieser Sonderregelung wird dann der allen C-Programmieren bekannte NULL-Wert simuliert.
Dies funktioniert aber nicht, wenn ein Parameter explizit als Zeiger "@C" auf einen String vereinbart wurde. Bei solch einer Typenvereinbarung führt die spätere Übergabe eines NULL-Werts als Aktualparameter zu einer Fehlermeldung!
Interessanterweise wird in allen Fällen in denen in der SDK-Referenz die Zeigerübergabe (theoretisch unter FPW also als "@C" zu deklarieren) auf einen nicht mehr veränderbaren String gefordert wird, auch die "einfache" Registrierung ("C") in FPW akzeptiert und fehlerfrei ausgeführt!
So weit, so gut. Nun gibt es aber im WIN-API, das ja komplett auf "C"-Programmierung aufbaut, bestimmte Datentypen und Konstruktionen, zu denen es in FoxPro's xBase-Dialekt keine Entsprechung gibt. Hier hilft ein wenig Kenntnis über die verschiedenen C-Variablentypen und Strukturen und ein bißchen "Experimentierfreude" weiter.
Hier die Liste aller möglichen Kennbuchstaben die in der REGFN()-Funktion unter FPW zur Datentypvereinbarung benutzt werden können:
I = Integer
L = Long
F = Float/Gleitkommazahl
D = Double/Gleitkommazahl
C = Zeichenkette
dabei müssen die C-Datentypen wie folgt mit den FPW-Datentypen übereinstimmen:
F, D -> muß in Foxpro eine Gleitkommazahl sein
I, L -> muß in Foxpro eine ganze Zahl sein
C -> muß in Foxpro eine als Wert übergebene Zeichenfolge
oder 0 (NULL) sein. Bei Übergabe von nummerisch 0
wird der Nullzeiger übergeben.
Wird in der SDK-Referenz ein Parameter vom Typ LPCSTR vereinbart, versteht man darunter einen 32Bit-Zeiger auf einen "nicht modifizierbaren" String. Solchermaßen vereinbarte Parametertypen können in der REGFN()-Funktion unter FoxPro immer als einfacher String "C"-Typ vereinbart werden. Wird dagegen in der SDK-Referenz ein Parameter vom Typ LPSTR vereinbart, ist dies ein 32Bit-Zeiger auf einen normalen String, der auch in FoxPro in der REGFN()-Funktion als String-Pointer "@C"-Typ vereinbart werden muß!
Als weiteres Hindernis müssen die in C gebräuchlichen Strukturen in FoxPro simuliert werden. Etwas vergleichbares stellen in FPW die Arrays dar, die Sie theoretisch genau wie Strukturen in C einsetzen können. Da die Arrays in FPW aber dynamisch erweiterbar sind, werden die einzelnen Datenelemente der (Array)Struktur in FPW ganz anders im Speicher abgelegt und verwaltet als die statischen Stukturen, die als Übergabeparameterblöcke in den verschiedenen WIN-API Funktionen Verwendung finden! Somit können Arrays von FoxPro nicht als Strukturen in WIN-API Calls benutzt werden.
Als C-Programmierer wissen Sie, daß eine Struktur "nur" eine im RAM des Computers aufeinanderfolgende Anzahl von Speicherstellen ist, die nach einem vorgegebenen Schema (Strukturdefinition) mit den einzelnen Strukturelementen belegt wurde. Jedes dieser Elemente der Struktur besitzt einen vereinbarten Datentyp und dieser wiederum hat eine vorgegebene Byte-Länge. Jetzt kann man in "C" unmittelbar über den Offset (Elementname) auf jedes der Elemente der Struktur und dessen Inhalt zugreifen.
Hier die gebräuchlichen Byte-Längen der diversen C-Datentypen:
Auszugs aus der C/C++ 7.0 Referenz: Verschiedenen Datentypen in der C/C++ Programmierung, Tabelle der Variablentypen, Bytelängen und Wertebereiche
int *2 signed, System abhängig (normal 2 Bytes)
(signed int)
unsigned int *2 unsigned System abhängig (normal 2 Bytes)
char 1 signed char -128 to 127
unsigned char 1 none 0 to 255
short 2 short int, -32,768 to 32,767
(signed short int)
unsigned short 2 unsigned short int 0 to 65,535
long 4 long int, -2,147,483,648 to 2,147,483,647
signed long int
unsigned long 4 unsigned long int 0 to 4,294,967,295
enum 2 none -32,768 to 32,767
float 4 none 3.4E ± 38 (7 digits)
double 8 none 1.7E ± 308 (15 digits)
long double 10 none 1.2E ± 4932 (19 digits)
* Für unseren Fall sind int und short, sowie unsignd int und unsignd short identisch.
Der eigentliche Trick besteht nun darin, zu ermitteln, wie viele Bytes die im SDK beschriebene Struktur einer benötigten API-Funktion hat (unter C würde man sich dies mit der sizeof()-Funktion anzeigen lassen). Hat man dies ermittelt, erstellt man unter FoxPro eine Stringvariable mit der gleichen Anzahl Zeichen, da jedes Zeichen ja genau ein Byte lang ist und somit einem Byte der C-Struktur entspricht (daß die interne Länge des FP-Strings aus verwaltungstechnischen Gründen etwas länger ausfällt, kann vernachlässigt werden). Bei der Registrierung der WIN-API Funktion mittels REGFN() wird dann an der Stelle, an der die API-Funktion den Zeiger auf die Struktur erwartet (wahrheitsgemäß) ein Parameter vom Typ "C" (Zeiger auf einen nicht modifizierbaren String) registriert. Die einzelnen Buchstaben (Bytes) des Strings können nun vor Aufruf der registrierten API-Funktion mit selbstgeschriebenen "LowLevel"-Routinen typgerecht vorbelegt werden. Das "Kauderwelsch" des Stringinhalts wird von der WIN-API Funktion dann strukturelementweise korrekt interpretiert (für die C-Routine im WIN-API sieht's halt aus wie: "vollkommen normal in C programmiert"). Damit wäre das System "überlistet" und das Problem der Strukturdefinitionen unter FoxPro gelöst!
Erweiterungen in VFP 3.0
Visual FoxPro, als echte 32 Bit Entwicklungsumgebung bedient sich heute externer (32Bit-)DLLs via der nun verfügbaren DECLARE DLL Funktion. Um externe 32bit-Funktionen aus 32bit-DLLs aufzurufen, benutzen Sie also nicht mehr die Foxtools.fll Bibliothek (auch nicht die mit VFP 3.0 ausgeliefert wird!) Nur aus Gründen der Rückwärtskompatibilität wurde eine 32bit-Version der FoxTools.Fll Bibliothek beigefügt!
Als Faustregel sollten Sie sich folgendes merken:
1.) Externe 16bit-Funktionen in 16bit-DLLs werden auch unter VFP weiterhin über die Foxtools.fll Bibliothek registriert und aufgerufen.
2.) Externe 32bit Funktionen in allen neuen 32bit-DLLs müssen via DECLARE DLL eingebunden werden.
Hinweis: Achten Sie peinlichst darauf, mit Ihrer neuen VFP-Anwendung nicht die alte Foxtools.fll-Bibliothek von FPW 2.6 weiterzugeben. Pfiffige Anwender könnten nun sagen: Wieso, es gibt doch in beiden Bibliotheken eine Funktion FOXTOOLSVER(), mit der ich die korrekte Bibliothek ermitteln kann!" Dies ist bedingt auch richtig, aber leider läßt sich die 16bit-Version erst gar nicht unter VFP mehr laden, sondern erzeugt sofort einen (abfangbaren) Laufzeitfehler. Also läßt sich auch die Versionsnummer über die interne Bibliotheksfunktion nicht mehr abfragen!
Multi-Plattform-Entwicklung
Damit die Verwirrung innerhalb der verschiedenen Einbindungstechniken noch etwas gesteigert wird, gibt es von Microsoft bekanntlich auch noch verschiedene Windows-Plattformen, unter denen externen DLL-Funktionen mit den gleichen Aufgaben
Na bitte! Jetzt brauchen wir also unter VFP 3.0 auch noch eine Unterscheidungsmöglichkeit, auf welchem der 3 momentan verfügbaren Windows-Betriebssysteme denn unsere Anwendung gerade läuft, damit wir bei der Registrierung und Nutzung der externen DLL-Funktionen keinen Schiffbruch erleiden!
Hier ist eine Möglichleit, mit der die genaue Plattform abgefragt werden kann. Ich verwende s.g. "Wrapper"-Programme, von denen aus die palttformspezifischen Funktionsaufrufe eingebunden werden:
#DEFINE _WIN31 FILE(GETENV(WINDIR)+ \SYSTEM\WIN32S\W32SCOMB.DLL)
#DEFINE _WINNT FILE(GETENV(WINDIR)+ \SYSTEM32\USER32.DLL)
#DEFINE _WIN95 FILE(GETENV(WINDIR)+\SYSTEM\USER32.DLL)
DO CASE
CASE _WIN31
SET PROCEDURE TO WIN31API ADDITIVE
CASE _WINNT
SET PROCEDURE TO WINNTAPI ADDITIVE
CASE _WIN95
SET PROCEDURE TO WIN95API ADDITIVE
ENDCASE
Innerhalb der Prozedur-Dateien werden die von mir immer gleich benannten API-Aufrufe entsprechend der Zielplattform umparametrisiert". Damit erreicht man die größtmögliche Plattformunabhängigkeit und kann zudem noch die korrekte Parametrisierung vor Aufruf der API-Funktionen testen, bzw. deren Rückgabewerte in ein von der VFP-Anwendung verständliches Format umrechnen.
Tips zu DECLARE DLL:
1.) In den Fällen, in denen Sie Funktionen aus den Standard-DLLs der einzelnen Plattformen mit VFPs DECLARE DLL einbinden wollen, reicht der Zusatz IN WIN32API. Damit übernimmt VFP die Aufgabe die plattformspezifisch unterschiedlich benannten DLL für Sie zu durchsuchen, und schon haben wir ein Problem weniger!
2.) Seien Sie besonders achtsam bei der Benennung der Funktionen in den externen DLLs. Der Funktionsname muß absolut genau dem internen Funktionsnamen innerhalb der DLL entsprechen. Groß- und Kleinschreibung sind dabei zu beachten, anderenfall erhalten Sie eine (abfangbare) Fehlermeldung von VFP!
3.) Bei der Vereinbarung der Parametertypen innerhalb der DECLARE DLL Funktion müssen Sie zwar angeben, welchen Datentyp (INTEGER, SHORT, SINGLE oder DOUBLE) Foxpro zu der jeweiligen Funktion schicken soll, während der Programmausführung wird aber intern ein VFP-Standardvariablentyp für die Übergabe von Werten an, und für den Empfang von Werten von der DLL benutzt. Der von Ihnen angegebene Datentyp wird vom DECLARE-Befehl intern benutzt, um die verschiedenen Datentypen anzupassen und umzurechnen.
Beispiel:
Angenommen Sie haben eine Funktion innerhalb einer DLL, die Ihnen einen logischen Wert zurückgeben soll. Sie melden die Funktion dann z.B. so an:
DECLARE SHORT TestFunktion IN "C:\WINDOWS\SYSTEM\TESTDLL.DLL";
STRING cString1, STRING cString2
Diese Funktion vergleicht z.B. ob cString1 in cString2 buchstabengetreu enthalten ist. Ein logisches falsch wird nun als 16bit-Wert 0 zurückgegeben.
Abschließend hier eine Aufstellung zu den verschiedenen Datentypen, die im DECLARE DLL Befehl verwendet werden können:
Typ Benutzt für
STRING Zeichen und nicht numerische Daten, Puffer, Strukturen und Arrays
INTEGER 32bit-Ganzzahl, die folgenden C-Typen entspricht: INT, LONG, WORD, DWORD, UINT, BOOL FileHandles, WindowHandles, ModuleHandles etc.
SHORT 16bit-Ganzzahl, die nur dann benutzt werden sollte, wenn auch wirklich 16bit-Werte (wie SHORT oder BYTE) benötigt werden, was meistens nur bei FN-Rückgaben der Fall ist.
SINGLE 32bit-Fließkommazahl, die bei den C-Datentypen FLOAT oder REAL verwendet werden muß.
DOUBLE 64bit-Fließkommazahl, die beim C-Datentyp DOUBLE eingesetzt werden muß.
Bitte beachten Sie weiterhin, daß nun beim DECLARE DLL-Befehl der Klammeraffe @, der die Parameterübergabe per Referenz erzwingt nun nach dem Variablentyp, also vor dem Variablennamen zu stehen hat.
Weiterführende Techniken entnehmen Sie bitte meinen Ausführungen zum jeweiligen Vortragsthema.