Umgebungsvariablen setzen mit Launch Services und Drag&Drop

  • Umgebungsvariablen setzen mit Launch Services und Drag&Drop

    Weiss mir im Moment keinen Rat mehr :(

    Ich habe meine Applikation nach OS X portiert (arbeite aber nicht mit XCode). Das Programm benutzt eine unter Unix entwickelte Bibliothek (ROOT, vielleicht kennt das jemand).

    Um ROOT nutzen zu können muss eine Umgebungsvariable (ROOTSYS) gesetzt sein, die den Pfad zur Bibliothek enthält. Im Moment liegt die Bibliothek im Bundle und ich setze ROOTSYS über einen LSEnvironment-Eintrag in der Info.plist. Das geht eigentlich nicht, weil der Pfad statisch ist und beim Verschieben des Bundles nicht mehr auf die richtige Stelle zeigt. Um das in den Griff zu kriegen, habe ich mein eigentliches Bundle in ein anderes Bundle eingepflanzt. Im äusseren Bundle wird ein Skript ausgeführt, das den ROOTSYS-Eintrag in der Info.plist anpasst und dann die eigentliche Applikation mit 'open' startet. Das funktioniert. So weit so gut also, oder auch nicht.

    Der Ansatz mit dem Skript war ja schlau ausgedacht, aber jetzt sehe ich doch zwei Probleme:
    1. Das mit dem Anpassen der Info.plist kann gründlich schiefgehen wenn keine Schreibrechte da sind, bspw. wenn die Applikation von CD gestaret werden soll.
    2. Ich möchte jetzt Drag&Drop auf das Applikations-Ikon unterstützen. Das funktioniert aber nicht, weil da im Bundle ein Skript sitzt. OS X erwartet aber eine Aplikation, die den Apple-Event "OpenDoc" verarbeiten kann. Hmmmh.

    Gut. Ich dachte mir, Punkt 2. kann ich lösen indem ich das Skript durch ein Programm ersetze, das die Aufgaben des Skripts erledigt und zusätzlich OpenDoc-Events entgegennimmt. Dann kann das Programm das Hauptprogramm mit LSLaunchFSRefSpec starten. Drag&Drop funktioniert jetzt, die Sache hat aber auch wieder einen Haken: sobald ich per Programm den LSEnvironment-Eintrag in der Info.plist-Datei verändere, startet das Hauptprogramm nicht mehr. Das ist doch merkwürdig, weil die gleiche Modifikation über ein Shell-Skript funktioniert!?

    Eine mögliche Alternative wäre vielleicht, die Bibliothek fest unter /Library/Application Support zu installieren. Das man dahin ohne weiteres schreiben kann, habe ich eben erst gelesen. Ich könnte dann beim ersten Start des Programms die Bibliothek dahin verschieben. Damit wären wohl beide Probleme gelöst. So richtig gefällt mir das aber auch nicht, weil dann beim "Deinstallieren" des Programms die Bibliothek übrigbleibt.

    Kann mir jemand weiterhelfen?
  • weil dann beim "Deinstallieren" des Programms die Bibliothek übrigbleibt.

    Das macht so gut wie jedes Mac-Programm, Reste in ~/Library/Preferences oder in Application Support übrig zu lassen. Da würde ich mir keinen Kopf machen.
    Ganz im Gegenteil: das sind genau die Orte, wo Daten und Einstellungen gespeichert werden sollten.

    Das mit den "Umgebungsvariablen" kenne ich eigentlich nur von Windows, wenn dort z. B. was in "Path" eingetragen wird, damit ein Programm überhaupt läuft oder seine ini-Dateien findet. Grauslich....

    No.
  • RE: Umgebungsvariablen setzen mit Launch Services und Drag&Drop

    Spricht etwas dagegen, das Environment in der Applikation selbst zu setzen?

    man 3 setenv

    Installationsverzeichnis herauskriegen mit NSBundle +mainBundle und -bundlePath.
    Die Sache mit den verschachtelten Bundles und dem Start-Skript nur für das Environment-Setting erscheint mir etwas sehr aufwendig.

    gruß kisch
  • Original von norbi
    weil dann beim "Deinstallieren" des Programms die Bibliothek übrigbleibt.

    Das macht so gut wie jedes Mac-Programm, Reste in ~/Library/Preferences oder in Application Support übrig zu lassen. Da würde ich mir keinen Kopf machen.
    Ganz im Gegenteil: das sind genau die Orte, wo Daten und Einstellungen gespeichert werden sollten.

    Habe gestern noch geguckt ob sich mit Apple Events was machen lässt. Die haben aber mit dem Starten von Programmen nix zu tun.

    Also werde ich das jetzt so machen wie Du empfiehlst. Ist im Moment auch die einzig überhaupt funktionierende Möglichkeit, wenn ich das richtig sehe.

    Danke schön.
  • RE: Umgebungsvariablen setzen mit Launch Services und Drag&Drop

    Original von kisch
    Spricht etwas dagegen, das Environment in der Applikation selbst zu setzen?

    man 3 setenv

    Ja, das hatte ich anfangs auch mal so implementiert, mit 10.3.x, da ging das noch. Mit 10.4.x ging's plötzlich nicht mehr.

    Ich hole ein bisschen weiter aus: die Bibliothek die ich nutzen will, hat ein statisches Singleton, ein Wurzelobjekt, das alle möglichen Infos bereitstellt. Das wird mit dem Laden der Bibliotheken initialisiert und ist dann da bis die Applikation beendet wird. Dieses Wurzelobjekt braucht nun die Umgebungsvariable, d.h. die Umgebung muss initialisiert sein bevor die Bibliotheken geladen werden. Es hängt also davon wann der dynamische Lader von OS X aufgerufen wird, ob ein setenv im Programm geht oder nicht.

    Mit 10.3.x hatte ich eine statische Routine, die sich die Adresse des Bundles holt und daraus den von ROOTSYS ableitet. Das ging, weil statische Routinen ausgeführt wurden bevor die Bibliotheken geladen wurden. Hat sich aber wie gesagt geändert. Danach war mir dann die Lust vergangen mich auf so einen fragilen Ansatz zu verlassen.

    Vielleicht ist es ja irgendwie möglich das Verhalten des dynamischen Laders zu steuern, etwa so dass die Bibliotheken erst dann geladen werden, wenn eine Bibliotheksfunktion zum ersten Mal aufgerufen wird. Wenn das ginge wäre der Ansatz mit setenv wieder im Rennen...

    Vielleicht ist hier jemand der sich gut mit diesen systemnahen Dingen auskennt?
  • RE: Umgebungsvariablen setzen mit Launch Services und Drag&Drop

    Okay, die Art Problem also...

    Da sehe ich vier Ansätze.
    1/
    so wie Du's mit der .plist versucht hast, mit absoluter Pfadangabe, sollte man das mit einem Installer machen (der dann auch "ordentliche" Schreibrechte ins Programmverzeichnis hat). Nur läuft's dann immer noch nicht von direkt von read-only Pfaden, wie Du schon ausgeführt hast. Frage wäre, ob das eine echte Anforderung ist.

    2/
    Versuche, den ROOTSYS-Pfad in der .plist RELATIV anzugeben, in der Hoffnung, daß bei der Inialisierung der Library nur erstmal die Information kopiert wird, ohne weitere Aktionen.
    Dann beim Startup der Applikation sofort ins eigene Installationsverzeichnis wechseln, so daß der relative ROOTSYS-Pfad dann richtig zeigt.

    3/
    Problem bei der Wurzel packen.
    Wenn die ROOT-Bibliothek einen Initialisierer vor den main()-Aufruf packen kann, dann kannst Du das auch. Schlimmstenfalls könntest Du eine eigene Mini-Library bauen, die eine "init"-Funktion enthält, in der setenv() ausgeführt wird. Diese Library würdest Du in Deinem Hauptprojekt in der Link-Liste vor den ROOT-Bibliotheken angeben. Dann sollte Dein init vor dem von ROOT drankommen.
    Der runtime-Linker von OS X hat ein paar hilfreiche Optionen, mit denen Du herausbekommen kannst, was da alles vor main() aufgerufen wird. man dyld. Grundsätzlich sind das Funktionen, die beim Linken als "init" gekennzeichnet sind, und Konstruktoren von globalen C++-Objekten.
    Höchstwahrscheinlich gibt's auch ähnlich wie beim GNU-Linker Tricks, wie man eigene Funktionen an den Anfang der Liste der Init-Funktionen schmuggeln kann, indem man eine bestimmte Linker-Section dafür angibt. Aber da müßte ich bei OSX auch erst forschen.

    4/
    Wenn Du doch in den sauren Apfel beißen mußt und eine Starter-Applikation vorschaltest, die das Open-Event annimmt, dann reicht's aber dicke, wenn diese mit setenv() den Pfad einträgt. Das vererbt sich ja an den nachgestarteten Prozeß. Also keine .plist umschreiben.

    Ach ja, und die Library im Application Support zu hinterlassen, wäre vielleicht wirklich nicht so das Gelbe... ROOT ist root.cern.ch, ja? 35MB Downloadgröße --- vielleicht doch etwas dick :)

    gruß kisch
  • RE: Umgebungsvariablen setzen mit Launch Services und Drag&Drop


    2/
    Versuche, den ROOTSYS-Pfad in der .plist RELATIV anzugeben, in der Hoffnung, daß bei der Inialisierung der Library nur erstmal die Information kopiert wird, ohne weitere Aktionen.
    Dann beim Startup der Applikation sofort ins eigene Installationsverzeichnis wechseln, so daß der relative ROOTSYS-Pfad dann richtig zeigt.

    Relative Pfadangabe hatte ich probiert, funktioniert aber nicht.


    3/
    Problem bei der Wurzel packen.
    Wenn die ROOT-Bibliothek einen Initialisierer vor den main()-Aufruf packen kann, dann kannst Du das auch. Schlimmstenfalls könntest Du eine eigene Mini-Library bauen, die eine "init"-Funktion enthält, in der setenv() ausgeführt wird. Diese Library würdest Du in Deinem Hauptprojekt in der Link-Liste vor den ROOT-Bibliotheken angeben. Dann sollte Dein init vor dem von ROOT drankommen.

    Ja, ja, die Idee ist gar nicht dumm. Das müsste gehen...

    Der runtime-Linker von OS X hat ein paar hilfreiche Optionen, mit denen Du herausbekommen kannst, was da alles vor main() aufgerufen wird. man dyld. Grundsätzlich sind das Funktionen, die beim Linken als "init" gekennzeichnet sind, und Konstruktoren von globalen C++-Objekten.
    Höchstwahrscheinlich gibt's auch ähnlich wie beim GNU-Linker Tricks, wie man eigene Funktionen an den Anfang der Liste der Init-Funktionen schmuggeln kann, indem man eine bestimmte Linker-Section dafür angibt. Aber da müßte ich bei OSX auch erst forschen.

    Hmmmh, ich hatte gehofft hierzu ein paar schnell verwertbare Tipps von einem OS X-Guru zu kriegen...
    Ansonsten ist wohl wieder tagelanges Studium von Dokumentation und Foren-Beitägen angesagt. Na ja, das hat ja auch seinen Reiz :)


    4/
    Wenn Du doch in den sauren Apfel beißen mußt und eine Starter-Applikation vorschaltest, die das Open-Event annimmt, dann reicht's aber dicke, wenn diese mit setenv() den Pfad einträgt. Das vererbt sich ja an den nachgestarteten Prozeß. Also keine .plist umschreiben.

    Du meinst, indem ich das Hauptprogramm mit dem Starter zusammen in den MacOS-Ordner packe und das Hauptprogramm dann mit exec starte? Das hatte ich natürlich auch probiert. Das geht zwar prinzipiell, aber es gibt dann Probleme mit OS X-Spielereien wie dem Dock. Das scheint nur dann sauber zu funktionieren, wenn man eine Applikation in einem Bundle wie vom System vorgesehen startet, also über den Finder oder mit 'open' oder über die Launch Services. Jedenfalls habe ich das mit exec nicht sauber hingekriegt.
  • RE: Umgebungsvariablen setzen mit Launch Services und Drag&Drop

    Original von majusebetta
    Relative Pfadangabe hatte ich probiert, funktioniert aber nicht.

    Buuuh!

    Wegen der eigenen Lib hab' ich ein bißchen rumprobiert.
    Nennen wir dieses murx.c:

    C-Quellcode

    1. #include <stdio.h>
    2. #include <stdlib.h>
    3. void init () {
    4. setenv ("murx", "bla", 0);
    5. puts ("dylib init()");
    6. }
    7. void dummy () {
    8. }

    main.cc:

    C-Quellcode

    1. #include <stdio.h>
    2. #include <stdlib.h>
    3. extern "C" {
    4. void dummy (void);
    5. }
    6. struct Setenv {
    7. Setenv () {
    8. setenv ("murx", "zweiter?", 0);
    9. puts ("main init()");
    10. }
    11. } the_setenv;
    12. int main () {
    13. dummy (); // murx.dylib anziehen
    14. puts (getenv ("murx"));
    15. return 0;
    16. }
    Alles anzeigen

    Quellcode

    1. gcc -c murx.c -o murx.o
    2. libtool -dynamic -o murx.dylib murx.o -init _init -lc
    3. g++ -c main.cc -o main.o
    4. g++ -v -o main main.o murx.dylib
    5. DYLD_PRINT_INITIALIZERS= DYLD_PRINT_LIBRARIES= ./main


    Aus dem Output:

    Quellcode

    1. dyld: loaded: /private/tmp/./main
    2. dyld: loaded: murx.dylib
    3. dyld: loaded: /usr/lib/libstdc++.6.dylib, cpu-sub-type: 0
    4. dyld: loaded: /usr/lib/libgcc_s.1.dylib
    5. dyld: loaded: /usr/lib/libSystem.B.dylib, cpu-sub-type: 0
    6. ...
    7. dyld: calling initializer function 0x9010ba64 in /usr/lib/libSystem.B.dylib
    8. ...
    9. dyld: calling -init function 0x0x6ee8 in murx.dylib
    10. dylib init()
    11. dyld: calling initializer function 0x94c397e4 in /usr/lib/libstdc++.6.dylib
    12. ...
    13. dyld: calling initializer function 0x3f30 in /private/tmp/./main
    14. main init()
    15. bla
    16. ...
    Alles anzeigen


    Die eigene init() hat sich also schon mal vor die Inits der libstdc++ geschoben.
    Andererseits gibt's noch inits aus libSystem, die wiederum noch davor kommen.
    Die Lade-Reihenfolge der Libraries entspricht aber genau der Reihenfolge auf der Linker-Kommandozeile (der Output aus g++ -v -o main ...).
    Also versucht dyld die Inits in der Link-Folge aufzurufen, berücksichtigt aber anscheinend zusätzlich funktionale Abhängigkeiten (setenv()/puts() in murx.dylib:init() =?> libSystem). Schlau von dyld; ich könnte nicht garantieren, daß Linux ld.so das ohne Hilfe hinkriegt.

    Die eigene Konstruktor-Init-Funktion in main.cc kommt dagegen erst ganz zum Schluß; das entspricht dem, was Du beobachtet hast.

    Ich glaube, das ist einen Versuch wert.
  • RE: Umgebungsvariablen setzen mit Launch Services und Drag&Drop

    Quellcode

    1. gcc -c murx.c -o murx.o
    2. libtool -dynamic -o murx.dylib murx.o -init _init -lc
    3. g++ -c main.cc -o main.o
    4. g++ -v -o main main.o murx.dylib
    5. DYLD_PRINT_INITIALIZERS= DYLD_PRINT_LIBRARIES= ./main

    PRIMA DAS GEHT!
    ABER: Ich habe auch ein bisschen rumgespielt um zu verstehen was da passiert. Und siehe da: wenn ich murx.c mit dem g++ kompiliere, geht's nicht mehr. Kriege dann beim nachfolgenden

    Quellcode

    1. libtool -dynamic -o murx.dylib murx.o -init _init -lc -framework Carbon

    eine Fehlermeldung:

    Quellcode

    1. ld: Undefined symbols:
    2. ___gxx_personality_v0
    3. libtool: internal link edit command failed

    Das Carbon-Rahmenwerk brauche ich um das Bundle zu lokalisieren. Hinzufügen von -lstdc++.6 bringt zwar das undefinierte Symbol zum Verschwinden, der "internal link edit"-Fehler bleibt aber. Heisst das ich muss eine C-Bibliothek erzeugen? C++ geht nicht?
  • RE: Umgebungsvariablen setzen mit Launch Services und Drag&Drop

    PRIMA DAS GEHT!

    :] :] :]

    Und siehe da: wenn ich murx.c mit dem g++ kompiliere, geht's nicht mehr. Kriege dann beim nachfolgenden

    Quellcode

    1. libtool -dynamic -o murx.dylib murx.o -init _init -lc -framework Carbon

    eine Fehlermeldung:

    Quellcode

    1. ld: Undefined symbols:
    2. ___gxx_personality_v0
    3. libtool: internal link edit command failed

    Das Carbon-Rahmenwerk brauche ich um das Bundle zu lokalisieren. Hinzufügen von -lstdc++.6 bringt zwar das undefinierte Symbol zum Verschwinden, der "internal link edit"-Fehler bleibt aber. Heisst das ich muss eine C-Bibliothek erzeugen? C++ geht nicht?


    C und eine Library-Init-Funktion geht am einfachsten.
    Eine in C verfaßte Lib läßt sich problemlos mit C++ zusammenlinken, wie gezeigt.

    Wenn Du die C-Source mit C++ übersetzt, dann bekommen die Funktionen andere Linker-Namen. Das löst wahrscheinlich den verbleibenden Fehler aus.
    Bei C ist es simpel; die Funktion "init()" hat als Linker-Symbol "_init" (daher "-init _init" beim libtool-Aufruf). Bei C++ codiert der Compiler allerlei Typinformationen in das Linker-Symbol hinein ("name mangling", man c++filt).
    Wie die Funktion nach dem Compilern heißt, kannst Du mit "nm murx.o" herausfinden; diesen Namen dann für "libtool -init ..." nehmen.
    Bei C++ könntest Du stattdessen auch das globale Objekt mit Konstruktor verwenden, wie das "struct Setenv" Ding in main.cc.

    Aber die Idee war eigentlich, in der Extra-Lib nur das "setenv()" zu erledigen; also warum nicht bei C bleiben?
  • RE: Umgebungsvariablen setzen mit Launch Services und Drag&Drop

    C und eine Library-Init-Funktion geht am einfachsten.
    Eine in C verfaßte Lib läßt sich problemlos mit C++ zusammenlinken, wie gezeigt.

    Verstehe.

    Wenn Du die C-Source mit C++ übersetzt, dann bekommen die Funktionen andere Linker-Namen. Das löst wahrscheinlich den verbleibenden Fehler aus.

    Aha.

    Bei C ist es simpel; die Funktion "init()" hat als Linker-Symbol "_init" (daher "-init _init" beim libtool-Aufruf). Bei C++ codiert der Compiler allerlei Typinformationen in das Linker-Symbol hinein ("name mangling", man c++filt).
    Wie die Funktion nach dem Compilern heißt, kannst Du mit "nm murx.o" herausfinden; diesen Namen dann für "libtool -init ..." nehmen.

    Ja, geht jetzt! Der Symbolname ist jetzt __Z4initv. Nicht unbedingt eine Verbesserung gegenüber C...

    Aber die Idee war eigentlich, in der Extra-Lib nur das "setenv()" zu erledigen; also warum nicht bei C bleiben?

    Ja, habe ich jetzt auch so gemacht. Und läuft prima! Ich hatte nur ursprünglich für den Aufbau des Bibiothekspfades, ausgehend vom Bundle-Pfad, std::string benutzt. Jetzt nehme ich eben strcpy und strcat. Kein Problem.

    Abschliessend nochmals Danke für die Hilfe! Mein Stresshormonspiegel ist in den letzten Tagen doch deutlich gesunken :) Ich plädiere unbedingt dafür, Dich zum 3-Sterne-Entwickler zu befördern. Mindestens.
  • RE: Umgebungsvariablen setzen mit Launch Services und Drag&Drop

    Na ja, Name Mangling ermöglicht eben das Überladen von Bezeichnern. Das kann man ja halten wie ein Dachdecker, es ist aber eine wichtige Eigenschaft von C++.
    Es hat noch nie etwas gefunzt. To tear down the Wall would be a Werror!
    25.06.2016: [Swift] gehört zu meinen *Favorite Tags* auf SO. In welcher Bedeutung von "favorite"?