C Als Hauptklasse Oder; Eine Cocoa App ohne ObjC

8

Für diejenigen, die meine Vernunft / Zeitverschwendung / Fähigkeit / Motive in Frage stellen, ist dieses Ding ein Port des " Foundation mit einem Löffel " Projekt, jetzt berüchtigt für eine All-C-iOS-App.

Also, ich habe eine c-Datei auf den Markt gebracht, die die Haupt-Klasse hinter einer all-C-Mac-App ist, jedoch verhindert eine Kombination von einschränkenden Faktoren, dass die Anwendung gestartet wird. Wie es derzeit ist, ist das Projekt nur eine main.m und eine Klasse namens AppDelegate.c, also habe ich "AppDelegate" als Name der Hauptklasse in der info.plist eingegeben, und zu meiner völligen Überraschung, das Protokoll gedruckt:

  

Klasse kann nicht gefunden werden: AppDelegate, Beenden

Dies würde in iOS perfekt funktionieren, da die Hauptfunktion den Namen einer Delegatklasse akzeptiert und automatisch verarbeitet, aber NSApplicationMain () kein solches Argument akzeptiert.

Nun, ich weiß, dass dies auf die Tatsache zurückzuführen ist, dass es keine @interface/@implementation Direktiven in C gibt, und genau das scheint das Betriebssystem zu suchen, also schrieb ich eine einfache NSApplication Unterklasse und stellte sie als Principal zur Verfügung Class to the Plist, und es startete perfekt gut. Meine Frage ist, wie könnte man eine c-Datei als Hauptklasse in einer Mac-Anwendung einstellen und sie korrekt starten lassen?

BEARBEITEN: Gelöst! Die Datei kann eine .m (Framework-Fehler, aus irgendeinem Grund) sein, aber die Zuordnung von Klassenpaaren ist genug, um vorbei zu gehen. Sie können die Quelle für die C-basierte Mac App hier herunterladen . Glückliches Graben!

Der Code wurde geschrieben, signiert, versiegelt und gestempelt, aber alles, was ich brauche, ist ein Weg um die NSPrincipalClass-Anforderung im plist.

    
CodaFi 03.07.2012, 21:05
quelle

3 Antworten

8

Okay, hier ist eine substantiell umgeschriebene Antwort, die zu funktionieren scheint ich.

Ihre Probleme sind also ziemlich unabhängig von der Hauptklasse. Sie sollte die Hauptklasse als NSApplication belassen.

Das Hauptproblem, auf das ich bereits hingewiesen habe, ist, dass Sie keine registrieren geeignete Anwendungsdelegate mit NSApplication. Wechseln Hauptklasse wird das nicht beheben. Eine NIB-Datei kann die Anwendung festlegen delegieren, aber das ist wirklich übertrieben für dieses Problem.

Allerdings gibt es tatsächlich mehrere Probleme:

  1. Ihr (geposteter) Code wird mit einigen iOS Klassen und Methoden geschrieben passen nicht ganz zu den OS X-Versionen.

  2. Sie müssen sicherstellen, dass Ihre AppDelegate-Klasse registriert ist das System, und dann müssen Sie NSApplication manuell initialisieren und Legen Sie den Anwendungsdelegierten fest.

  3. Die Verknüpfung ist hier sehr sehr wichtig. Du brauchst habe ein externes Symbol, das den Linker dazu bringt, das Foundation-Kit zu ziehen und das AppKit in den Speicher. Ansonsten gibt es keine einfache Möglichkeit, sich zu registrieren Klassen wie NSObject mit der Objective-C-Laufzeit.

iOS / OS X-Klassen

OSX-Anwendungsdelegaten stammen von NSObject , nicht von UIResponder , also die Zeile:

%Vor%

sollte lauten:

%Vor%

Außerdem antworten die Delegierten der OSX-Anwendung auf andere Nachrichten als die Delegierten der iOS-Anwendung. Insbesondere müssen sie auf den applicationDidFinishLaunching: Selektor antworten (der ein NSNotifier Objekt vom Typ id benötigt).

Die Zeile

%Vor%

sollte lauten:

%Vor%

Beachten Sie, dass die Parameter ( "i@:@@" und "i@:@" ) unterschiedlich sind.

Einrichten von NSApplication

Es gibt zwei Möglichkeiten, um AppDelegate mit der Objective-C-Laufzeit zu registrieren:

  1. Deklarieren Sie Ihre initAppDel -Methode mit __attribute__((constructor)) , Dadurch wird er vom Setup-Code vor main oder

  2. aufgerufen
  3. Rufen Sie sich selbst an, bevor Sie das Objekt instanziieren.

Ich vertraue generell __attribute__ labels nicht. Sie werden wahrscheinlich gleich bleiben, aber Apple könnte sie ändern. Ich habe gewählt, dass initAppDel von main aufgerufen wird.

Sobald Sie Ihre AppDelegate -Klasse mit dem System registriert haben, ist es im Grunde genommen funktioniert wie eine Objective-C-Klasse. Sie instanziieren es wie eine Objective-C-Klasse, und Sie können es wie ein id weitergeben. Es ist tatsächlich ein id .

Um sicherzustellen, dass AppDelegate als Anwendungsdelegat ausgeführt wird, müssen Sie NSAppliction einrichten. Da Sie Ihren eigenen Anwendungsdelegaten ausführen, können Sie wirklich NSApplicationMain nicht dazu verwenden. Es stellte sich heraus, dass es nicht so schwer war:

%Vor%

Linking und die Objective-C Laufzeit

Also, hier ist das wahre Fleisch des Problems, zumindest für mich. Alle der oben wird ganz und gar versagen, es sei denn, du hast es tatsächlich getan NSObject und NSApplication wurden in der Objective-C-Laufzeit registriert.

Normalerweise, wenn Sie in Objective-C arbeiten, teilt der Compiler dem Linker mit dass es das braucht. Es tut dies, indem es ein Bündel von speziellen setzt ungelöste Symbole in der Datei .o . Wenn ich die Datei SomeObj.m kompiliere:

%Vor%

Verwenden Sie clang -c SomeObj.m , und sehen Sie sich dann die Symbole mit nm SomeObj.o an:

%Vor%

Sie sehen all diese netten _OBJC_CLASS_$_ Symbole mit einem U an Die linke zeigt an, dass die Symbole nicht aufgelöst sind. Wenn Sie verlinken Diese Datei nimmt der Linker und stellt fest, dass er geladen werden muss der Rahmen der Stiftung, um die Referenzen zu lösen. Dies zwingt die Foundation Framework, um alle seine Klassen mit dem Objective-C zu registrieren Laufzeit. Etwas ähnliches ist erforderlich, wenn Ihr Code das AppKit benötigt Rahmen.

Wenn ich Ihren AppDelegate-Code kompiliere, den ich umbenannt habe 'AppDelegate_orig.c' mit clang -c AppDelegate_orig.c und dann ausführen nm darauf:

%Vor%

Sie werden sehen, dass es keine ungelösten Symbole gibt, die das erzwingen würden Foundation- oder AppKit-Frameworks zum Verknüpfen. Dies bedeutet, dass alle meine Anrufe to objc_getClass würde NULL zurückgeben, was bedeutet, dass die ganze Sache kommt zusammenbrechen.

Ich weiß nicht, wie der Rest Ihres Codes aussieht, also kann das nicht sein ein Problem für Sie, aber das Lösen dieses Problems ließ mich eine modifizierte kompilieren AppDelegate.c-Datei von selbst in ein (nicht sehr funktionelles) OSX Anwendung.

Das Geheimnis hier ist, ein externes Symbol zu finden, das den Linker benötigt Foundation und AppKit einbringen. Das stellte sich als relativ heraus einfach. Das AppKit stellt eine globale Variable NSApp zur Verfügung, die den Anwendungsinstanz von NSApplication . Das AppKit-Framework beruht darauf auf dem Foundation Framework, also bekommen wir das kostenlos. Einfach erklären ein externer Hinweis darauf war genug:

%Vor%

