9

私の正気/時間の浪費/能力/動機に疑問を抱く人にとって、これは「Foundation With A Spoon」プロジェクトの移植版であり、現在はオールCiOSアプリとして有名です。

そのため、私はacファイルを使用して、all-C mac-appの背後にあるメインクラスになることができますが、制限要因の組み合わせにより、アプリケーションを起動できません。現在のところ、プロジェクトはmain.mとAppDelegate.cというクラスにすぎないため、info.plistにプリンシパルクラスの名前として「AppDelegate」と入力しました。驚いたことに、ログには次のように出力されました。

クラスが見つかりません:AppDelegate、終了します

main関数はデリゲートクラスの名前を受け入れ、それを自動的に処理するため、これはiOSで完全に機能しますが、NSApplicationMain()はそのような引数を取りません。

これは、Cにディレクティブがないことに起因していることがわかりました。これは@interface/@implementation、OSが実際に探しているものであるため、単純なNSApplicationサブクラスを作成し、それをプリンシパルクラスとしてplistに提供すると、完全に起動しました。良い。私の質問は、Macアプリケーションのプリンシパルクラスとしてacファイルを設定し、正しく起動させるにはどうすればよいでしょうか。

編集:解決しました!ファイルは.m(何らかの理由でフレームワークエラー)である可能性がありますが、クラスペアの割り当てはすり抜けるのに十分です。CベースのMacアプリのソースはこちらからダウンロードできます。ハッピーディギング!

コードは書かれ、署名され、封印され、スタンプされていますが、必要なのはplistのNSPrincipalClass要件を回避する方法だけです。

4

3 に答える 3

10

さて、これは私にとってうまくいくように見える実質的に書き直された答えです。

したがって、問題はプリンシパルクラスとはまったく関係ありません。プリンシパルクラスは。のままにしておく必要がありNSApplicationます。

前に触れたように、主な問題は、NSApplicationに適切なアプリケーションデリゲートを登録していないことです。プリンシパルクラスを変更しても、これは修正されません。NIBファイルでアプリケーションデリゲートを設定できますが、この問題ではそれは本当にやり過ぎです。

ただし、実際にはいくつかの問題があります。

  1. (投稿された)コードは、OSXのバージョンと完全に一致しないいくつかのiOSクラスとメソッドを使用して記述されています。

  2. AppDelegateクラスがシステムに登録されていることを確認してから、NSApplicationを手動で初期化し、そのアプリケーションデリゲートを設定する必要があります。

  3. ここでは、リンクが非常に重要であることがわかります。リンカがFoundationキットとAppKitをメモリにドラッグするようにする外部シンボルが必要です。NSObjectそうでなければ、 Objective-Cランタイムのようにクラスを登録する簡単な方法はありません。

iOSvOSXクラス

NSObjectOSXアプリケーションデリゲートは、からではなく、から 派生しUIResponderているため、次の行になります。

AppDelClass = objc_allocateClassPair((Class) objc_getClass("UIResponder"), "AppDelegate", 0);

読む必要があります:

AppDelClass = objc_allocateClassPair((Class) objc_getClass("NSObject"), "AppDelegate", 0);

また、OSXアプリケーションデリゲートは、iOSアプリケーションデリゲートとは異なるメッセージに応答します。特に、applicationDidFinishLaunching:セレクター(NSNotifierタイプのオブジェクトを受け取るid)に応答する必要があります。

この線

class_addMethod(AppDelClass, sel_getUid("application:didFinishLaunchingWithOptions:"), (IMP) AppDel_didFinishLaunching, "i@:@@");

読む必要があります:

class_addMethod(AppDelClass, sel_getUid("applicationDidFinishLaunching:"), (IMP) AppDel_didFinishLaunching, "i@:@");

パラメータ("i@:@@""i@:@")が異なることに注意してください。

NSApplicationの設定

AppDelegateObjective-Cランタイムに登録するには、次の2つの選択肢があります。

  1. initAppDelメソッドをで宣言します。これにより、前に__attribute__((constructor))セットアップコードによってメソッドが呼び出されるようになります。main

  2. オブジェクトをインスタンス化する前に、自分で呼び出してください。

__attribute__私は一般的にラベルを信用していません。それらはおそらく同じままですが、Appleはそれらを変更するかもしれません。initAppDelから電話することを選択しましたmain

クラスをシステムに登録するAppDelegateと、基本的にはObjective-Cクラスのように機能します。あなたはそれをObjective-Cクラスのようにインスタンス化し、それをのように渡すことができますid。実際にはidです。

AppDelegateがアプリケーションデリゲートとして実行されるようにするには、を設定する必要がありますNSAppliction。独自のアプリケーションデリゲートをローリングしているため、これを行うために実際に使用することはできませんNSApplicationMain。実際、それほど難しくはないことがわかりました。

void init_app(void)
{
  objc_msgSend(
      objc_getClass("NSApplication"), 
      sel_getUid("sharedApplication"));

  if (NSApp == NULL)
  {
    fprintf(stderr,"Failed to initialized NSApplication... terminating...\n");
    return;
  }

  id appDelObj = objc_msgSend(
      objc_getClass("AppDelegate"), 
      sel_getUid("alloc"));
  appDelObj = objc_msgSend(appDelObj, sel_getUid("init"));

  objc_msgSend(NSApp, sel_getUid("setDelegate:"), appDelObj);
  objc_msgSend(NSApp, sel_getUid("run"));
}

リンクとObjective-Cランタイム

それで、少なくとも私にとって、これが問題の本当の肉です。Objective-Cランタイムを実際に持っNSObjectて登録していない限り、上記のすべては完全に失敗し ます。NSApplication

通常、Objective-Cで作業している場合、コンパイラはリンカーにそれが必要であることを通知します。これは、ファイルに一連の特別な未解決のシンボルを配置することによって行われ.oます。SomeObj.mファイルをコンパイルすると:

#import <Foundation/NSObject.h>

@interface SomeObject : NSObject
@end

@implementation SomeObject
@end

を使用してから、 :clang -c SomeObj.mを使用して記号を確認します。nm SomeObj.o

0000000000000000 s L_OBJC_CLASS_NAME_
                 U _OBJC_CLASS_$_NSObject
00000000000000c8 S _OBJC_CLASS_$_SomeObject
                 U _OBJC_METACLASS_$_NSObject
00000000000000a0 S _OBJC_METACLASS_$_SomeObject
                 U __objc_empty_cache
                 U __objc_empty_vtable
0000000000000058 s l_OBJC_CLASS_RO_$_SomeObject
0000000000000010 s l_OBJC_METACLASS_RO_$_SomeObject

左側にaが付いたこれらの素敵な_OBJC_CLASS_$_シンボルがすべてU表示され、シンボルが未解決であることを示します。このファイルをリンクすると、リンカはこれを取得し、参照を解決するためにFoundationフレームワークをロードする必要があることを認識します。これにより、FoundationフレームワークはそのすべてのクラスをObjective-Cランタイムに登録するように強制されます。コードにAppKitフレームワークが必要な場合は、同様の何かが必要です。

「AppDelegate_orig.c」に名前を変更したAppDelegateコードをコンパイルしてclang -c AppDelegate_orig.c、実行 nmすると、次のようになります。

00000000000001b8 s EH_frame0
000000000000013c s L_.str
0000000000000145 s L_.str1
000000000000014b s L_.str2
0000000000000150 s L_.str3
0000000000000166 s L_.str4
0000000000000172 s L_.str5
000000000000017e s L_.str6
00000000000001a9 s L_.str7
0000000000000008 C _AppDelClass
0000000000000000 T _AppDel_didFinishLaunching
00000000000001d0 S _AppDel_didFinishLaunching.eh
                 U _class_addMethod
00000000000000c0 t _initAppDel
00000000000001f8 s _initAppDel.eh
                 U _objc_allocateClassPair
                 U _objc_getClass
                 U _objc_msgSend
                 U _objc_registerClassPair
                 U _sel_getUid

FoundationまたはAppKitフレームワークを強制的にリンクさせる未解決のシンボルがないことがわかります。これは、へのすべての呼び出しがobjc_getClassNULLを返すことを意味します。つまり、すべてがクラッシュします。

コードの残りの部分がどのように見えるかわからないので、これは問題ではないかもしれませんが、この問題を解決することで、変更されたAppDelegate.cファイルを単独で(あまり機能しない)OSXアプリケーションにコンパイルできます。

ここでの秘訣は、リンカーがFoundationとAppKitを取り込む必要がある外部シンボルを見つけることです。それは比較的簡単であることが判明しました。AppKitはNSApp、アプリケーションのインスタンスを保持するグローバル変数を提供しますNSApplication。AppKitフレームワークはFoundationフレームワークに依存しているため、無料で入手できます。これへの外部参照を宣言するだけで十分でした。

extern id NSApp;

(注:実際にどこかで変数を使用する必要があります。そうしないと、コンパイラーが変数を最適化してしまう可能性があり、必要なフレームワークが失われます。)

コードとスクリーンショット:

これが私のバージョンのAppDelegate.cです。これにはmainすべてが含まれ、設定する必要があります。結果はそれほどエキサイティングではありませんが、画面に小さなウィンドウが開きます。

#include <stdio.h>
#include <stdlib.h>

#include <objc/runtime.h>
#include <objc/message.h>

extern id NSApp;

struct AppDel
{
    Class isa;
    id window;
};


// This is a strong reference to the class of the AppDelegate
// (same as [AppDelegate class])
Class AppDelClass;

BOOL AppDel_didFinishLaunching(struct AppDel *self, SEL _cmd, id notification) {
    self->window = objc_msgSend(objc_getClass("NSWindow"),
      sel_getUid("alloc"));

    self->window = objc_msgSend(self->window, 
      sel_getUid("init"));

    objc_msgSend(self->window, 
      sel_getUid("makeKeyAndOrderFront:"),
      self);

    return YES;
}

static void initAppDel() 
{
  AppDelClass = objc_allocateClassPair((Class)
    objc_getClass("NSObject"), "AppDelegate", 0);

  class_addMethod(AppDelClass, 
      sel_getUid("applicationDidFinishLaunching:"), 
      (IMP) AppDel_didFinishLaunching, "i@:@");

  objc_registerClassPair(AppDelClass);
}

