amiga-news ENGLISH VERSION
.
Links| Forum| Kommentare| News melden
.
Chat| Umfragen| Newsticker| Archiv
.

amiga-news.de Forum > Programmierung > Anfängerfrage: Wieso sind OpenLibrary und OpenDevice unterschiedlich? [ - Suche - Neue Beiträge - Registrieren - Login - ]

-1- [ - Beitrag schreiben - ]

10.11.2019, 00:57 Uhr

Reth
Posts: 1858
Nutzer
Hallo zusammen,

hab mir bisher noch nie Gedanken darüber gemacht, aber wieso arbeiten die beiden Funktionen denn so unterschiedlich?

Während OpenLibrary einen intialisierten Basiszeiger zurück liefert, ist das bei OpenDevice nicht der Fall.

Die einzige Erklärung, die ich auf die Schnelle gefunden hab ist diese:
Unlike most Amiga system functions, OpenDevice() returns zero for success and a device-specific error value for failure.

Aber das hätte man doch sicher auch anders lösen können, so dass die beiden Funktionen mehr oder weniger konsistent funktionieren - oder nicht?

Oder gibt es da noch andere Gründe?

Ciao

[ - Antworten - Zitieren - Direktlink - ]

11.11.2019, 16:02 Uhr

Bizcocho
Posts: 15
Nutzer
Ich erbarme mich.
Naja. Das liegt (lag) wohl in der Natur des Programmierers.
Außerdem sind das 2 verschiedene Funktionen.
Wenn jemand meint, dass ihm d0 nicht für den Fehlercode toll erscheint und er Bock auf d7 hat, dann ist es so.
Nicht so schön, aber es geht.

OpenDevice()
Null ist gleich Erfolg für OpenDevice ().
Im Gegensatz zu den meisten Amiga-Systemfunktionen gibt OpenDevice () für den Erfolg Null und für den Fehler einen gerätespezifischen Fehlerwert zurück.

Und:
OpenLibrary() eben den Zeiger auf die geöffnete Lib
oder Null bei Fehler.

Man beachte:
OpenDevice()...Im Gegensatz zu den meisten Amiga-Systemfunktionen....


;)

[ - Antworten - Zitieren - Direktlink - ]

11.11.2019, 16:33 Uhr

Holger
Posts: 8116
Nutzer
@Bizcocho:
Ich glaube nicht, dass eine Frage nach dem „Warum“ dadurch beantwortet wird, dass man das vom Thread-Ersteller bereits gesagt mit anderen Worten wiederholt.

--
Good coders do not comment. What was hard to write should be hard to read too.

[ - Antworten - Zitieren - Direktlink - ]

11.11.2019, 16:37 Uhr

Bizcocho
Posts: 15
Nutzer
@Holger:
Dann tendiere ich dazu:
Naja. Das liegt (lag) wohl in der Natur des Programmierers.

[ - Antworten - Zitieren - Direktlink - ]

11.11.2019, 17:00 Uhr

Holger
Posts: 8116
Nutzer
Zitat:
Original von Reth:
Die einzige Erklärung, die ich auf die Schnelle gefunden hab ist diese:
Unlike most Amiga system functions, OpenDevice() returns zero for success and a device-specific error value for failure.

Aber das hätte man doch sicher auch anders lösen können, so dass die beiden Funktionen mehr oder weniger konsistent funktionieren - oder nicht?

Hätte man auf jeden Fall, da sowohl der Device-Basiszeiger als auch der error code in der IORequest Struktur enthalten sind. Man hatte also die Wahl.

Allerdings zieht sich die Asymmetrie durch die ganze Behandlung von Devices. Der Basiszeiger wird ja nicht (direkt) benutzt, wenn Du die I/O Funktionen aufrufst, sondern Du rufst Exec Funktionen mit der IORequest Struktur als Argument auf.

Der Basiszeiger als Rückgabewert wäre also unter Berücksichtigung der anderen Funktionen zu nichts weiter nutze, als auf NULL getestet zu werden. Da ist der Fehlercode schon nützlicher.

Der Umweg über die exec.library ermöglicht es, gemeinsame Funktionalität wie z.B. warten auf Antwort im Falle von DoIO an einer zentralen Stelle zu implementieren.

Das heißt natürlich nicht, dass man nicht auch das hätte anders lösen können.

--
Good coders do not comment. What was hard to write should be hard to read too.

[ - Antworten - Zitieren - Direktlink - ]

11.11.2019, 17:24 Uhr

68kassembler
Posts: 62
Nutzer
Hallo,

es ist zwar etwas her, das ich mich damit beschäftigt hatte, aber es gibt mehrere gründe für diesen Unterschied:

1) Alter der API. Früher wurde nicht alles in C geschrieben sondern in einen der Vorgängern. Und da war das eine oder andere unterschiedlich. Sieht man an alten APIs des AmigaOS.

2) OpenLibrary da gibt es mehrere Varianten von. Hier muss man schon aufpassen! Und außerdem wird hier faktisch nur eine Library ins RAM geladen und du musst dann die Funktionen aufrufen, was du machen willst. Deswegen wird hier die Adresse benötigt. (Gut, ist nicht ganz korrekt, beim laden der Library wird auch etwas Code zur Initialisierung aufgerufen)

3) OpenDevice Wenn ich mich hier richtig erinnere, wird hier ein Programm gestartet, was eine bestimme Struktur hat. Das läuft dann einfach parallel zu deinen Programm (Multitasking!). Das hier schon in Prinzip was anderes passiert, und damit der Rückgabewert auch anders sein muss, sollte klar sein. Schließlich wird damit auch anders gearbeitet. Soweit ich mich erinnern konnte, über Messages und nicht über Funktionsaufrufe (so wie bei Libraries!).

hoffe mal, das ich mich jetzt richtig erinnere.

[ - Antworten - Zitieren - Direktlink - ]

11.11.2019, 21:12 Uhr

jolo
Posts: 110
Nutzer
Die Antwort hat Holger eigentlich schon gegeben:

Zitat:
Der Basiszeiger wird ja nicht (direkt) benutzt, wenn Du die I/O Funktionen aufrufst, sondern Du rufst Exec Funktionen mit der IORequest Struktur als Argument auf.

Zur Verdeutlichung:

Bei OpenLibrary() wird Dir die Basisadresse zu einer bestehenden oder gerade geöffneten Bibliothek retourniert, oder null, falls es diese Bibliothek nicht gibt; bei OpenDevice() erstellst Du die individuellen Datensätze selber (IORequests), welche für unterschiedliche Geräte unterschiedlich groß sind, die Du wiederum vorher zu initialisieren hast - inklusive MessagePort -, um dann anzufragen, ob dieser Datensatz (dein erstellter IORequest) so vom Gerätetreiber akzeptiert wird, denn, unter Umständen kann die zugrundeliegende Hardware nicht von mehreren Tasks/Prozessen gleichzeitig benutzt werden (audio, timer etc. - je nach verwendeter Komponente), also wird ein Fehlercode retourniert, falls dein Gesuch so nicht akzeptiert werden konnte.
Das Gerät selber, also z.B. "audio", "trackdisk" oder "timer" wird in den meisten Fällen vorhanden sein, aber je nachdem welche Aktionen Du durchführen möchtest, könnte es zu Überschneidungen mit anderen Programmen führen, welche die gleiche Aktion derzeit tätigen, die Du beabsichtigst. Also stellt OpenDevice() eigentlich nur sicher, dass es keine Überschneidungen gibt, und dafür wird ein initialisierter Datensatz (IORequest) benötigt, der Dir *und* dem Gerätetreiber als Basis dient, und nicht der Device-Zeiger selber.
Ein einzelnes Gerät kann mehrere Komponenten aufweisen (audio device = min. 4 Kanäle (= 4 Komponenten)). Ein einzelner Zeiger deckt dies nicht ab.

Zudem, OpenLibrary() initiiert keinen neuen Task/Prozess, sondern Du benutzt Routinen (Library Vectors), die einmal im Speicher vorhanden, sofort ausgeführt werden können (relativ zur Basisadresse) im Kontext deines eigenen Tasks/Prozesses.
Bei IORequests sieht das aber gänzlich anders aus: Unter Umständen schiebst Du nur eine Anfrage an den Gerätetreiber rüber (BeginIO()), wobei das Gesuch (IORequest) selber in der Größe zu anderen Gerätetreiber differieren kann, und zudem noch von einem anderen Task/Prozess ausgeführt werden muss, welcher vom Device selber erstellt wurde (siehe Beitrag von 68kassembler).

Library = Sammlung von Routinen ohne Hardware-Relevanz, welche synchron ausgeführt werden.
Device = Funktionen eines Gerätetreibers, die zumeist asynchron ausgeführt werden, mit unterschiedlichen Größen der Datensätze (IORequests) - abhängig vom Device - für unterschiedliche Komponenten (Unit) des Geräts (Device).

Der Funktionsname OpenDevice() der Exec-Library ist kurz und prägnant aber in Bezug auf seine Funktionalität nicht wirklich eindeutig gewählt, weil man nicht nur einen Gerätetreiber öffnen kann, sondern gleich beim Öffnen auch erfragen kann, ob eine Komponente (Unit) des Geräts (Device), abhängig von dem spezifizierten Datensatz (IORequest), für eigene Zwecke nutzbar ist, wobei die Basisadresse immer Dein IORequest ist und nicht der Device-Zeiger, denn Standard-Devices bieten nur zwei Funktionen an, die wie bei einer gewöhnlichen Library angeordnet sind: BeginIO() = Offset -30 und AbortIO() = Offset -36, ergo wird die Basisadresse des Geräts nur zum Ermitteln des Sprungzieles gebraucht - der Datensatz (IORequest) aber zum Ansprechen der Hardware-Komponente.

OpenDevice() entspricht somit einem "Zugriff auf Komponente X des Geräts Y erlaubt?" und nicht nur "Öffne Gerät". Dass man da halt einen Fehlercode retourniert finde ich passend und wohlüberlegt.

[ - Antworten - Zitieren - Direktlink - ]

12.11.2019, 00:00 Uhr

Reth
Posts: 1858
Nutzer
Vielen Dank an alle Antworter! :) Das hilft mir schon.

Eine Frage noch zu folgendem Punkt:
Zitat:
Original von Holger:
Der Basiszeiger wird ja nicht (direkt) benutzt, wenn Du die I/O Funktionen aufrufst, sondern Du rufst Exec Funktionen mit der IORequest Struktur als Argument auf.

Der Basiszeiger als Rückgabewert wäre also unter Berücksichtigung der anderen Funktionen zu nichts weiter nutze, als auf NULL getestet zu werden. Da ist der Fehlercode schon nützlicher.


Bei OpenLibrary() nutze ich den Basiszeiger ja auch nicht selbst, sondern rufe nach erfolgreichem Öffnen einfach die Lib-Funktionen auf - der Basiszeiger wird dann wohl intern von diesen selbst verwendet nehme ich an, oder vom OS, um die Lib-Funktionen zu finden/zu rufen?

[ - Antworten - Zitieren - Direktlink - ]

12.11.2019, 17:40 Uhr

68kassembler
Posts: 62
Nutzer
Zitat:
Original von Reth:
Bei OpenLibrary() nutze ich den Basiszeiger ja auch nicht selbst, sondern rufe nach erfolgreichem Öffnen einfach die Lib-Funktionen auf - der Basiszeiger wird dann wohl intern von diesen selbst verwendet nehme ich an, oder vom OS, um die Lib-Funktionen zu finden/zu rufen?


In Assembler sieht man das ganz schnell. Zur Basis Adresse wird einfach die Offset Adresse addiert. Dadurch hat man dann die Funktionsadresse. Ist in 68k Assembler sogar nur ein Befehl (inkl. Aufruf der Unterfunktion), wenn ich mich jetzt richtig erinnere. Steht eigentlich auch so in jeden einfachen Amiga Assembler Tutorial drin.

Was aber die Library intern macht, kann ich dir nicht sagen.

[ - Antworten - Zitieren - Direktlink - ]

12.11.2019, 19:15 Uhr

Holger
Posts: 8116
Nutzer
Zitat:
Original von 68kassembler:
In Assembler sieht man das ganz schnell. Zur Basis Adresse wird einfach die Offset Adresse addiert. Dadurch hat man dann die Funktionsadresse. Ist in 68k Assembler sogar nur ein Befehl (inkl. Aufruf der Unterfunktion), wenn ich mich jetzt richtig erinnere.


code:
 
	jsr	offset(a6)


wobei der Offset negativ ist und bis auf wenige explizite Ausnahmen die Library-Funktion erwarten darf, auf die Daten ihrer eigene lib über A6 zugreifen zu können.

@Reth:
Damit das unter C funktioniert, musst Du den Basiszeiger nach dem Öffnen einer genau definierten Variablen zuweisen. Dann kann der generierte Code die Adresse nach A6 laden.

Abgesehen von den internen Details, rufst Du eben Funktionen der geöffneten Bibliothek auf, während Du bei Devices weiterhin Funktionen von Exec aufrufst.

--
Good coders do not comment. What was hard to write should be hard to read too.

[ - Antworten - Zitieren - Direktlink - ]

12.11.2019, 21:30 Uhr

jolo
Posts: 110
Nutzer
Die negativen Offsets einer Bibliothek (Library) verweisen auf absoluten Code, der so aufgebaut ist:

code:
Offset_Minus_36
   jmp _ByeBye
Offset_Minus_30
   jmp _Aloha
Offset_Minus_24
   jmp _Reserved
Offset_Minus_18
   jmp _Expunge
Offset_Minus_12
   jmp _Close
