さて、これは私にとってうまくいくように見える実質的に書き直された答えです。
したがって、問題はプリンシパルクラスとはまったく関係ありません。プリンシパルクラスは。のままにしておく必要がありNSApplication
ます。
前に触れたように、主な問題は、NSApplicationに適切なアプリケーションデリゲートを登録していないことです。プリンシパルクラスを変更しても、これは修正されません。NIBファイルでアプリケーションデリゲートを設定できますが、この問題ではそれは本当にやり過ぎです。
ただし、実際にはいくつかの問題があります。
(投稿された)コードは、OSXのバージョンと完全に一致しないいくつかのiOSクラスとメソッドを使用して記述されています。
AppDelegateクラスがシステムに登録されていることを確認してから、NSApplicationを手動で初期化し、そのアプリケーションデリゲートを設定する必要があります。
ここでは、リンクが非常に重要であることがわかります。リンカがFoundationキットとAppKitをメモリにドラッグするようにする外部シンボルが必要です。NSObject
そうでなければ、 Objective-Cランタイムのようにクラスを登録する簡単な方法はありません。
iOSvOSXクラス
NSObject
OSXアプリケーションデリゲートは、からではなく、から
派生し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の設定
AppDelegate
Objective-Cランタイムに登録するには、次の2つの選択肢があります。
initAppDel
メソッドをで宣言します。これにより、前に__attribute__((constructor))
セットアップコードによってメソッドが呼び出されるようになります。main
オブジェクトをインスタンス化する前に、自分で呼び出してください。
__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_getClass
NULLを返すことを意味します。つまり、すべてがクラッシュします。
コードの残りの部分がどのように見えるかわからないので、これは問題ではないかもしれませんが、この問題を解決することで、変更された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>
スクリーンショット付き:
