BlitzMax mit Assembler und C/(C++)
Übersicht

![]() |
ThunderBetreff: BlitzMax mit Assembler und C/(C++) |
![]() Antworten mit Zitat ![]() |
---|---|---|
Hallo,
in diesem Tutorial, das voraussichtlich aus drei Teilen bestehen wird, möchte ich erklären, wie man BlitzMax-Code dazu bringt mit Assembler oder C/C++ zu interagieren. Auf C++ werde ich nicht explizit eingehen, weil ich mich damit nicht wirklich beschäftige und weil das meiste, was nicht mit Objektorientierung zusammenhängt in C und C++ gleich ist. Tiefergehende Erklärungen sind blau markiert und zum allgemeinen Verständnis nicht unbedingt notwendig. Wichtige Anmerkungen sind rot gefärbt. Teil 1 Grundlagen zur Zusammenarbeit von BlitzMax- und C-Code Code! BlitzMax: [AUSKLAPPEN] SuperStrict Code: [AUSKLAPPEN] //imp1.c
int addnumbers(int zahl1, int zahl2){ return zahl1+zahl2; } Die C-Funktion mit Namen 'addnumbers' nimmt zwei Integer als Argumente und gibt die Summe der beiden zurück. Nicht wirklich sinnvoll, aber genug für eine Demonstration. Um den C-Code in den BlitzMax-Code einzubinden muss man ihn mit 'Import' importieren: BlitzMax: [AUSKLAPPEN] Import "imp1.c" 'Import nimmt den Namen der Datei als Parameter Wichtig: Die C-Codedatei muss auf '.c' enden! (für C++ entweder auf '.cpp' oder auf '.cxx') Mit Import eingefügte Code-Dateien werden zuerst kompiliert und dann statisch dazugelinkt. Auf Windows wird COFF, auf Linux ELF als Objektdatei-Format verwendet. Ein Import alleine reicht nicht um die Funktion direkt aufrufen zu können und das ist auch gut so. In einem Extern-Block im BlitzMax-Code muss man im die Funktionen, die man importieren will definieren. Funktionen, die zwar im C-Code vorhanden sind, aber nicht im Extern-Block definiert wurden, können auch nicht von BlitzMax aus aufgerufen werden. Ein Extern-Block anhand unseres Beispiels könnte so aussehen: BlitzMax: [AUSKLAPPEN] Extern Strings und Instanzen von User-Defined Types können nicht ohne weiteres von einer C-Funktion bearbeitet werden. Denn die eigentliche String-Variable bzw. die eigentliche Variable, die die Instanz hält, ist nur ein Zeiger (eine Referenz) auf eine Struktur. Das Problem ist, dass diese Strukturen auch versteckte Daten enthalten. Für Strings habe ich eine Lösung parat, doch dazu später. Es lassen sich nicht nur externe Funktionen einbinden, sondern auch externe, globale Variablen. Das hat Sinn, wenn diese hauptsächlich von externen Funktionen gebraucht werden, aber auch manchmal von BlitzMax-Funktionen. Ein Beispiel für eine häufig benutzte, externe Variable ist 'AppTitle'. Wenn man also in einem C-Code zum Beispiel eine globale Integervariable definiert, wie hier: Code: [AUSKLAPPEN] //imp2.c
int x=50; void print_x(void){ // die Funktion soll zeigen, wie C die Variable x "sieht" printf("[C] X: %d\n",x); } und diesen C-Code importiert, ist die Variable, genauso wie eine Funktion, nicht ohne weiteres ansprechbar. Man muss sie, innerhalb eines Extern-Blocks mit Global definieren: BlitzMax: [AUSKLAPPEN] SuperStrict Der Startwert von x wird außerhalb (in der exvar.c) festgelegt. Von dort wird gestartet. Bei einem Import einer Variable kann sie von Innen genauso angesprochen werden, wie von Außen. Das kann kompliziert und gefährlich werden (besonders bei Zeigern)! Trotzdem kann es sehr nützlich sein. Wichtig! Der Startwert einer extern deklarierten Variablen kann nicht im Extern-Block angegeben oder geändert werden! Wichtig! Die globale Variable darf in C/C++ nicht als 'static' deklariert werden! Wichtig! Ist die Variable in C/C++ als 'const' deklariert, muss sie trotzdem mit Global importiert werden. Daraus folgt, dass BlitzMax nicht weiß, dass es eigentlich eine Konstante ist und bei einer Manipulation an der Variable gibt es eine EXCEPTION_ACCESS_VIOLATION. BlitzMax bietet noch ein Feature, das sich auf Variablen und Funktionen anwenden lässt. Man kann die externe Funktion innerhalb des BlitzMax-Codes umbenennen. Außerhalb wird sie immernoch mit ihrem Originalnamen angesprochen, doch im BlitzMax-Quelltext muss sie mit dem angegebenen Namen angesprochen werden. Dieses Umbenennen hat aber keine Auswirkung auf die Ansprechbarkeit der Variable oder der Funktion. Ich nehme den Code aus dem Beispiel 1, modifiziere aber den BlitzMax-Code leicht: BlitzMax: [AUSKLAPPEN] SuperStrict Das ganze funktioniert mit externen Variablen genauso, mit einer Zuweisung per = und einem String der den Originalnamen der externen Funktion/Variable angibt. Es mag sehr falsch aussehen, aber innerhalb des Extern-Blocks ist das legal. Um BlitzMax-Funktionen in C aufrufen zu können, muss man nur im C-Code vor den Funktionsnamen ein 'bb_' stellen. Ein Beispiel: BlitzMax: [AUSKLAPPEN] SuperStrict Code: [AUSKLAPPEN] //imp3.c
/* Bei mir hat es ohne Vorwärtsdeklaration der Funktion bb_AddNumbers funktioniert. Sollte das bei euch nicht so sein, so müsst ihr folgende Zeile in den C-Code einfügen: int bb_AddNumbers(int,int); */ void cmain(void){ printf("5+10 = %d\n",bb_AddNumbers(5,10)); } Um eine globale Variable aus dem BlitzMax-Code zu teilen, muss man, wie bei Funktionen im C-Code ein 'bb_' vor den eigentlichen Namen stellen und die Variable im C-Code als extern deklarieren: BlitzMax: [AUSKLAPPEN] SuperStrict Code: [AUSKLAPPEN] //imp4.c
extern int bb_x; //diese Zeile ist wichtig! void cmain(void){ printf("X: %d\n",bb_x); } So, das wars erst Mal. Ich freue mich über konstruktive Vorschläge zur Erweiterung und über Korrekturen, falls ich irgendwo einen Fehler gemacht oder etwas schlecht erklärt habe. |
||
Meine Sachen: https://bitbucket.org/chtisgit https://github.com/chtisgit |
![]() |
Thunder |
![]() Antworten mit Zitat ![]() |
---|---|---|
Teil 2 Grundlagen zur Zusammenarbeit von BlitzMax- und Assemblercode
Gleich vorweg zwei Dinge: 1. Das ist kein Grundlagen-Tutorial zu Assembler, sondern ein Tutorial für die, die Assembler schon können und es in Verbindung mit BlitzMax verwenden wollen. Ich werde daher am Assemblercode nur Besonderheiten, die man im Zusammenhang mit BlitzMax braucht, erklären. 2. BlitzMax ist für den flat assembler (fasm) ausgelegt, daher werde ich auch nur mit Code für den flat assembler arbeiten. Trotzdem möchte ich hier gesagt haben, dass man auch mit anderen Assemblern entwickeln kann. Eine knappe Erklärung gibt es am Ende dieses Teils. Beim flat assembler wird das Ausgabeformat, das der assembler am Ende produziert, in der Datei festgelegt und nicht von einem Kommandozeilenparameter. Das heißt auf Windows muss am Anfang jeder fasm-Assembler-Datei die Zeile Code: [AUSKLAPPEN] format MS COFF
und auf Linux die Zeile Code: [AUSKLAPPEN] format ELF
stehen. MS COFF steht für Microsoft Common Object File Format. Das COFF Format gibt es schon sehr lange, es wurde mit Unix eingeführt (nicht die erste Version von Unix, laut Wikipedia Unix V). Microsoft hat dieses Objektcodeformat übernommen und etwas modifiziert. ELF steht für Executable and Linkable Format. Es hat auf vielen Unixsystemen das COFF Format abgelöst und wird heute auf den meisten Linux-Distributionen und einigen anderen Betriebssystemen als Objektdateiformat verwendet. Außerdem wird es auf diesen Systemen meistens gleichzeitig auch als ausführbares Format verwendet - daher executable and linkable. Wenn man in Assembler eine Funktion schreibt und die in BlitzMax aufrufen möchte, so muss man noch folgendes beachten: 1. Der Funktionsname muss mit einer Underline ( '_' ) beginnen. Einfach weil es sich eingebürgert hat, dass jeder Compiler vor jeden Funktionsnamen eine Underline macht, damit es keine Überschneidungen mit Assemblerbefehlen gibt. 2. Das Label, das die Funktion einleitet muss als public deklariert sein, damit der Name in die Objektdatei geschrieben wird. 3. Die Funktion muss, wie auch bei C, im BlitzMax-Code in einem Extern-Block deklariert werden (und zwar ohne die vorangehende Underline). 4. Die Assemblerdatei muss die Endung .s haben. Und weil Code mehr sagt als tausend Worte, hier ein Beispiel: Code: [AUSKLAPPEN] ;asm_beispiel1.s
format MS COFF ; Für Linux mit ELF public _Add _Add: mov eax,[esp+4] mov ebx,[esp+8] add eax,ebx ret BlitzMax: [AUSKLAPPEN] SuperStrict Importieren von Variablen Auch Variablen können (wie mit C) in BlitzMax sichtbar gemacht werden. Diese müssen, wie Funktionen, im Assemblercode die Underline als Präfix erhalten und als public deklariert sein. In BlitzMax muss die Variable in einem Extern-Block als Global deklariert werden. Das Umbenennen einer Variable funktioniert wie in Teil 1, da es eine Eigentheit von BlitzMax und nicht von C oder Assembler ist. Hier ein Beispiel: Code: [AUSKLAPPEN] ;test.s
format MS COFF public _Variable _Variable: dd 1337 BlitzMax: [AUSKLAPPEN] SuperStrict BlitzMax braucht für Variablen immer nur ihre 'wahre' Größe. Das heißt, eine Variable vom Typ Byte ist wirklich nur ein Byte groß, ein Variable vom Typ Short ist auch nur ein Word groß ... (ist nicht selbstverständlich) Die Werte der Variablen müssen dementsprechend mit db, dw beziehungsweise dd festgelegt werden. Zeiger haben eine Größe von zwei Words. Strings sind keine Primitive, dennoch wäre eine Stringvariable nichts anderes als zwei Words, die auf eine Blitzmax-spezifische Stringstruktur zeigen. Wie man mit Strings umgehen kann (direkt und indirekt), kommt im dritten Teil dieser Tutorialserie. Float und Double Mit Floats und Doubles habe ich in Assembler noch nicht gearbeitet, daher kann ich dazu nichts sagen, werde es aber bei Gelegenheit hier einfügen. Bei der Arbeit mit Floats und Doubles möchte ich aber eher von Assembler abraten, da C an dieser Stelle stark vereinfacht und trotzdem sehr gut optimiert. BlitzMax-Funktionen in Assembler aufrufen Ich habe bis jetzt die Erfahrung gemacht, dass alle Funktionen, die in BlitzMax-Modulen deklariert wurden '_bb' als Präfix erhalten. Man muss also die Funktion, die man in Assembler aufrufen will, mit diesem Präfix als extern deklarieren. Hier ein kleines Beispiel: Code: [AUSKLAPPEN] format MS COFF
extrn _bbWriteStdout extrn _bbReadStdin public _cat _cat: push ebp mov ebp,esp sub esp,8 call _bbReadStdin mov [ebp-4],eax push eax call _bbWriteStdout add esp,4 mov eax,[ebp-4] mov esp,ebp pop ebp ret BlitzMax: [AUSKLAPPEN] SuperStrict Wichtig! Es muss immer darauf geachtet werden, dass die Funktion, die man im Assemblercode aufrufen will, auch wirklich (in BlitzMax) importiert wird! Sonst bekommt ihr böse Linkerfehler! Wichtig! selbstdefinierte Funktionen bekommen ein anderes Präfix; nämlich '_bb_' ! BlitzMax mit anderen Assemblern bmk ruft von selbst auf Linux- und Windowssystemen den flat assembler auf, wenn eine Assemblerdatei importiert werden soll. Daher hat man die wenigsten Probleme, wenn man fasm verwendet. Ein einfacher Weg, einen anderen Assembler zu verwenden ist, einfach nach jeder Änderung an der Assemblerdatei jenen Assembler aufzurufen und die Assemblerdatei nach MS COFF bzw. ELF zu assemblieren und mit Import die entstandene Objektdatei zu importieren. |
||
- Zuletzt bearbeitet von Thunder am So, Jan 09, 2011 16:01, insgesamt 2-mal bearbeitet
![]() |
Thunder |
![]() Antworten mit Zitat ![]() |
---|---|---|
Teil 3 Tipps und Tricks im Umgang mit BlitzMax in Verbindung mit C/C++ oder Assembler
Dieser (letzte) Teil der Tutorialreihe beschäftigt sich tiefer mit BlitzMax und zeigt einige Tipps, mit denen mancher Programmcode kürzer wird und einige Tricks im Zusammenhang mit Assembler/C(++). Datentypen und ihre Größen
Short - 16 Bit (ohne Vorzeichen) Int - 32 Bit (mit Vorzeichen) Long - 64 Bit (mit Vorzeichen) Float - 32 Bit (mit Vorzeichen) Double - 64 Bit (mit Vorzeichen) Alle Zeiger - 32 Bit Tipps zu Zeigern Wenn man einen Zeiger übergibt, übergibt man eigentlich (auf 32-Bit PCs) eine Adresse mit einer Größe von 4 Byte. Ich kann also einen BlitzMax Integer an eine C-Funktion übergeben, die eigentlich einen Zeiger (egal welchen Datentyps) als Argument erwartet, weil beide 4 Byte groß sind - einfach indem ich im Extern Block von BlitzMax statt des Zeigertyps den Integertyp angebe. Niemand wird was davon erfahren (nicht der Compiler, nicht der Linker ...). Geht natürlich genauso in die andere Richtung. Das kann nützlich sein, weil es (meiner Meinung nach) unnötige Typumwandlungen in BlitzMax oder C ersparen kann. Schluss mit Meldungen wie "passing arg 1 of 'xyz' makes integer from pointer without a cast". Paramaterattribut Var Was der BlitzMax-Befehl 'Var', der nach einer Parametervariable in einer Funktionsdeklaration stehen darf, genau macht, muss erst Mal geklärt werden: - Der Programmierer wird dazu gezwungen eine Variable zu übergeben (es darf kein direkter Wert übergeben werden) - Die Variable wird per call-by-reference übergeben (d.h. es wird ein Zeiger auf Ihren Speicherbereich übergeben. Innerhalb der Funktion wird bei jedem Aufscheinen der Variable, statt der Adresse des Zeigers der Wert an der Adresse des Zeigers verwendet -> eine Manipulation innerhalb der Funktion wird sich auch auf den Wert der Variable außerhalb der Funktion auswirken) Das ist ein sehr nützlicher Befehl, doch um das ganze auf Code-Ebene zu erklären, habe ich dieses Beispiel geschrieben: BlitzMax: [AUSKLAPPEN] SuperStrict Das heißt also, wenn ich einem Parameter einer (von BlitzMax aus gesehen) externen Funktion das Var-Attribut gebe, muss ich in C/C++ oder Assembler einen Zeiger erwarten. AT&T-Syntax Wer zwar Assembler-Code schreiben will, aber die Intel-Syntax nicht mag (und nicht den Umweg gehen will, den ich im letzten Teil kurz ansprach), kann sich eine C-Funktion schreiben, die Inline-Assembler enthält. Das funktioniert, weil der gcc standardmäßig nach AT&T-Syntax Assembler kompiliert. Da der C-Compiler aber den Header und den Footer einer Funktion selber schreibt ist man an diesen Rahmen gebunden. String übergeben Einen String übergeben ist eine leichte Sache. Gegeben sei eine Variable s vom Typ String und eine externe C-Funktion die einen Zeiger auf char (char*) erwartet. Dann markiere ich diesen Parameter in BlitzMax als 'Byte Ptr' und übergebe ihm einfach s oder s.ToCString(). Die Methode ToCString der Klasse String wandelt (wie der Name schon sagt) eine BlitzMax-Stringstruktur in einen einfachen C-String um. Normalerweise ist der Aufruf der Methode nicht explizit notwendig - in dem Fall macht BlitzMax die Typumwandlung automatisch. String zurückgeben Strings zurückgeben ist, soweit ich weiß, nicht einfach (wenn jemand einen einfacheren Weg kennt, bitte melden). Ich importiere dazu das Modul pub.stdc, allokiere in der externen Funktion Speicher für den String (Wichtig! Der Speicher muss dynamisch allokiert sein. Es darf kein lokales Char-Array verwendet werden!), beschreibe ihn (er muss mit dem Nullterminierungszeichen; "~0" , \0 , Chr(0)) enden und gebe die Adresse zurück. In BlitzMax nehme ich die Adresse und wandle den C-String mit Hilfe der Funktion String.FromCString() in einen String um. Nachdem ich das getan habe, gebe ich den Speicher des C-Strings, den ich nicht mehr brauche, mit free_ (aus pub.stdc) frei. BlitzMax: [AUSKLAPPEN] SuperStrict Code: [AUSKLAPPEN] #include <ctype.h>
#include <stdlib.h> #include <string.h> char* Rot13(char *s){ char *x=malloc(strlen(s)+1),*y=x; for(;*s!=0;s++,y++){ if(isalpha(*s)) *y=(*s-((isupper(*s))?65:97)+13) % 26+(isupper(*s)?65:97); else *y=*s; } *y=0; return x; } Direkter Stringzugriff - Übergabe an Nicht-BlitzMax-Code Ich hab mich ein bisschen in von Blitzmax generierten Assemblercodes umgesehen und dabei herausgefunden welchem Schema Strings folgen. Ein Beispielstring ("hallo welt!") sähe so aus: Code: [AUSKLAPPEN] dd _bbStringClass
dd 2147483647 dd 11 dw 104,97,108,108,111,32,119,101,108,116,33 Für Nicht-Assemblerianer: - Das ganze ist eine Struktur. C-Programmierer können sich also eine struct mit folgendem Aufbau vorstellen: - Der erste Integer (4 Byte Wert) enthält einen Zeiger auf die Stringklasse. Was da alles genau drinnen ist, weiß ich auch nicht. - Der zweite Integer enthält den Wert (256^4/2-1). Dabei handelt es sich wahrscheinlich um die Maximallänge des Strings (bin mir aber nicht sicher). - Der dritte Integer enthält die Länge des Strings. - Darauf folgt ein Short-Array (Array aus 2 Byte Werten) mit jeweils einem Zeichen (BlitzMax dürfte UTF-8 Strings verwenden, daher hat ein ASCII-Zeichen im BlitzMax-String denselben Wert, ist aber ein Short groß). Eine struct für C sähe so aus: Code: [AUSKLAPPEN] struct BMXString{
void *class; int max; int len; unsigned short string[]; }; Echte Assemblerprogrammierer werden aus der Struktur und der vorangehenden Erklärung die nötigen Informationen zur Umsetzung in Assembler herauslesen können! ![]() Wichtig ist, dass die externe Funktion nicht ein Objekt dieser Struktur, sondern einen Zeiger auf so eine Struktur erwarten muss. Außerdem wird, soweit ich das beobachtet habe, ein Zeiger auf den Originalstring, nicht auf eine Kopie, übergeben. Das heißt, Änderungen haben einen direkten Effekt auf den String in BlitzMax. Wichtig! Das Schreiben auf Strings mit der direkten Methode ist absolut nicht zu empfehlen. Wenn der neue String nämlich länger ist als der ursprüngliche, kann das zu ungültigen Speicherzugriffen führen! Hier ist ein einfaches Programm, das einen in BlitzMax definierten String in einer C-Funktion ausgibt: Code: [AUSKLAPPEN] struct BMXString{
void *class; int max; int len; unsigned short string[]; }; void sout(struct BMXString *s){ int i; for(i=0;i<s->len;i++) putchar((char) s->string[i]); } BlitzMax: [AUSKLAPPEN] SuperStrict Falls ich noch ein paar Hacks entdecke oder auf welche aufmerksam gemacht werde, werden die natürlich reineditiert. |
||
- Zuletzt bearbeitet von Thunder am So, Jan 09, 2011 16:36, insgesamt 2-mal bearbeitet
Macintosh |
![]() Antworten mit Zitat ![]() |
|
---|---|---|
Sehr schön... und vorallem nützlich :)
Danke |
||
![]() |
Thunder |
![]() Antworten mit Zitat ![]() |
---|---|---|
Die Tutorialserie ist jetzt fertig und ich freue mich über Fragen, Wünsche, Anregungen und/oder Beschwerden!
Und bitte entschuldigt den sanften Push. Ich wollte nur, dass die Änderungen auch bemerkt werden. mfg Thunder |
||
Meine Sachen: https://bitbucket.org/chtisgit https://github.com/chtisgit |
![]() |
mpmxyz |
![]() Antworten mit Zitat ![]() |
---|---|---|
Schönes Tutorial!
Dieses Zeichen kennzeichnet C-Strings: $z Man kann dieses anscheinend auch bei Rückgaben verwenden: BlitzMax: [AUSKLAPPEN] Function lua_getinfo:Int (lua_state:Byte Ptr, what$z, ar:lua_Debug Ptr) ' no ~0 expected mfG mpmxyz Edit: Hast du dir die Codes der BlitzMax-Strings durchgelesen? Da gibt es zum Beispiel auch das hier: Code: [AUSKLAPPEN] struct BBString{
BBClass* clas; int refs; int length; BBChar buf[]; }; (im Modul brl.Blitz "blitz_string.h") |
||
Moin Moin!
Projekte: DBPC CodeCruncher Mandelbrot-Renderer |
- Zuletzt bearbeitet von mpmxyz am So, Jan 09, 2011 17:31, insgesamt 2-mal bearbeitet
![]() |
Lord Stweccys |
![]() Antworten mit Zitat ![]() |
---|---|---|
Klasse Tutorial, schöne Übersicht außerdem.
Bei BlitzMax ist das ganze ja nur sehr schäbig dokumentiert, also sind solche Tutorials garantiert hilfreich. Danke für das Tut ![]() Mfg, LordSt |
||
Übersicht


Powered by phpBB © 2001 - 2006, phpBB Group