Offset_Minus_6
   jmp _Open
LibraryBasePointer ; (Offset 0)


Da "JMP" mit einer absoluten Adresse 6 Bytes belegt, sind die Offsets immer in Schritten von -6 Bytes von der Basisadresse entfernt.

Die ersten 4 Sprungziele (Open, Close, Expunge und Reserved) sind für alle Bibliotheken (Libraries) und Gerätetreiber (Devices) in dieser Anordnung zu implementieren, weil bei einem Aufruf von OpenLibrary() / OpenDevice() die Funktion "_Open" durchlaufen werden muss, beim Schließen der Bibliothek / Gerätetreiber "_Close". Somit müssen die ersten vier Sprungziele ein verbindliches Verhalten aufweisen, damit die Handhabung von Bibliotheken und Gerätetreibern von Exec aus bewerkstelligt werden kann.

Gerätetreiber bauen auf Bibliotheken auf, wobei aber die ersten *6* Sprungziele verbindlich sind:

code:
Offset_Minus_36
   jmp _AbortIO
Offset_Minus_30
   jmp _BeginIO
Offset_Minus_24
   jmp _Reserved
Offset_Minus_18
   jmp _Expunge
Offset_Minus_12
   jmp _Close
Offset_Minus_6
   jmp _Open
DeviceBasePointer ; (Offset 0)


Beim Aufruf, von z.B. AllocVec() der Exec-Library musst Du die absolute Adresse des Sprungziels ermitteln und dann anspringen:

code:
AllocVec  EQU  -684  ; wie beschrieben, negativ und in 6-Byte
                     ; Schritten - zeigt auf "JMP ABSOLUTE ADRESSE"
                     ; - siehe oben

     movea.l _SysBase,A6
     adda.w  #AllocVec,A6
     jsr     0(A6)


oder einfacher:

code:
     movea.l _SysBase,A6  ; A6 = z.B: $40000810 - Basis Exec
     jsr     AllocVec(A6) ; = -684(A6) == $40000564


In anderen Worten, springe zur Adresse $40000564 und mache da weiter (ergo AllocVec() ausführen).

BeginIO() wird nicht anders aufgerufen:
code:
     movea.l _IORequest,A1
     movea.l 20(A1),A6    ; io_Device(A1),A6 - Geräte-Basis-Zeiger
                          ; aus IORequest Datensatz
     jsr     -30(A6)      ; Aufruf von BeginIO des betreffenden Geräts


Für Hochsprachen wie C verschleiert die Amiga-API durch INLINE- oder GLUE-Code, dass zu einem Funktionsaufruf immer der Basis-Zeiger benötigt wird:
code:
mem = AllocVec( 4096, 65536); /* 65536 = MEMF_CLEAR */


Tatsächlich wird aber ein:
code:
mem = SysBase->AllocVec( 4096, 65536);

ausgeführt.

Da aber negative Offsets von Hochsprachen aus nicht ganz unproblematisch sind, wie auch die 6er Schritte zur Bestimmung des Sprungziels, hat man das in 68k Assembler gekapselt, was zur besagten Notation ohne Basis-Zeiger führt.


Zitat:
Abgesehen von den internen Details, rufst Du eben Funktionen der geöffneten Bibliothek auf, während Du bei Devices weiterhin Funktionen von Exec aufrufst.

DoIO() und SendIO() sind nur Exec Container-Funktionen, die aber auch immer BeginIO() des Geräts bemühen müssen.

[ - Antworten - Zitieren - Direktlink - ]

13.11.2019, 13:34 Uhr

Holger
Posts: 8116
Nutzer
Zitat:
Original von jolo:
Beim Aufruf, von z.B. AllocVec() der Exec-Library musst Du die absolute Adresse des Sprungziels ermitteln und dann anspringen:

code:
AllocVec  EQU  -684  ; wie beschrieben, negativ und in 6-Byte
                     ; Schritten - zeigt auf "JMP ABSOLUTE ADRESSE"
                     ; - siehe oben

     movea.l _SysBase,A6
     adda.w  #AllocVec,A6
     jsr     0(A6)


Bitte?!
Seit wann rufen wir library Funktionen mit etwas anderem als der Basisadresse der library in A6 auf?

Zitat:
oder einfacher:

code:
     movea.l _SysBase,A6  ; A6 = z.B: $40000810 - Basis Exec
     jsr     AllocVec(A6) ; = -684(A6) == $40000564


Nur so.
Wenn Du willst, kannst Du die absolute Adresse in einem anderen Register als A6 berechnen, aber da liegt keinerlei Vorteil drin.

Zitat:
Für Hochsprachen wie C verschleiert die Amiga-API durch INLINE- oder GLUE-Code, dass zu einem Funktionsaufruf immer der Basis-Zeiger benötigt wird:
code:
mem = AllocVec( 4096, 65536); /* 65536 = MEMF_CLEAR */


Tatsächlich wird aber ein:
code:
mem = SysBase->AllocVec( 4096, 65536);

ausgeführt.

Da aber negative Offsets von Hochsprachen aus nicht ganz unproblematisch sind, wie auch die 6er Schritte zur Bestimmung des Sprungziels, hat man das in 68k Assembler gekapselt, was zur besagten Notation ohne Basis-Zeiger führt.

Das halt vor allem auch damit zu tun, dass SysBase->AllocVec( 4096, 65536); niemals zu einem jsr AllocVec(A6) compiliert werden würde und es auch keinerlei Konstrukt im C-Standard gibt, das den gewünschten Code erzeugen würde. Wie bereits oben erwähnt, reicht es eben nicht, die Zieladresse zu ermitteln, der Standard schreibt auch vor, dass die Basisadresse im Register A6 übergeben wird.

Deshalb braucht man #pragma, inline Assembler oder eine Linkbibliothek, die den gewünschten Code in vorgefertigter Form enthält.

In AmigaOS 4 sieht das ganze dagegen ganz anders aus.

Zitat:
Zitat:
Abgesehen von den internen Details, rufst Du eben Funktionen der geöffneten Bibliothek auf, während Du bei Devices weiterhin Funktionen von Exec aufrufst.

DoIO() und SendIO() sind nur Exec Container-Funktionen, die aber auch immer BeginIO() des Geräts bemühen müssen.


Ändert aber nichts daran, dass die Anwendung nicht die Basisadresse des Devices, sondern die der exec.library in A6 zur Verfügung stellen muss, wenn sie diese Exec Funktionen aufruft. Außerdem macht z.B. DoIO() schon ein bisschen mehr, wie z.B. Warten, wenn das device keine synchrone Ausführung beherrscht.

--
Good coders do not comment. What was hard to write should be hard to read too.

[ - Antworten - Zitieren - Direktlink - ]

13.11.2019, 23:58 Uhr

Reth
Posts: 1858
Nutzer
Noch ein Punkt speziell zum Timer-Device:

In diesem Artikel wird Folgendes erwähnt:

Zusätzlich hat das timer.device noch einen kleinen Satz an Funktionen, die wie jene einer Library aufgerufen werden. Diese sind in clib/timer_protos.h deklariert und benötigen wie jede Library einen initialisierten Basiszeiger.

Das liest sich für mich, als ob es hier wirklich eines initialisierten Basiszeigers benötigt, damit die Funktionen genutzt werden können - genauso wie bei Libs. Ist dem so, oder hab ich da was falsch verstanden?

Und noch eine Frage: Kann ich in einem Programm ein Device auch mehrfach öffnen, um z.B. Timer mit verschiedenen zeitlichen Auflösungen zu erhalten?

[ Dieser Beitrag wurde von Reth am 14.11.2019 um 00:19 Uhr geändert. ]

[ - Antworten - Zitieren - Direktlink - ]

14.11.2019, 18:44 Uhr

thomas
Posts: 7715
Nutzer
Zitat:
Original von Reth:
Das liest sich für mich, als ob es hier wirklich eines initialisierten Basiszeigers benötigt, damit die Funktionen genutzt werden können - genauso wie bei Libs. Ist dem so, oder hab ich da was falsch verstanden?


Das hast du richtig verstanden. Den Library-Pointer bekommst du aus IORequest->io_Device.


Zitat:
Und noch eine Frage: Kann ich in einem Programm ein Device auch mehrfach öffnen, um z.B. Timer mit verschiedenen zeitlichen Auflösungen zu erhalten?

Na selbstverständlich.



Ein Device ist auch eine Library. Es hat nur noch etwas drumherum, was für I/O-Operationen nützlich ist.

Libraries liefern Funktionen. Devices machen Ein-/Ausgaben. Ein-/Ausgaben dauern per Definition sehr viel länger als Funktionsaufrufe. Außerdem können viele Ein-/Ausgaben von der Hardware eigenständig bearbeitet werden, ohne dass der Prozessor darauf wartet. Deshalb ist das Device-Interface so designed, dass es leicht Asynchron implementiert werden kann.

Intern ist ein Device auch eine Library. Deshalb kann es zusätzlich zu den obligatorischen Funktionen auch weitere haben, die man per Library-Interface aufruft.

--
Email: thomas-rapp@web.de
Home: thomas-rapp.homepage.t-online.de/

[ - Antworten - Zitieren - Direktlink - ]

14.11.2019, 21:55 Uhr

Reth
Posts: 1858
Nutzer
Zitat:
Original von thomas:
Zitat:
Original von Reth:
Das liest sich für mich, als ob es hier wirklich eines initialisierten Basiszeigers benötigt, damit die Funktionen genutzt werden können - genauso wie bei Libs. Ist dem so, oder hab ich da was falsch verstanden?


Das hast du richtig verstanden. Den Library-Pointer bekommst du aus IORequest->io_Device.


Vielen Dank! Aus diesem Grund hatte ich auch die Eingangsfrage gestellt, ob sich die Open-Funktionen nicht gleich verhalten können und gleich einen initialisierten Basiszeiger zurückgeben.

[ - Antworten - Zitieren - Direktlink - ]

19.11.2019, 21:56 Uhr

jolo
Posts: 110
Nutzer
Früher konnte ich nicht antworten, weil der Broterwerb im Wege steht...

Egal.

Zitat:
Original von Holger:
Zitat:
Original von jolo:
Beim Aufruf, von z.B. AllocVec() der Exec-Library musst Du die absolute Adresse des Sprungziels ermitteln und dann anspringen:

code:
AllocVec  EQU  -684  ; wie beschrieben, negativ und in 6-Byte
                     ; Schritten - zeigt auf "JMP ABSOLUTE ADRESSE"
                     ; - siehe oben

     movea.l _SysBase,A6
     adda.w  #AllocVec,A6
     jsr     0(A6)


Bitte?!
Seit wann rufen wir library Funktionen mit etwas anderem als der Basisadresse der library in A6 auf?
[/code]


Erst nach zweimaligen Lesen Deines Einwandes verstehe ich jetzt worauf Du hinaus willst.
Da nur ein absolutes Sprungziel ermittelt werden musste um den dortigen Code auszuführen, kann irgendein Register (A0-A6) Verwendung finden.
Würde man die Funktion zum Allokieren von Speicher aber wirklich so aufrufen, also unter Missachtung der Amiga-m68k-ABI, ist ein Software-Error nicht mehr fern.
Die Verwendung von A6 ist hier tatsächlich unglücklich von mir gewählt; hier hätte ich besser ein anderes Register nehmen sollen.
Ich war mir dessen gar nicht bewusst, weil es für mich nur darum ging, die Abarbeitung ab "JMP ABSOLUTE ADRESSE" zu bestimmen und nicht wirklich AllocVec() aufzurufen.


Zitat:
Zitat:
Für Hochsprachen wie C verschleiert die Amiga-API durch INLINE- oder GLUE-Code, dass zu einem Funktionsaufruf immer der Basis-Zeiger benötigt wird:
code:
mem = AllocVec( 4096, 65536); /* 65536 = MEMF_CLEAR */


Tatsächlich wird aber ein:
code:
mem = SysBase->AllocVec( 4096, 65536);

ausgeführt.

Da aber negative Offsets von Hochsprachen aus nicht ganz unproblematisch sind, wie auch die 6er Schritte zur Bestimmung des Sprungziels, hat man das in 68k Assembler gekapselt, was zur besagten Notation ohne Basis-Zeiger führt.

Das halt vor allem auch damit zu tun, dass SysBase->AllocVec( 4096, 65536); niemals zu einem jsr AllocVec(A6) compiliert werden würde und es auch keinerlei Konstrukt im C-Standard gibt, das den gewünschten Code erzeugen würde. Wie bereits oben erwähnt, reicht es eben nicht, die Zieladresse zu ermitteln, der Standard schreibt auch vor, dass die Basisadresse im Register A6 übergeben wird.

Sag' niemals nie.
Allerdings hast Du recht; momentan gibt es kein C-Sprachgerüst, welches die Amiga-m68k-ABI unterstützt.


Zitat:
Deshalb braucht man #pragma, inline Assembler oder eine Linkbibliothek, die den gewünschten Code in vorgefertigter Form enthält.

Nicht unbedingt. Mit ein bisschen Aufwand ist es mit modernen C/C++ Compilern ohne Probleme möglich, siehe Beispiel.
Wie man sieht, sogar nur mit Bordmitteln.
Aber so effektiv wie INLINE-Code ist es natürlich nicht. :-)
Da bleibe ich lieber bei INLINE-Code.

Trotzdem, mit jedem X-beliebigen C/C++ Compiler kann man selbst den Self-Pointer für m68k (A6) auch schön verschleiern (siehe Makro im Beispiel), so dass wir wieder beim Verstecken von Pointern sind, was bei Nichtbewanderten der Amiga-API/ABI zu Kopfschmerzen führen könnte.
Aber, als in den 80er Jahren des letzten Jahrhunderts die Amiga-m68k-ABI spezifiziert wurde, konnte kein C-Compiler Argumente in explizite CPU-Register platzieren, nicht einmal der Green Hills C Compiler. Das musste händisch nachgerüstet werden.
Ergo, erst heute könnten wir das so machen; nicht in den 80er des letzten Jahrhunderts.

code:
extern void *LVOWrite;	/* amiga oder small lib */
extern void *LVOOutput;

extern void *DOSBase;

#ifndef REG
 #ifdef __VBCC__
  #define REG( reg, arg) __reg(#reg) arg
 #endif

 #ifdef __GNUC__
  #define REG( reg, arg) arg __asm(#reg)
 #endif

 #ifdef __MAXON__
  #define REG( reg, arg) register __##reg arg
 #endif
#endif

struct DOS {
	long (*IWrite)( REG(a6, void *),
				    REG(d1, long stream),
				    REG(d2, char *buffer),
				    REG(d3, long length));
	long (*IOutput)( REG(a6, void *));
};

struct DOS Idos;
struct DOS *IDOS = &Idos;


#define Write( s, b, l) IWrite( DOSBase, (s), (b), (l))
#define Output() IOutput( DOSBase)


int main( int argc, char **argv)
{
	IDOS->IWrite = (long (*)( REG(a6, void *),
				    REG(d1, long stream),
				    REG(d2, char *buffer),
				    REG(d3, long length)))
				   ((long) DOSBase + (long) &LVOWrite);
	IDOS->IOutput = (long (*)( REG(a6, void *)))
				   ((long) DOSBase + (long) &LVOOutput);


	IDOS->Write( IDOS->Output(), "Hello World!\n", 13);
}


Zitat:
In AmigaOS 4 sieht das ganze dagegen ganz anders aus.

Na ja, so viel Unterschied sehe ich da jetzt nicht. Das Konzept ist eigentlich immer noch das Gleiche, auch wenn jetzt mehrere Interfaces unterstützt werden und es C/C++ freundlicher ausgelegt wurde (nicht beim Erstellen, sondern beim Benutzen). Der Aufwand, eine AmigaOS4 Bibliothek zu erstellen ist um ein Vielfaches umfangreicher als eine Bibliothek unter OS1 bis OS3, weil eine gänzlich andere ABI zugrunde liegt.
Man hätte das auch in den 80er Jahren des letzten Jahrhunderts schon annähernd machen können, falls a) der Compiler dies unterstützt hätte, b) mehr CPU-Register zur Verfügung gestanden hätten, c) eine ähnliche ABI wie beim PPC als Grundlage herangezogen wäre, d) die Rechenleistung höher gewesen wäre.
Ich weiß, hätte, hätte, Fahrradkette...


Zitat:
Abgesehen von den internen Details, rufst Du eben Funktionen der geöffneten Bibliothek auf, während Du bei Devices weiterhin Funktionen von Exec aufrufst.

Zitat:
DoIO() und SendIO() sind nur Exec Container-Funktionen, die aber auch immer BeginIO() des Geräts bemühen müssen.

Ändert aber nichts daran, dass die Anwendung nicht die Basisadresse des Devices, sondern die der exec.library in A6 zur Verfügung stellen muss, wenn sie diese Exec Funktionen aufruft. Außerdem macht z.B. DoIO() schon ein bisschen mehr, wie z.B. Warten, wenn das device keine synchrone Ausführung beherrscht.


