Dieser und noch weitere Artikel wurde von joomoo erstellt.


Folgende Themen werden von diesem Artikel berührt:


Druckversion des Artikels


1 Vorwort

1.1 Einleitung

Willkommen zu meinem zweiten Artikel, in dem es sich um Softwareinstallation unter Linux dreht. Wir werden ein Beispiel-Programm in ein installierbares Paket mit Menüeintrag verwandeln.

1.2 Voraussetzungen

Die verwendete IDE bzw. das verwendete Buildsystem sollte keine Rolle spielen. Auch sind keine Kenntnisse über gtkmm notwendig, da dies nur eine Nebenrolle spielt und das Beispiel auf andere Libraries übertragbar sein sollte.

Sie sollten einfache C- und C++-Kenntnisse mitbringen und bereits etwas Erfahrung mit der Bash, sowie mit Linux allgemein haben.

2 Das Beispielprogramm

Bei unserem Beispiel handelt es sich um ein einfaches gtkmm-Programm, welches ein Bild anzeigt. Es besteht aus einer Source-Datei namens main.cpp mit folgendem Inhalt:

C++:
1
2
3
4
5
6
7
8
9
10
11
#include <gtkmm.h>
 
int main(int argc, char** argv)
{
    Gtk::Main kit(argc, argv);
    Gtk::Window win;
    Gtk::Image img("hallowelt.png");
    win.add(img);
    win.show_all();
    kit.run(win);
}

sowie der hallowelt.png, ein simples PNG-Bild, welches im selben Verzeichnis wie unser Beispiel liegt:



Kompilieren wir nun dieses Beispiel mit:

Code:
g++ `pkg-config gtkmm-2.4 --cflags --libs` main.cpp -o hallowelt


Anschließend starten wir das Programm mit:

Code:
./hallowelt


Oben links sollte ein Fenster erscheinen, welches das Bild darstellt:



Schauen wir uns nun die wichtige Zeile in diesem Programm an:

Code:
Gtk::Image img("hallowelt.png");


Hier wird die hallowelt.png geladen, welche im gleichen Verzeichnis wie die Binärdatei liegt. Es handelt sich um einen relativen Pfad, doch zu was ist er relativ? Leider nicht zum Ort der Binärdatei, wie angenommen, sondern zum Arbeitsverzeichnis.
Dies lässt sich leicht zeigen. Wir verändern das Arbeitsverzeichnis, indem wir einen Ordner hoch springen:

Code:
~/C++/hallowelt$ cd ..
~/C++$


Nun starten wir unser Programm nochmal:

Code:
~/C++$ ./hallowelt/hallowelt


Ein kleines Fenster erscheint, welches einen Platzhalter enthält, da das Bild nicht gefunden wurde. Es wurde versucht die Datei mit dem Pfad ~/C++/hallowelt.png zu öffnen.

3 BinReloc

Um dieses Problem zu beheben, verwenden wir die C-Library "BinReloc", welche eine Funktion bereitstellt, um den Ort der Binärdatei herauszufinden. Unter ftp://ftp.sunsite.dk/proj ....... tools/binreloc-2.0.tar.gz kann man sich den Sourcecode herunterladen. Nach dem Entpacken wechseln wir in der Konsole zu dem Verzeichnis und lassen uns Header und Source erstellen:

Code:
~/Desktop/binreloc-2.0$ ./generate.pl normal
Source code written to 'binreloc.c'
Header written to 'binreloc.h'
~/Desktop/binreloc-2.0$


Nun können wir binreloc.h und binreloc.c in unser hallowelt-Verzeichnis kopieren.
Wir binden die Header-Datei ein und erstellen eine Funktion, die BinReloc initialisiert:

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <gtkmm.h>
#include <iostream>
#include "binreloc.h"
 
void InitBinReloc()
{
    BrInitError error; // Hier wird der Error-Code gespeichert
    if(!br_init(&error)) // Gibt true zurück, wenn alles gut ging
    {
        std::cerr << "Warning: BinReloc failed to initialize (error code " << error << ")\n"
                  << "Will fallback to hardcoded default path." << std::endl;
    }
}


Nun benutzen wir die Funktion char* br_find_exe_dir(const char* fallback), welche uns den Pfad als C-String zurückgibt. Sollte BinReloc nicht richtig initialisiert worden sein, bekommen wir eine Kopie von fallback. Den C-String müssen wir per Hand wieder freigeben, deswegen kapseln wir uns die Funktion:

C++:
std::string ExeDir()
{
    char* temp = br_find_exe_dir("");
    std::string exe_dir(temp);
    free(temp);
    return exe_dir;
}


In der main-Funktion ergänzen wir das Initialisieren von BinReloc:

C++:
int main(int argc, char** argv)
{
    InitBinReloc();
   
    Gtk::Main kit(argc, argv);
    Gtk::Window win;


Nun kommen wir zum Laden der Bilddatei. Hierbei verwenden wir jetzt einen absoluten Pfad, der mit dem Pfad der Binärdatei beginnt:

C++:
    Gtk::Image img(ExeDir() + "/hallowelt.png");
 
    win.add(img);
    win.show_all();
    kit.run(win);
}


Jetzt können wir unser Programm kompilieren und testen. Zuerst kompilieren wir die BinReloc-Library mit dem C-Compiler. Dabei muss die Variable ENABLE_BINRELOC gesetzt sein:

Code:
~/C++/hallowelt$ gcc -DENABLE_BINRELOC -o binreloc.o -c binreloc.c


Anschließend folgt unser Programm. Wir testen ob das Bild auch angezeigt wird, wenn wir das Arbeitsverzeichnis ändern:

Code:
~/C++/hallowelt$ g++ `pkg-config gtkmm-2.4 --cflags --libs` main.cpp binreloc.o -o hallowelt
~/C++/hallowelt$ ./hallowelt
~/C++/hallowelt$ cd ..
~/C++$ ./hallowelt/hallowelt
~/C++$


In beiden Fällen sollte das Bild angezeigt werden.

4 Die Linux-Verzeichnisstruktur

Doch unter Linux liegen Dateien eines Programms nicht in einem Ordner. Stattdessen gibt es verschiedene Ordner, die entsprechende Typen der Programmdateien enthalten. Das einfachste Beispiel ist /usr/bin/. Hier wird die Binärdatei jedes Programms gespeichert. Daten, wie unser Bild, werden in /usr/share/<Programmname>/ abgelegt.

Da es auch unter Linux die Möglichkeit gibt, Programme nur für einen Benutzer zu installieren, können diese beiden Pfade auch wie folgt lauten:

Code:
/usr/bin/                  -> [b]/home/<Benutzername>/.local/bin/[/b]
/usr/share/<Programmname>/ -> [b]/home/<Benutzername>/.local/share/<Programmname>/[/b]


Der Ordner .local existiert bereits im Home-Verzeichnis, ist allerdings versteckt (unter Linux werden alle Dateien versteckt, die mit einem Punkt beginnen).
Eine weitere Möglichkeit wäre die Installation auf einer anderen Festplatte:

Code:
/usr/bin/                  -> [b]/media/sda2/usr/bin/[/b]
/usr/share/<Programmname>/ -> [b]/media/sda2/usr/share/<Programmname>/[/b]


Alle diese Pfade sind gleich, bis auf den so genannten Prefix, der in diesen drei Beispielen /usr, /home/jhasse/.local und /media/sda2/usr wäre. Um unser Programm richtig in Linux zu integrieren, müssen wir auch solch eine Struktur aufbauen. Dazu erstellen wir in unserem hallowelt-Ordner einen bin-Ordner und einen share-Ordner, der noch einen Ordner mit dem Namen hallowelt enthält. Zum Schluss verschieben wir die hallowelt.png nach share/hallowelt:

Code:
~/C++/hallowelt$ mkdir bin
~/C++/hallowelt$ mkdir share
~/C++/hallowelt$ cd share
~/C++/hallowelt/share$ mkdir hallowelt
~/C++/hallowelt/share$ cd ..
~/C++/hallowelt$ mv hallowelt.png share/hallowelt/
~/C++/hallowelt$


Nun müssen wir unseren Quellcode anpassen. Eine neue Funktion muss her, die den Prefix des bin-Ordners zurückgibt, in dem die Binärdatei liegt, und anschließend "share/" anhängt. Diese Funktion existiert bereits in BinReloc, wir müssen sie nur noch kapseln:

C++:
std::string DataDir()
{
    char* temp = br_find_data_dir("/usr/share");
    std::string data_dir(temp);
    free(temp);
    return data_dir;
}


Nun rufen wir diese Funktion auf, wenn wir unsere Bilddatei laden und hängen /hallowelt/hallowelt.png an:

C++:
    Gtk::Image img(DataDir() + "/hallowelt/hallowelt.png");


Was genau passiert hier? Die br_find_data_dir-Funktion findet den Pfad der exe-Datei heraus:

Code:
/home/jhasse/C++/beispiel/bin/


Sie erkennt, dass wir uns in einem bin-Ordner befinden und springt ein Verzeichnis nach oben, wir erhalten nun den Prefix:

Code:
/home/jhasse/C++/beispiel/


Als letztes hängt die Funktion noch "share/" an:

Code:
/home/jhasse/C++/beispiel/share/


Anschließend wird von uns "/hallowelt/hallowelt.png" hinzugefügt:

Code:
/home/jhasse/C++/beispiel/share/hallowelt/hallowelt.png


Damit wird unsere Datei richtig aufgespürt; dies würde auch mit einem andern Prefix, wie z.B. "/usr" funktionieren. Wir können unser Programm jetzt kompilieren, müssen allerdings natürlich die Ausgabedatei in bin/ erstellen:


Code:
~/C++/hallowelt$ g++ `pkg-config gtkmm-2.4 --cflags --libs` main.cpp binreloc.o -o [b]bin/[/b]hallowelt


Unser Bild sollte nun auch angezeigt werden.

5 Menüeintrag

Nun wollen wir einen Menüeintrag (Startmenüeinträge unter Windows genannt) für unser Programm erstellen. Er soll in die Kategorie "Zubehör" eingeordnet werden und unser eigenes Logo enthalten. Dazu verwende ich folgendes:



Icons sind unter Linux Sonderfälle, werden also nicht unter share/<Programmname> abgelegt. Da jedes Programm meistens nur ein Icon hat, wird dies unter share/icons gespeichert. Außerdem können Sie mehrere Auflösungen des Icons dort speichern, je nach angeforderter Größe im Menü wird das richtige gewählt.
Bei den Icons muss es sich nicht um ico- oder xpm-Dateien handeln, wie oft geglaubt. Sogar svg-Grafiken sind möglich.

Menüeinträge sind unter Linux keine einfachen Verknüpfungen auf eine Binärdatei. Stattdessen handelt es sich um ein eigenes Dateiformat, welches verschiedene Parameter enthält, die verschiedene Eigenschaften des Eintrags beschreiben.
Es handelt sich um desktop-Dateien, welche wie ini-Dateien aufgebaut sind. Zu jedem Programm wird diese desktop-Datei unter share/applications gespeichert. Eine genaue Dokumentation findet sich in der Spezifikation von freedesktop.org.

Legen wir nun eine hallowelt.desktop-Datei an. Der Ort ist eigentlich unwichtig, da sie sowieso erst nach der Installation vom System gefunden wird. Die erste Zeile sieht wie folgt aus:

Code:
[Desktop Entry]

Da desktop-Dateien auch noch andere Sachen beinhalten können als einen Menüeintrag, legen wir hier fest, dass wir einen Menüeintrag meinen.

Code:
Version=1.0

Diese Zeile hat nichts mit der Version unseres Programms zu tun, sondern gibt die Version der Spezifikation der desktop-Datei an. Sie sollte immer auf 1.0 bleiben.

Code:
Name=Hallo Welt
Type=Application

Hier wird der Name unseres Programms festgelegt und dass es sich um eine Anwendung handelt.

Als nächstes wollen wir den Tooltipp definieren, doch hier wollen wir, dass nicht-deutsche Computer eine verständliche Nachricht erhalten. Dazu definieren wir einen deutschen Text, der auch nur für deutschsprachige Computer angezeigt wird, und einen englischen, der für den Rest angezeigt wird:

Code:
Comment=Displays a picture
Comment[de]=Zeichnet ein Bild


Um die eigene Sprache angezeigt zu bekommen, muss man nur echo $LANG auf der Konsole eingeben.

Fahren wir mit dem Befehl fort:

Code:
Exec=hallowelt

Hierbei handelt es sich um den Befehl, der ausgeführt werden soll, wenn unser Menüeintrag ausgewählt wird. Wir können hier hallowelt eingeben, da alle Dateien die in /usr/bin liegen, automatisch ohne Pfadangabe ausgeführt werden können. Sollte unser Programm unter einem anderen Prefix als /usr liegen, muss man hier natürlich anpassen, dazu aber später mehr.

Nun legen wir das Icon fest:

Code:
Icon=hallowelt.png


Für Icons gibt es viele Orte wo gesucht wird, unter anderem auch /usr/share/icons, deswegen wieder keine Pfadangabe.

Code:
Categories=Utility;

Mit dieser letzten Zeile legen wir noch die Kategorie fest, in die unser Programm einzuordnen ist. Ich habe "Zubehör" gewählt, obwohl unser Programm ja eigentlich sinnlos ist. Eine Übersicht über alle Kategorien gibt's hier: http://standards.freedesktop.org/menu-spec/latest/apa.html

6 Installationsskript

Da unsere hallowelt.desktop-Datei hier natürlich noch nutzlos ist, müssen wir uns ein Installationsskript erstellen, welches unsere Dateien installiert. Wir erstellen eine Datei namens install.sh mit folgendem Inhalt:

Code:
#!/bin/sh
prefix=$1
install -D bin/hallowelt $prefix/bin/hallowelt
install -D share/icons/hallowelt.png $prefix/share/icons/hallowelt.png
install -D share/hallowelt/hallowelt.png $prefix/share/hallowelt/hallowelt.png
install -D hallowelt.desktop $prefix/share/applications/hallowelt.desktop


Dieses Skript verwendet seinen ersten Parameter als Prefix, erstellt die Ordner und kopiert die Dateien an die richtige Position. Nun müssen wir es noch ausführbar machen:

Code:
~/C++/hallowelt$ chmod +x install.sh


Führen wir das Skript aus, um zu überprüfen ob alles geklappt hat. Hierbei verwenden wir den Prefix "/usr", da wir es normal installieren wollen. Damit das Skript auf die Systemverzeichnisse zugreifen kann, muss es als root ausgeführt werden, entweder mit sudo oder indem man sich vorher über su als root anmeldet:

Code:
~/C++/hallowelt$ sudo ./install.sh /usr
[sudo] password for jhasse:
~/C++/hallowelt$


Nun sollte unser Programm im Menü erscheinen:



7 Autopackage

Als nächstes wollen wir das eigentliche Installationspaket erstellen. Hierzu verwende ich Autopackage, da es an keine Distribution gebunden ist. Um selbst Pakete erstellen zu können, müssen wir uns die Development Environment von der Downloadseite herunterladen. Die .package-Datei installieren wir anschließend nach der Anleitung auf der Autopackage-Seite.

Nun steht uns der neue Konsolen-Befehl makepackage zur Verfügung, mit dem wir Autopackages erstellen können.
Mit dem Parameter --mkspec können wir eine Vorlage ausgeben lassen die wir in dem Ordner autopackage speichern:

Code:
mkdir autopackage
makepackage --mkspec > autopackage/default.apspec


Hierbei steht apspec für autopackagespecification. Öffnen wir nun die default.apspec-Datei und verändern sie für unser Programm:

Code:
1
2
3
4
5
6
7
8
9
# -*-shell-script-*-
 
[Meta]
RootName: @c-plusplus.de/hallowelt:$SOFTWAREVERSION
DisplayName: Hallo-Welt-Programm
ShortName: hallowelt
Maintainer: Jan Niklas Hasse <jhasse@gmail.com>
Packager: Jan Niklas Hasse <jhasse@gmail.com>
Summary: Hallo-Welt zeichnet ein Bild


In diesen ersten Zeilen geben wir allgemeine Informationen zu unserem Paket an. Fangen wir mit dem RootName an: Er beschreibt das Paket mit einem eindeutigen Namen, damit es nicht zu Konflikten kommt. Der erste Teil sollte dabei aus der Internetseite bestehen, es ist allerdings nicht erforderlich, dass es sich um eine gültige handelt.
DisplayName ist der eigentliche Name unseres Programms. ShortName dagegen ist eine Kurzform ohne Leerzeichen und meistens nur in Kleinbuchstaben. Maintainer ist der Autor des Programms und Packager der Hersteller des Paketes.
Als letztes geben wir noch eine Zusammenfassung unseres Programms an.

Code:
URL: http://www.c-plusplus.de/forum
License: GNU General Public License, Version 3
SoftwareVersion: 1.0
 
AutopackageTarget: 1.2


Die URL setzen wir auf unsere Internetseite, erforderlich ist sie nicht. Bei Lizenz geben wir den Namen der verwendeten Softwarelizenz an und bei SoftwareVersion die Version, beides kann bei dem Hallo-Welt-Programm so bleiben.
Der Kommentar nach SoftwareVersion ist für Programme, die autotools verwenden wichtig, deswegen können wir die Zeile entfernen.
Ein Repository geben wir auch nicht an, dies wäre zum Beispiel wichtig, wenn unser Programm von einer Library abhängt, die sich nicht in den normalen Paketquellen befindet.
Eine PackageVersion brauchen wir auch nicht angeben, da unser Paket zum ersten Mal erstellt wird. Sollten wir eine Änderung am Paket, aber nicht am Programm machen, können wir diese Nummer erhöhen, damit das Paket, trotz gleicher Programmversion, installiert werden kann. Als AutopackageTarget verwenden wir Version 1.2. Die InterfaceVersion brauchen wir nicht benutzen.

In BuildPrepare und BuildUnprepare geben wir die Befehle an, die für's Kompilieren und für's Aufräumen danach benutzt werden. Das sieht bei uns so aus:

Code:
1
2
3
4
5
6
7
8
[BuildPrepare]
gcc -DENABLE_BINRELOC -o binreloc.o -c binreloc.c
g++ `pkg-config gtkmm-2.4 --cflags --libs` main.cpp binreloc.o -o bin/hallowelt
./install.sh $build_root
 
[BuildUnprepare]
rm bin/hallowelt
rm binreloc.o


Nun müssen wir bestimmen, welche Dateien mit in das Paket aufgenommen werden. Dazu rufen wir unser install.sh-Skript nochmal auf und geben dabei als Prefix ein temporäres Verzeichnis von autopackage an. Danach importieren wir alle Dateien mit dem import-Befehl.

Code:
[Imports]
echo '*' | import


Bevor ein Paket installiert wird, müssen erst die benötigten Abhängigkeiten überprüft werden:

Code:
[Prepare]
require @gtkmm.org/gtkmm2 3.0
 
removeOwningPackage $PREFIX/bin/hallowelt


Der require-Befehl sorgt dafür, dass ohne die angegebenen Pakete eine Installation nicht möglich ist. Um zu überprüfen, ob eine Abhängigkeit auf dem Rechner vorhanden ist, verwendet Autopackage Shellskripte, die nach den Dateien suchen. Alle diese so genannten "skeleton files" befinden sich unter /usr/share/autopackage (oder unter einem anderen Prefix, je nachdem wohin autopackage installiert wurde). Wir binden in unserem Fall folgende Datei ein: /usr/share/autopackage/skeletons/@gtkmm.org/gtkmm2/skeleton.1.
Anschließend sagen wir noch, dass alle vorherigen Versionen unseres Paketes vor der Installation gelöscht werden sollen.

Nun kommen wir zum Code, der diese importierten Dateien installiert. Hierzu müssen wir die vorgefertigten Funktionen von autopackage verwenden. Eine Übersicht gibt's in der Autopackage-Dokumentation.
Die Funktionen haben meistens nur einen Parameter, der die importierte Datei angibt, so wie sie unter BuildPrepare installiert wurde.

Code:
[Install]
installExe bin/hallowelt
installData share/hallowelt/
installIcon share/icons/hallowelt.png
installMenuItem "Utility" share/applications/hallowelt.desktop


Ausnahmen bilden hier die installData- und die installMenuItem-Funktionen: installData erwartet einen Ordner und kopiert diesen mit allen seinen Unterverzeichnissen nach $PREFIX/share/. installMenuItem erwartet als ersten Parameter noch eine Kategorie für ältere Desktop-Umgebungen.

Zum Deinstallieren reicht ein vorgefertigter Befehl:

Code:
[Uninstall]
uninstallFromLog


Er deinstalliert alle Dateien, die vorher von den Autopackage-Funktionen installiert wurden.

Nun da unsere Datei fertig ist, rufen wir den makepackage-Befehl ohne Parameter auf:

Code:
~/C++/hallowelt$ makepackage


Jetzt können wir unser Programm per Doppelklick auf die Hallo-Welt-Programm 1.0.package-Datei installieren:



8 Binärdatei

8.1 Abhängigkeiten

Als Abhängigkeit haben wir gtkmm angegeben, doch woher weiß man eigentlich, was hier angegeben werden muss? Bei Abhängigkeiten handelt es sich um dynamische Bibliotheken, ohne die unsere Binärdatei nicht gestartet werden kann. Um uns diese Abhängigkeiten anzuschauen gibt es den Befehl ldd:

Code:
~/C++/hallowelt$ ldd bin/hallowelt


Die Ausgabe beinhaltet allerdings mehr Bibliotheken als notwendig. Einfaches Beispiel: GTK+ müssen wir nicht angeben, da es von gtkmm sowieso benötigt wird. Um diese Abhängigkeiten der Abhängigkeiten auszuschließen habe ich ein Python-Skript geschrieben, welches uns nur noch die wirklich notwendigen anzeigt. Wir laden uns das Skript herunter und führen es auf unser Programm aus:

Code:
~/C++/hallowelt$ python ldd.py bin/hallowelt
'/usr/lib/libgtkmm-2.4.so.1'
~/C++/hallowelt$


Unsere Abhängigkeit ist also tatsächlich nur gtkmm.
Eine weitere Möglichkeit, sich die Abhängigkeiten anzuzeigen, ist der Visual Dependency Walker.

8.2 Doppeltes Kompilieren

Unter Linux gibt es verschiedene Probleme mit Binärkompatibilität. Dazu gehört zum Beispiel die GNU C Library, die von jedem Programm benötigt wird, aber häufig geändert wird. Auf Systemen mit älteren Versionen kann dann die auf einem neuen System kompilierte Binärdatei nicht ausgeführt werden. Um dieses und andere Probleme zu beheben, gibt es das Tool apbuild. Es ist ein Wrapper für die GNU Compiler Collection, in unserem Fall dem g++-Befehl.

Ein weiteres Problem tritt nur bei C++ auf: Das ABI hat sich mehrmals geändert. Genauere Infos gibt's im Autopackage-Manual. Wichtig ist nur, dass Programme, die neuere C++-Libraries verwenden, nicht auf älteren Systemen laufen werden. Um das zu beheben, kompilieren wir unser Programm zweimal, einmal mit der alten g++-Version (< 3.4), die das ältere ABI verwendet und einmal mit der neuen (>= 3.4). Autopackage entscheidet sich dann während der Installation für die richtige Version.

Als erstes müssen wir eine ältere g++-Version, entweder 3.2 oder 3.3 installieren. Nun definieren wir in unserer default.apspec-Datei den Befehl für diesen Compiler:

Code:
[BuildPrepare]
export CXX2=g++-3.3 # Der Befehl für die alte g++-Version
# Nun verwenden wir apbuild zum Kompilieren:
apgcc -DENABLE_BINRELOC -o binreloc.o -c binreloc.c
apg++ `pkg-config gtkmm-2.4 --cflags --libs` main.cpp binreloc.o -o bin/hallowelt
./install.sh $build_root


Wenn wir nun makepackage aufrufen, wird zweimal kompiliert, beim zweiten Mal wird der Befehl verwendet, den wir in der CXX2-Variable definiert haben.

9 Nachwort

Autopackage ist momentan das beste System um distributionsunspezifische Pakete zu erstellen. Doch in letzter Zeit wird es nicht mehr stark weiterentwickelt. Viele Distributionen verweigern die Aufnahme, da sie kein System wollen, das am Paketmanager "vorbei" installiert. Doch das könnte sich in Zukunft ändern, dank PackageKit. Damit könnte Autopackage automatisch Abhängigkeiten mit dem Paketmanager nachladen oder sogar komplett integriert werden. Bis es soweit ist, bleibt es trotzdem noch die einfachste Möglichkeit für einen Entwickler, denn mehrere Pakete für die verschiedenen Distributionen zu erstellen, ist keine Alternative.
Für weitere Fragen zu Autopackage kann ich die Mailingliste empfehlen, näheres auf der Autopackage-Community-Seite, und natürlich die Linux/Unix-Abteilung dieses Forums.

10 Links

BinReloc-Tutorial:
http://autopackage.org/docs/binreloc/

Autopackage-Support:
http://autopackage.org/community.html

Sie können Kommentare zu diesem Artikel im Forum schreiben. (Eine Registrierung ist nicht notwendig.)

Logo-Design: MastaMind Webdesign