好的,这是一个似乎对我有用的经过大幅改写的答案。
因此,您的问题与主要课程无关。您应该将主要课程保留为NSApplication
.
正如我之前提到的,主要问题是您没有向 NSApplication 注册适当的应用程序委托。更改主要课程不会解决此问题。一个 NIB 文件可以设置应用程序委托,但这对于这个问题来说真的是矫枉过正。
但是,实际上有几个问题:
您的(发布的)代码是使用一些与 OS X 版本不完全一致的 iOS 类和方法编写的。
您必须确保您的 AppDelegate 类已在系统中注册,然后您必须手动初始化 NSApplication 并设置其应用程序委托。
事实证明,链接在这里非常重要。您需要有一些外部符号使链接器将 Foundation 工具包和 AppKit 拖入内存。否则,没有像NSObject
使用 Objective-C 运行时那样注册类的简单方法。
iOS v OSX 类
OSX 应用程序委托从 派生NSObject
,而不是从
派生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 运行时注册有两种选择:
要么用 声明你的initAppDel
方法__attribute__((constructor))
,这迫使它被之前的设置代码调用main
,要么
在实例化对象之前自己调用它。
我一般不相信__attribute__
标签。它们可能会保持不变,但苹果可能会改变它们。我选择从 拨打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 运行时
所以,这就是问题的真正实质,至少对我而言。NSObject
除非您实际拥有并NSApplication
在 Objective-C 运行时中注册,否则上述所有操作都将彻底失败
。
通常,如果您使用的是 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
您会看到所有这些漂亮的_OBJC_CLASS_$_
符号,U
左侧带有 a,表示这些符号未解析。当你链接这个文件时,链接器会接受它,然后意识到它必须加载 Foundation 框架来解析引用。这迫使 Foundation 框架将其所有类注册到 Objective-C 运行时。如果您的代码需要 AppKit 框架,则需要类似的东西。
如果我编译您的 AppDelegate 代码,我已将其重命名为“AppDelegate_orig.c”,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>
用截图: