[英]ObjC app project gets build error at its reference to a C function within a .cpp file within ObjC class lib imported into app
[英]C As Principal Class Or; A Cocoa App Without ObjC
对于那些质疑我的理智/浪费时间/能力/动机的人来说,这件事是“基础与勺子”项目的一个端口,现在因为是一个全C的iOS应用而臭名昭着。
所以,我已经有了ac文件,并成为了全c-mac-app背后的主要类,但是,限制因素的组合阻止了应用程序的启动。 就目前而言,该项目只是一个main.m和一个名为AppDelegate.c的类,所以我输入了“AppDelegate”作为info.plist中主要类的名称,令我完全惊讶的是,打印的日志:
无法找到类:AppDelegate,退出
这在iOS中可以很好地工作,因为main函数接受委托类的名称,并自动处理它,但NSApplicationMain()不接受这样的参数。
现在,我知道这是因为在C中没有@interface/@implementation
指令,而这正是操作系统似乎正在寻找的,所以我编写了一个简单的NSApplication
子类并将其作为Principal Class提供给plist,它发动得非常好。 我的问题是,怎样才能将ac文件设置为mac应用程序中的主要类并使其正确启动?
编辑:解决了! 该文件可能是.m(框架错误,由于某种原因),但类对的分配足以滑倒。 您可以在此处下载基于C的Mac App的源代码。 快乐挖!
代码已被编写,签名,密封和盖章,但我需要的只是解决plist中NSPrincipalClass要求的方法。
好的,这是一个基本上重写的答案,似乎对我有用。
所以,你的问题与主要课程无关。 您应该将主要类保留为NSApplication
。
正如我之前提到的那样,主要问题是您没有在NSApplication中注册适当的应用程序委托。 更改主要类不会解决此问题。 NIB文件可以设置应用程序委托,但这对这个问题来说真的太过分了。
但是,实际上有几个问题:
您的(已发布)代码是使用一些与OS X版本不完全对应的iOS类和方法编写的。
您必须确保您的AppDelegate类已在系统中注册,然后您必须手动初始化NSApplication并设置其应用程序委托。
链接在这里非常重要。 您需要一些外部符号,使链接器将Foundation Kit和AppKit拖入内存。 否则,没有简单的方法来注册像NSObject
这样的类与Objective-C运行时。
OSX应用程序委托派生自NSObject
,而不是来自UIResponder
,所以行:
AppDelClass = objc_allocateClassPair((Class) objc_getClass("UIResponder"), "AppDelegate", 0);
应该读:
AppDelClass = objc_allocateClassPair((Class) objc_getClass("NSObject"), "AppDelegate", 0);
此外,OSX应用程序委托响应不同于iOS应用程序委托的消息。 特别是,他们需要响应applicationDidFinishLaunching:
selector(它采用id
类型的NSNotifier
对象)。
这条线
class_addMethod(AppDelClass, sel_getUid("application:didFinishLaunchingWithOptions:"), (IMP) AppDel_didFinishLaunching, "i@:@@");
应该读:
class_addMethod(AppDelClass, sel_getUid("applicationDidFinishLaunching:"), (IMP) AppDel_didFinishLaunching, "i@:@");
请注意,参数( "i@:@@"
和"i@:@"
)是不同的。
使用Objective-C运行时注册AppDelegate
有两种选择:
使用__attribute__((constructor))
声明initAppDel
方法,这会强制它在main
之前被设置代码调用,或者
在实例化对象之前自己调用它。
我一般不信任__attribute__
标签。 它们可能保持不变,但Apple可能会改变它们。 我选择从main
调用initAppDel
。
一旦您使用系统注册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中工作,编译器会告诉链接器它需要它。 它通过在.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
,表示符号未解析。 链接此文件时,链接器会接受此文件,然后意识到必须加载Foundation框架才能解析引用。 这会强制Foundation框架使用Objective-C运行时注册其所有类。 如果您的代码需要AppKit框架,则需要类似的东西。
如果我编译你的AppDelegate代码,我用clang -c AppDelegate_orig.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
所有调用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>
截图:
沿着这条道路走下去就是疯狂,或者至少是浪费时间。 Cocoa应用程序本质上是基于Objective-C设计的,并且使用.app包装器和非常特定设计的子目录层次结构(包括需要像Info.plist这样的东西,可能还有代码签名)。
你认为你遇到的代表问题完全是一个红色的鲱鱼; 真正的问题是你实际上并没有构建一个合适的Cocoa应用程序。
并且,不,它在iOS下不能很好地工作,因为该平台更需要以非常特定的方式构建应用程序。
如果你想“包裹C”,那么:
如果你想要“纯C”作为你的主要课程,那么:
完成 - 就这么简单。 在你正在做的时候滚动你自己当然是有教育意义的(不,真的 - 这是我鼓励大家去探索它),它只是重新发明了一个轮子。
tl; dr如果您正在与系统API作斗争,那么您做错了。
当然,但是我用勺子将Rich的基础移植到OS X.这是一个完整的时间浪费,但这并不会让我感到沮丧
真棒!
在这种情况下 - 你需要看一下pythonw
和/或(IIRC)rubycocoa shell的实现,它允许从一个相对非.app包装器的shell脚本实现GUI。
这个问题远远超出了主要课程。 考虑到[NSBundle mainBundle]
确实需要有意义,它确实需要封装某种资源。
此外, PyObjC
项目在做基于“shell脚本”的Cocoa应用程序时有许多不同的尝试。 您可能想要了解各种示例,因为我认为至少仍然存在一些您想做的事情。
谷歌搜索“来自shell脚本的Cocoa应用程序”等也可能是有用的,因为在过去的23年里,这已经出现了很多次。
如果您愿意调用Objective-C运行时函数但不愿意使用@implementation(出于任何疯狂的原因),只需使用objc_allocateClassPair
和朋友创建一个类,并将其用作主类。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.