void init_app(void)
{
  objc_msgSend(
      objc_getClass("NSApplication"), 
      sel_getUid("sharedApplication"));

  if (NSApp == NULL)
  {
    fprintf(stderr,"Failed to initialized NSApplication...  terminating...\n");
    return;
  }

  id appDelObj = objc_msgSend(
      objc_getClass("AppDelegate"), 
      sel_getUid("alloc"));
  appDelObj = objc_msgSend(appDelObj, sel_getUid("init"));

  objc_msgSend(NSApp, sel_getUid("setDelegate:"), appDelObj);
  objc_msgSend(NSApp, sel_getUid("run"));
}


int main(int argc, char** argv)
{
  initAppDel();
  init_app();
  return EXIT_SUCCESS;
}

次のようにコンパイル、インストール、実行します。

clang -g -o AppInC AppDelegate.c -lobjc -framework Foundation -framework AppKit
mkdir -p AppInC.app/Contents/MacOS
cp AppInC AppInC.app/Contents/MacOS/
cp Info.plist AppInC.app/Contents/
open ./AppInC.app

Info.plistは次のとおりです。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>CFBundleDevelopmentRegion</key>
        <string>en</string>
        <key>CFBundleExecutable</key>
        <string>AppInC</string>
        <key>CFBundleIconFile</key>
        <string></string>
        <key>CFBundleIdentifier</key>
        <string>com.foo.AppInC</string>
        <key>CFBundleInfoDictionaryVersion</key>
        <string>6.0</string>
        <key>CFBundleName</key>
        <string>AppInC</string>
        <key>CFBundlePackageType</key>
        <string>APPL</string>
        <key>CFBundleShortVersionString</key>
        <string>1.0</string>
        <key>CFBundleSignature</key>
        <string>????</string>
        <key>CFBundleVersion</key>
        <string>1</string>
        <key>LSApplicationCategoryType</key>
        <string>public.app-category.games</string>
        <key>LSMinimumSystemVersion</key>
        <string></string>
        <key>NSPrincipalClass</key>
        <string>NSApplication</string>
</dict>
</plist>

スクリーンショット付き:

アプリケーションのスクリーンショット

于 2012-07-03T22:00:35.713 に答える
8

この道の先には、狂気、または少なくとも時間の膨大な浪費があります。Cocoaアプリは、本質的にObjective-Cベースであり、非常に特殊な設計のサブディレクトリ階層を持つ.appラッパーを使用するように設計されています(Info.plistや、場合によってはコード署名などが必要です)。

あなたが遭遇していると思う代理人の問題は完全に赤いニシンです。本当の問題は、あなたが実際に適切なココアアプリを構築していないということです。

そして、いいえ、iOSでは完全には機能しません。そのプラットフォームでは、アプリケーションを非常に特殊な方法で構築する必要があるためです。

「Cをラップ」したい場合は、次のようにします。

  • 基本的なCocoaアプリケーションから始めます
  • main関数の名前をmainC()などに変更します(本当に必要な場合は、コンパイラ/リンカーコマンドラインから実行できます)
  • [理想的には]メインスレッドのすべてのCグープを移動して、メインイベントループをブロックしないようにします

プリンシパルクラスとして「純粋なC」が必要な場合は、次のようにします。

  • クラスを実装する.mファイルを作成します
  • クラスのメソッドを実装して、C関数を呼び出します

完了-それはとても簡単です。あなたがしているようにあなた自身を転がすことは確かに教育的ですが(いいえ、本当に-そうです、そして私は皆にそれを探求することを勧めます)、それは単に車輪を再発明することです。

tl; drシステムAPIと戦っている場合、それは間違っています。


もちろん、私はリッチのファンデーションをスプーンでOS Xに移植しています。これは完全に時間の無駄ですが、少なくとも私を落胆させることはありません。

素晴らしい!

その場合pythonw、比較的非.appラッパーベースのシェルスクリプトからのGUIの実装を可能にするrubycocoaシェルの実装および/または(IIRC)を確認する必要があります。

この問題は、プリンシパルクラスをはるかに超えています。[NSBundle mainBundle]それは本当に意味のあるものである必要があり、ある種のリソースをカプセル化する必要があると考えてください。

また、このPyObjCプロジェクトでは、「シェルスクリプト」ベースのCocoaアプリケーションを実行するためのさまざまな試みが行われました。自分のやりたいことを実行するものが少なくともいくつか存在すると思うので、さまざまな例について話したいと思うかもしれません。

「シェルスクリプトからのココアアプリケーション」などのグーグル検索も、過去23年間に何度も出てきたので、役に立つかもしれません。

于 2012-07-03T21:24:26.603 に答える
1

Objective-Cランタイム関数を呼び出すことをいとわないが、@implementationを使用することを望まない場合(なんらかのクレイジーな理由で)、objc_allocateClassPairand friendsを使用してクラスを作成し、それをメインクラスとして使用します。

于 2012-07-03T21:31:19.133 に答える