|
Antwort |
Registriert seit: 10. Jun 2002 Ort: Deidesheim 2.886 Beiträge Turbo Delphi für Win32 |
#31
Also zu der Sache mit den Arrays: Arrays sind in Java Objekte, vielleicht kannst du dann mit dem CallObjectMethode was anfangen.
Martin Leim
Egal wie dumm man selbst ist, es gibt immer andere, die noch dümmer sind |
Zitat |
Registriert seit: 12. Jul 2006 39 Beiträge |
#32
Gut also zum Befehl ReleaseIntArrayElement.
Dieses Release heißt ja sowas wie freigabe. Was bedeutet das in Java ? Bin mir nicht ganz sicher, ob arrays einfach mit CallObjectMethode geholt werden können, denn sie sind in Delphi als JObjectArray deklariert. Vllt. können diese auch als einfache Objekte behandelt werden un mit CallObjectMethode geholt werden. Was aber hat es mit dem CallObjectMethodeA befehl oder dem CallObjectMethodeV Befehl auf sich. Welchen Unterschied hat dieser Befehl zum normalen CallObjectMethode? -------------------------------------------------------------------------------------------------- So ein großer Absatz und ein großer Sprung. Ich hab nun einiges an Informationen über die Klasse in Erfahrung gebracht. Also das mit der Bitmap variante ist möglich, hat aber den nachteil, dass ich den schon bestehenden Eventlistener in Delphi neu proggen muss. Mit diesem Eventlistener kann man zum Beispiel ein gebief mit der Maus markieren welches rangezoomt werden soll. Nun gut. Also zum Rendervorgang Grob gesagt gibt es eine Klasse, die nennt sich Map. Dort sind alle informationen über die Karte gespeichert, die später gerendert werden können. Mit Methoden kann ich diese Informationnen ändern. Das wirkt sich dann auf das späterer Rendern aus (z.b. Zoomen). Ich weiß nicht ob ich es ganz richtig verstanden habe, aber gerendert wird mit der Klasse graphics2D und es werden 4 ebenen als Bild gerendert. Einmal Geometrie, dann Text usw.. Geseichnet wird mit einfachen Vectoren dieser Klasse und ich glaub am ende wird diese alles auf einem JPanel gespeichert. Das zusammenfügen und alles macht eine eigens entwickelte Klasse namens MBView oder so. Der sag ich Renderkarte und alles geschieht automatisch. Die Klasse ruft Graphics2d usw. auf. Achso ich hab noch den Befehl PopLocalFrame und PushLocalFrame gefunden. Vielleicht ist der ja des Rätsels lösung. Gruß Simon |
Zitat |
Registriert seit: 13. Dez 2003 Ort: Berlin 1.756 Beiträge |
#33
Zitat von Simon1988:
Was aber hat es mit dem CallObjectMethodeA befehl oder dem CallObjectMethodeV Befehl auf sich. Welchen Unterschied hat dieser Befehl zum normalen CallObjectMethode?
Mit CallObjectMethode werden die Parameter einfach Kommagetrennt (wie bei einem lokalen Aufruf üblich) übergeben. Eine CallObjectMethodeA würde hier ein Array erwarten, in dem alle Variablen stehen (so wie beim Format die Argumente übergeben werden) Und CallObjectMethodeV erwartet die Parameter als va_list. Du siehst letztlich ist es völlig äquivalent (ob intern nicht eh immer in die jeweils andere Form gewandelt wird, merkst du nicht einmal). [ADD1] Werde mich brav an die Forenregeln halten und ranhängen, solange hier keiner zwischendrin antwortet!
Zitat von Simon1988:
Gut also zum Befehl ReleaseIntArrayElement.
Dieses Release heißt ja sowas wie freigabe. Was bedeutet das in Java ? Rufst du Release für dieses Array auf, so muss der Zustand des Arrays im Java-Programm nicht mehr konsistent mit dem in deinem nativen Teil übereinstimmen. Vielmehr würde es dazu kommen, dass die Änderungen nur noch lokal stattfinden und Java das "alte" Array wieder herstellt. [/ADD1] [ADD2] Sorry, hab gerade noch ein paar Dinge nebenbei zu erledigen und gleichzeitig immer ein paar Minuten die ich eh warten muss, so dass ich hier etwas sprunghaft schreibe. Deswegen kommen jetzt erstmal die Stellen, die ich sofort und/oder kurz beantworten kann.
Zitat von Simon1988:
Achso ich hab noch den Befehl PopLocalFrame und PushLocalFrame gefunden. Vielleicht ist der ja des Rätsels lösung.
Ja, hier solltest du Frame wörtlich verstehen, es ist nur eine Rahmen. Genau genommen kannst du dir hier die Größe eines Rahmen, der x-Referenzen aufnimmt zusichern lassen. Mehr ist es nicht. Du kannst hier dann einfach bestimmen ob die Anzahl der Referenzen übergeben werden kann oder ob nicht genug Speicher zur Verfügung steht. Es bezieht sich alles auf die LocalReferences (auch das wäre wohl wieder ein Thema für sich). Ja, die Referenzen müssen auch (geht ja aus dem Namen der Methode hervor) localReferences sein. Die bekommst du wenn du die JNI newObject Methode verwendest (wie auch immer die heißt, weißt schon was ich meine). [/ADD2] [ADD3]
Zitat von Simon1988:
So ein großer Absatz und ein großer Sprung. Ich hab nun einiges an Informationen über die Klasse in Erfahrung gebracht. Also das mit der Bitmap variante ist möglich, hat aber den nachteil, dass ich den schon bestehenden Eventlistener in Delphi neu proggen muss. Mit diesem Eventlistener kann man zum Beispiel ein gebief mit der Maus markieren welches rangezoomt werden soll.
Ich denke der Weg bürgt dann doch einige (mögliche) Probleme in sich. Da ist es dann doch besser, wenn du einfach einen anderen Weg wählst. Was die neuen Listener angeht, so hast du nicht unrecht. Ich denke man kann halt nicht mal eben ein Java Programm komplett in einer anderen Umgebung zum laufen bekommen. Dafür ist Java ja auch nie gedacht gewesen, deswegen muss ich hier sagen, dass JNI trotz allem schon bis zu diesem Punkt gezeigt hat, wie mächtig es trotz alledem ist! Damit du wenig Inkonsistenz zwischen dem Java Programm und dem Delphi-Code hast, würde ich dir ein raten, dass du hier eine Art Adapter baust. Du zeigst einfach das gerendert Bild an (wie ist jetzt erstmal egal). Um an dieses Bild zu kommen gibt es halt eine Schnittstelle, die dir das aktuelle Bild (das sonst auf einem Panel dargestellt werden würde) liefert. Dieses kannst du dann in Delphi anzeigen. Hier gibt es dann für alle implementierten Ereignisse einen Beobachter, der jedes dieser Ereignisse einfach an das Java-Programm weiterreicht. Also um es einfach zu sagen, um das Zoomen sollte sich dann weiterhin dass Java-Programm kümmern, nur der auslöser ist dann halt dein Delphi-Programm. Obwohl, hier kommt dann natürlich das Problem zu tragen, dass du einen Auswahlrahmen dann in Delphi zeichnen musst... Na ja, versuch einfach sowenig wie möglich nach Delphi zu übernehmen und weiterhin Java die Arbeit machen zu lassen, dann sind Änderungen leichter möglich (ohne dass es zu Inkonsistenzen kommt). Wie gesagt, es ist nur meine Meinung, dass das der beste Weg ist, vielleicht gibt es eine schöne Zusammenarbeit zwischen den beiden Welten. [/ADD3] |
Zitat |
Registriert seit: 12. Jul 2006 39 Beiträge |
#34
Gut also nochmal
Ich habe eine Klasse Namens Map. Die hat ganz viele eigenschaften. z.b. wie weit rangezoomt ist , welche elemente, wie gezeichnet werden. Diese Eigenschaften kann ich über Methoden ändern. d.h. diese Methoden kann ich über delphi steuern. Jetzt kommt es zum render. CallVoidMethod('render mir ein BITMAP'); so der spuckt mir jetzt ein Bitmap aus und ich lade diese Bitmap ganz einfach in Delphi. Da gibt es ja auch Komponenten zum laden. So. Jetzt is die Karte da. Wenn ich nun auf ranzoomen drücke dann werden die Eigenschaften der map via Methode geändern, dann ruf ich die Methode Render mir eine BitMap auf und lade sie neu in Delphi. So müsste es erstmal laufen oder ? Ich denke das ist eine gut lösung. und das markieren eines Bestimmten bereiches müsste dann wieder Delphi übernehmen und die Markierten bereichsdaten via Methode an Java übergeben. |
Zitat |
Registriert seit: 13. Dez 2003 Ort: Berlin 1.756 Beiträge |
#35
Zitat von Simon1988:
Gut also nochmal ...
So müsste es erstmal laufen oder ? Ich denke das ist eine gut lösung. Ja, etwas vorsichtig muss du nur mit dem Satz:
Zitat von Simon1988:
CallVoidMethod('render mir ein BITMAP');
so der spuckt mir jetzt ein Bitmap aus und ich lade diese Bitmap ganz einfach in Delphi. Da gibt es ja auch Komponenten zum laden Eine HBitmap könnte eher dass sein, was sich hier eignet. Diese kannst du direkt aus der Höhe, Breite, Farbinformation und den Pixeln erzeugen. Während die ersten Teile hierbei reine Metainfos sind (die sich bei verschiedenen Maps vielleicht nicht mal ändern?) ist letzteres nur ein Array. Also nur falls du den internen Aufbau einer Bitmap nicht kennst, der ist wirklich denkbar einfach. Sagen wir du hast ein Byte pro Pixel an Farbinformation, dann kannst du dir die Bitmap so vorstellen, wie es ihr Name schon vermuten lässt, es ist einfach eine Matrix (Höhe x Breite), in der jede Zelle den Wert des Pixels enthält. Ob man es nun als Matrix betrachtet oder als ein Array ist natürlich äquivalent. Beim Array musst du halt selbst schauen, wo eine Zeile endet (jede Zeile geht natürlich von i*Breite + 0 bis i*Breite + (Breite - 1), mit 0 <= i < Höhe). Jedenfalls hast du dann alles, was du leicht übergeben kannst. Ein Array von Bytes (du wirst sicherlich mindestens ein Byte pro Pixel an Informationen nehmen?), Höhe und Breite (Integer) und die Anzahl der BitsProPixel (ebenfalls Integer, wahrscheinlich konstant). Dein Renderer muss also nur die Pixel, die gesetzt werden in einem Array abspeichern und dieses zur Verfügung stellen. In Delphi solltest du dann keine Probleme haben, mit diesen Daten weiter zu arbeiten. Natürlich kann eine Bitmap (aus der Windows Unit) auch direkt gezeichnet werden (hat ja ein Handle), du kannst aber auch den Umweg über ein TBitmap gehen (achtung, glaube windows kennt auch ein TBitmap). |
Zitat |
Registriert seit: 12. Jul 2006 39 Beiträge |
#36
Also es ist möglich ein Bitmap von dem Objekt "Karte" zu erstellen. Von Java aus.
dann liegt das ding als karte.bmp in einem Ordner vor. dann kommt delphi: procedure TForm1.Render ; var x,y : integer; begin // 2. Objekt erzeugen bild := TBitMap.Create; // 3. BitMap aus Datei laden, damit wird Größe gesetzt bild.LoadFromFile('karte.bmp'); // 4. Eigenschaften einstellen bild.Transparent := true; // 5. Bitmap anzeigen. x und y sind Position Canvas.Draw(x,y,bild); end; so hab ich das grob verstanden. Oder gibt es in Java eine Klasse, die genau so funktioniert und dass man dann die daten direkt, ohne sie auf der Platte zu speichern, übergibt ? |
Zitat |
Registriert seit: 13. Dez 2003 Ort: Berlin 1.756 Beiträge |
#37
So, hab jetzt noch mal etwas zu dem Thema Arrays.
Der Umgang mit Arrays ist ähnlich einfach, wie der mit allem anderen. Solange du mit primitiven Datentypen arbeitest, hast du alle Freiheiten und eine einfache Anbindung. Möchtest du mit einer kompletten Klasse arbeiten, macht es die Sache um einiges schwieriger. Objekte folgen ein paar Besonderheiten, die nicht zuletzt von der Programmiersprache abhängen. JNI ist hauptsächlich für C geschaffen, hier gibt es nicht einmal eine Entsprechung der Klasse. Was Arrays angeht, so hat Chewie ja schon richtig gesagt, dass die in Java auch wieder Objekte sind. Aber für Delphi ist das so egal. JNI geht hier mal wieder von der kleinsten Gemeinsamkeit aus. Ein Array liegt irgendwo im Adressraum der JVM. Das Objekt direkt zu kopieren ist nicht möglich (C könnte nichts mit einem Objekt anfangen). Deshalb wird eine Kopie der Werte gemacht (gilt jetzt für ein Array von einem primitiven Datentyp!) und dir ein Zeiger auf diese Kopie gegeben. Auch hier muss man schauen, wie ein Array in C aussieht. C legt Arrays immer am Stück im Speicher an. Man bekommt einen Zeiger auf das erste Element und kann (da alles am Stück liegt) den Offset von dieser Adresse zu jedem Element im Array bestimmen. Das Problem ist, dass C nicht prüfen kann wie groß das Array ist. Übergibst du das Array als Argument an eine Methode, so übergibst du hier nur den Zeiger auf das erste Element (und i.d.R. noch die Länge des Arrays). Hier kann man aber auch beliebig weit springen und in einem völlig falschen Adressraum landen, der garnichts mehr mit dem eigentlichen Array zu tun hat! JNI ist wie gesagt auf C ausgelegt, was daran liegt, dass sich C Code leicht von sehr unterschiedlichen Sprachen (auch Delphi) verwenden lässt und C auch auf nahezu jeder Plattform verfügbar ist. Jedenfalls bekommst du ein PJINT, was nur ein Zeiger auf ein Integer ist. Diesen kannst du direkt in die Adresse für ein IntegerArray casten, solltest aber bedenken, dass die Länge hier nicht übergeben wird. Wenn du also die length-Funktion von Delphi bemühst, wäre ich mir nicht sicher was sie liefert. Sicherer (und der Weg den man gehen soll, egal ob was anderes klappt oder nicht) ist es, hier die JNI Funktion zu verwenden, die dir die Länge gibt. Wie gesagt, du arbeitest mit einer lokalen Kopie. Hast du die Werte dieses Arrays verändert, so müssen diese von JNI zurückgeschrieben werden (wenn die Änderung übernommen werden soll!). Wurden die Werte nur lokal verwendet, brauchst du sie nicht zurückzuschreiben, nur um sie zu löschen (obwohl auch die Möglichkeit existiert). Jedenfalls kannst du mit einem Flag angeben, ob du commiten möchtest (zurückschreiben, nichts freigeben), nur löschen möchtest oder zurückschreiben und dann löschen. Ist dein Array das Ergebnis einer Funktion, macht zurückschreiben natürlich keinen Sinn! Ich habe das Beispiel JavaGoesDelphi nochmal um ein einfaches Integer Array erweitert. Hoffe es hilft dir weiter mit deiner Frage nach Arrays.
Zitat von Simon1988:
Also es ist möglich ein Bitmap von dem Objekt "Karte" zu erstellen. Von Java aus.
dann liegt das ding als karte.bmp in einem Ordner vor. ... so hab ich das grob verstanden.
Zitat von Simon1988:
Oder gibt es in Java eine Klasse, die genau so funktioniert und dass man dann die daten direkt, ohne sie auf der Platte zu speichern, übergibt ?
Jedenfalls war Sun etwas früh damit dran (Web 2.0 setzt schließlich auf ähnliche Ansätze), aber egal. Java ist jedenfalls eine Sprache die im/mit Netzwerk groß gewurden ist. Die bietet somit jede Menge Alternativen zum direkten Speichern auf einem Datenträger an. Nebenbei, ich glaube JNI ist auch nur ein Netzwerkdienst, der halt lokal läuft. Jedenfalls hast du alle möglichen Alternativen. Das Problem ist, dass die meisten dieser Alternativen das Problem der unterschiedlichen Plattformen haben. So sind Java Klassen komplett anders aufgebaut als die in Delphi. Du kannst nicht einfach einen Java-Stream in einem Delphi-Stream einlesen (da hättest du schon die Probleme, dass du nicht ohne weiteres auf Adressen der virtuellen Maschine zugreifen kannst). Der Einfachste Weg ist es imho, dass du dir einfach so primitiv wie möglich die Daten holst, die du brauchst. Wenn du ein Bitmap erzeugen kannst bzw. eine beliebige Rastergrafik (was mit Java2D schon klappt), dann besteht die eigentlich nur aus zwei Teilen. Das eine ist (wie bereits erwähnt) der Datenteil. Hierbei handelt es sich um ein einfaches Array vom Typ Byte. Das kannst du ohne Probleme übertragen (wie siehst du ja im aktuellen Beispiel). Der Rest sind Metainformationen. Bei einer Bitmap halten diese sich auch deutlich in Grenzen. Was du wirklich brauchst ist die Größe (Höhe und Breite) und die Anzahl der Bits pro Pixel (hier sind 1, 8, 24 und 32) die üblichsten. 1 für Schwarz oder Weiß, 8 für indexierte Farben (wobei du dann noch die Farbpalette bräuchtest) und 24 sowie 32 für Farbbilder, die je ein Byte für R, G und B speichern (plus ein Füllbyte bei 32 Bit). Diese Metadaten sind also eigentlich nicht mehr als ein paar primitive Datentypen. In Delphi kannst du aus ebend diesen Daten sehr leicht wieder eine Bitmap erzeugen. Das sollte (imho) der einfachste und schnellste Weg sein. Das mit dem Laden- und speichern der Datei führt dann natürlich zu höheren Zugriffszeiten (durch die Festplatte) und hier auch zu evtl. Problemen (Lese/Schreibrechte, Platz auf der Platte, existierende Dateien...) |
Zitat |
Registriert seit: 12. Jul 2006 39 Beiträge |
#38
Hey, das trifft sich gut mit den array. dieses Thema ist mir mit der JNI noch etwaqs suspekt ^^
1) Also erstemal gibt es ja befehle wie GetIntArrayElements GetByteArrayElements usw. Diese holen sich alle Elemente des arrays ?! oder ? dann gibt es noch den Befehl GetObjectArrayElement, der immer nur ein Element des Objekts holt ?! was macht das für einen Sinn ? 2) In dem Buffer werden ja alle array Elemente als JIntarray oder so gespeichert. buffer := JIntArray(self.jvm.JniEnv.CallObjectMethod(instanc e, mid, [])); was hat aber die Funktion JIntArray da zu suchen ? und warum wird dann später nochmal alles auf der variablen p gespeichert , die glaube ich ein array Pointer ist ?! p := PIntegerArray(self.jvm.JniEnv.GetIntArrayElements( buffer, isCopy)); 3) muhaha und mit Pointern kenn ich mich nich aus kann ich die behandeln wie ein array , sprich EinElement := p[5] ; ? 4) Zum letzten befehl self.jvm.JniEnv.ReleaseIntArrayElements(buffer, PJInt(p), JNI_ABORT); Welche Parameter müssen da übergeben werden. Klar erstmal der array, der im Buffer gespeichert ist. Was soll der zweite übergabewer`? das ist doch dann doppeltgemoppelt ^^. ist doch das gleiche wie der buffer nur als Pointer oder ? Gruß Simon Fragen über Fragen |
Zitat |
Registriert seit: 13. Dez 2003 Ort: Berlin 1.756 Beiträge |
#39
Zitat von Simon1988:
In dem Buffer werden ja alle array Elemente als JIntarray oder so gespeichert.
buffer := JIntArray(self.jvm.JniEnv.CallObjectMethod(instanc e, mid, [])); was hat aber die Funktion JIntArray da zu suchen ? Wichtig ist, dass du hier den Datentyp anpasst. Die Methode self.jvm.JniEnv.CallObjectMethod gibt dir ein JObject zurück. Dies liegt daran, dass jede Instanz ein Nachfahre von JObject sein muss. Da Arrays in Java Klassen sind und du hier die Instanz eines Arrays bekommst, ist dieses Array also ein Nachfahre von Object (in Java, entspricht JObject in Delphi). Nun möchtest du aber sagen, dass du ein bestimmten Typ von Objekt hast, nämlich ein Array, dazu castest du dieses einfach. Stimmt der Typ in den du castest, wird alles klappen (andernfalls gibt es einen Laufzeitfehler!) Hier weißt du ja, dass der eigentlich Rückgabetyp ein Int-Array ist.
Zitat von Simon1988:
und warum wird dann später nochmal alles auf der variablen p gespeichert , die glaube ich ein array Pointer ist ?!
p := PIntegerArray(self.jvm.JniEnv.GetIntArrayElements( buffer, isCopy)); Jedenfalls legst du mit GetIntArrayElements eine Kopie an, die von einem C Programm verwendet werden kann. Wie auch schon gesagt, sind in C Arrays nur Zeiger auf das erste Element. Wenn du das Array A = [1,2,3,4,5] in C übergeben möchtest, es mit 4 Byte Werten gefüllt ist, so würdest du einfach die Adresse von A[0] übergeben (@A[0] in Delphi Syntax). Nun weißt du, dass A[i] einfach der Wert ist, der an @A[0] + (i * 4) liegt. 4 Bytes ist jedes Datum groß und bei @A[0] fängst du an. So einfach sind dann auch schon die Zeiger hier. Was dir GetIntArrayElements liefert ist nicht das komplette Arrays, sondern nur die Adresse @A[0]. In Delphi gibt es (anders als in C) die Möglichkeit direkt mit dyn. Arrays zu arbeiten, dass ist das was du hier siehst. Die gelieferte Adresse wird einfach gecastet (und der Datentyp dahinter als ein IntegerArray behandelt). Danach kannst du wie mit einem normalen Array of Integer damit arbeiten. Beim Zugriff p[i] wird automatisch dereferenziert (wenn ich mich nicht irre). Jedenfalls siehst du irgendwo im Beispiel wie man auf die Elemente zugreift (sorry, gerade viel zu arbeiten).
Zitat von Simon1988:
Zum letzten befehl
self.jvm.JniEnv.ReleaseIntArrayElements(buffer, PJInt(p), JNI_ABORT); Welche Parameter müssen da übergeben werden. Klar erstmal der array, der im Buffer gespeichert ist. Was soll der zweite übergabewer`? das ist doch dann doppeltgemoppelt ^^. ist doch das gleiche wie der buffer nur als Pointer oder ? Nimmst du nur einen kleinen Teil, so kostet das kopieren weniger Zeit. Würdest du die Werte verändern und zurückschreiben, muss Java aber wissen, dass es nur ein kleiner Teil war den du da hast. Sagen wir du hast in Java ein Array von 10.001 Elementen und veränderst nur die letzten 10. Dann möchtest du die sicher auch wieder an die Stellen 9.990 - 10.000 schreiben. Dafür ist der zweite Parameter gut! Hoffe ich konnte dir ein paar Fragen beantworten. |
Zitat |
Registriert seit: 13. Dez 2003 Ort: Berlin 1.756 Beiträge |
#40
HI,
ich habe noch eine kleine Anmerkung zu den Beispielen zu machen. bucchi war so freundlich mich auf ein Problem aufmerksam zu machen. In den Beispielen liegen die .class Dateien als Java 1.5/5.0 Kompilate vor. Diese sind so leider nicht mit Java 1.4.x (und anderen) kompatibel. Führt man das Beispiel aus, so wird die Klasse nicht gefunden (entsprechende Fehlermeldung wird ausgelöst). Zur Umgehung dieses Problems reicht es die .java Datei (\java\src\package1\subpackage\TestClass.java) mit dem eingesetzten JDK zu kompilieren (oder z.B. mit einem Compilerschalter im Kompatiblitätsmodus). Die .class Datei muss entsprechend dem Classpath abgelegt werden (\java\bin\package1\subpackage\TestClass.class), dann klappt's auch mit der 1.4.x Danke nochmal an bucchi Gruß Der Unwissende |
Zitat |
Ansicht |
Linear-Darstellung |
Zur Hybrid-Darstellung wechseln |
Zur Baum-Darstellung wechseln |
ForumregelnEs ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.
BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus. Trackbacks are an
Pingbacks are an
Refbacks are aus
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
Gehe zu... |
LinkBack |
LinkBack URL |
About LinkBacks |