(NB: Sie müssen die Variable irgendwo benutzen, oder den Compiler kann es optimieren, und Sie werden die Frameworks verlieren, die Sie benötigen.)

Code und Screenshot:

Hier ist meine Version von AppDelegate.c. Es enthält main und sollte gesetzt werden alles auf.Das Ergebnis ist nicht so aufregend, aber es öffnet sich ein kleines Fenster auf dem Bildschirm.

%Vor%

Kompilieren, installieren und ausführen wie folgt:

%Vor%

Info.plist ist:

%Vor%

Mit einem Screenshot:

    
sfstewman 03.07.2012, 22:00
quelle
8

Auf diesem Weg liegt der Wahnsinn oder zumindest eine große Zeitverschwendung. Cocoa-Apps sind inhärent auf Objective-C-Basis entworfen und verwenden einen .app-Wrapper mit einer Unterverzeichnishierarchie von sehr spezifischem Design (einschließlich der Anforderung von Dingen wie Info.plist und möglicherweise Codesignatur).

Die Delegiertenfrage, in die Sie hineingeraten sind, ist nur ein Ablenkungsmanöver; Das eigentliche Problem ist, dass Sie nicht wirklich eine richtige Cocoa-App erstellen.

Und nein, es würde unter iOS nicht perfekt funktionieren, weil diese Plattform, noch mehr, erfordert, dass die Anwendung auf eine sehr spezifische Art und Weise aufgebaut wird.

Wenn Sie "C" umbrechen möchten, dann:

  • Beginnen Sie mit einer grundlegenden Cocoa-Anwendung
  • benenne deine Hauptfunktion als mainC () oder etwas (kann über die Compiler / Linker-Befehlszeile ausgeführt werden, wenn du das wirklich willst)
  • [idealerweise] verschiebe all dein C-Goop des Hauptthreads, damit du die Hauptereignisschleife nicht blockierst

Wenn Sie "reines C" als Hauptklasse haben wollen, dann:

  • Erstellen Sie eine .m-Datei, die die Klasse
  • implementiert
  • implementieren Sie die Methoden der Klasse zum Aufruf Ihrer C-Funktionen

Fertig - so einfach ist das. Während Sie Ihre eigenen Rollen, wie Sie tun, ist sicherlich lehrreich (nein, wirklich - es ist und ich ermutige jeden, das zu erkunden), es erfindet einfach ein Rad neu.

tl; dr Wenn Sie gegen die System-APIs kämpfen, tun Sie es falsch.

  

Natürlich, aber ich portiere Rich's Foundation mit einem Löffel zu OS X.   Es ist ein kompletter Zeitfresser, aber das entmutigt mich nicht in der   mindestens

Super!

In diesem Fall sollten Sie sich die Implementierung von pythonw und / oder (IIRC) ansehen, die Rubycocoa-Shell, die die Implementierung von GUI aus einem relativ nicht-Wrapper-basierten Shell-Skript ermöglicht .

Das Problem geht weit über die Hauptklasse hinaus. Beachten Sie, dass [NSBundle mainBundle] wirklich sinnvoll sein muss und einige Ressourcen irgendwie eingekapselt werden müssen.

Außerdem hatte das PyObjC -Projekt verschiedene Versuche, "Shell-Skript" -basierte Cocoa-Anwendungen auszuführen. Sie könnten über die verschiedenen Beispiele stochern, da ich denke, dass es immer noch mindestens ein paar gibt, die tun, was Sie wollen.

Eine Google-Suche nach "Cocoa-Anwendung von Shell-Skript" oder ähnlichem könnte ebenfalls nützlich sein, da dies in den letzten 23 Jahren mehrmals aufgetreten ist.

    
bbum 03.07.2012 21:24
quelle
1

Wenn Sie bereit sind, die Objective-C-Laufzeitfunktionen aufzurufen, aber @implementation (aus irgendeinem verrückten Grund) nicht verwenden möchten, erstellen Sie einfach eine Klasse mit objc_allocateClassPair und Freunden und verwenden Sie diese als Hauptklasse.

    
Jesse Rusak 03.07.2012 21:31
quelle