Richtig.
Wenn ich spekulieren müsste, würde ich sagen, dass DoIO() und SendIO() eigentlich nur wegen des ursprünglichen DOS in Exec aufgenommen wurden, damit das DOS Zugriff auf das Serial-, Timer- und TrackDisk-Device bekommt; DoIO() macht ja nichts anderes als BeginIO() mit gesetzter IOF_QUICK-Flagge aufzurufen und je nachdem was der Gerätetreiber unterstützt, entweder im Kontext des aufrufenden Programms alles abzuarbeiten oder, falls das nicht möglich ist, WaitIO() zu bemühen um dann letztendlich die Nachricht aus der Nachrichten-Queue zu entfernen, während SendIO() nur BeginIO() mit gelöschter IOF_QUICK-Flagge aufruft, wobei DoIO() und SendIO() so ihre Tücken haben, weil sie unter Umständen die Flaggen des IORequest nach einem Funktionsaufruf überschreiben.
Zudem, die Möglichkeit die IOF_QUICK-Flagge zu setzen um schnellstmöglich wieder zum normalen Programmablauf zurück zu kehren, wobei die Ausführung länger dauert als erwartet, also die IOF_QUICK-Flagge seitens des Gerätertreibers gelöscht wird, und statt dessen ein anderer Task/Prozess die Arbeit übernimmt, wird nicht von DoIO()/SendIO() abgedeckt; das geht nur direkt über BeginIO().
Aber zurück, wie gesagt Spekulation, weil es zwei Funktionen im BCPL verfassten DOS mit gleichen Namen gibt (doIO() #13?, sendIO() #14?), wobei ein direkter Aufruf von BeginIO() unter BCPL nicht funktionieren kann, weil Adressen in BCPL immer Langwort-Indexe sind und nicht nur an geraden Adressen ausgerichtet sein müssen.
Falls ich mal Zeit finde, disassembliere ich die beiden BCPL Funktionen.


@Reth
Tue Dir selber einen Gefallen und setzte nach dem Öffnen irgendeines Devices manuell den Typ der Nachricht deines MsgPorts auf NT_REPLYMSG. CheckIO() dankt es dir.

iorequest.io_Message.mn_Node.ln_Type = NT_REPLYMSG;
oder fürs Timer-Device
timerio->tr_node.io_Message.mn_Node.ln_Type = NT_REPLYMSG;

[ - Antworten - Zitieren - Direktlink - ]

20.11.2019, 10:21 Uhr

Holger
Posts: 8116
Nutzer
Zitat:
Original von jolo:
Sag' niemals nie.
Allerdings hast Du recht; momentan gibt es kein C-Sprachgerüst, welches die Amiga-m68k-ABI unterstützt.

Angesichts der Entwicklungsrichtung des C Standards erscheint es mir auch sehr unwahrscheinlich, dass sich das je ändern wird.
Zitat:
Zitat:
Deshalb braucht man #pragma, inline Assembler oder eine Linkbibliothek, die den gewünschten Code in vorgefertigter Form enthält.

Nicht unbedingt. Mit ein bisschen Aufwand ist es mit modernen C/C++ Compilern ohne Probleme möglich, siehe Beispiel.

Eine Registerdeklaration, die eine Compiler-spezifische Syntax erfordert, stellt für mich irgendwie keine fundamental andere Sache als die von mir genannten Mittel dar. Im Falle von GNU C handelt es sich sogar im wahrsten Sinne des Wortes um inline-Assembler.

Zitat:
Wie man sieht, sogar nur mit Bordmitteln.
#pragma und inline Assembler sind auch Bordmittel. In den goldenen Zeiten, als Compiler direkt mit Amiga-spezifischen Linkbibliotheken ausgeliefert wurden, zählten selbst die zu den Bordmitteln.

Zitat:
Na ja, so viel Unterschied sehe ich da jetzt nicht. Das Konzept ist eigentlich immer noch das Gleiche, auch wenn jetzt mehrere Interfaces unterstützt werden und es C/C++ freundlicher ausgelegt wurde (nicht beim Erstellen, sondern beim Benutzen).
Dass es C/C++ freundlicher ausgelegt ist, war ja das einzige, worauf ich hinauswollte.

Zitat:
Man hätte das auch in den 80er Jahren des letzten Jahrhunderts schon annähernd machen können, falls a) der Compiler dies unterstützt hätte, b) mehr CPU-Register zur Verfügung gestanden hätten, c) eine ähnliche ABI wie beim PPC als Grundlage herangezogen wäre, d) die Rechenleistung höher gewesen wäre.
Wir können nicht in die Köpfe der Entwickler von damals schauen. Mir erschien es schon immer komisch, ein Betriebssystem zu >80% in C zu schreiben und gleichzeitig ein ABI zu entwerfen, das kein C Compiler out-of-the-box unterstützt.

--
Good coders do not comment. What was hard to write should be hard to read too.

[ - Antworten - Zitieren - Direktlink - ]

27.11.2019, 19:05 Uhr

Polluks
Posts: 105
Nutzer
Danke Leute, war sehr informativ :dance1:
--
Pegasos II G4 (MorphOS 3.9), Zalman M220W
PowerBook 5,8 (MorphOS 3.11)

[ - Antworten - Zitieren - Direktlink - ]


-1- [ - Beitrag schreiben - ]


amiga-news.de Forum > Programmierung > Anfängerfrage: Wieso sind OpenLibrary und OpenDevice unterschiedlich? [ - Suche - Neue Beiträge - Registrieren - Login - ]


.
Impressum | Datenschutzerklärung | Netiquette | Werbung | Kontakt
Copyright © 1998-2024 by amiga-news.de - alle Rechte vorbehalten.
.