繁体   English   中英

C作为校长级或;没有ObjC的可可应用程序

[英]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文件可以设置应用程序委托,但这对这个问题来说真的太过分了。

但是,实际上有几个问题:

  1. 您的(已发布)代码是使用一些与OS X版本不完全对应的iOS类和方法编写的。

  2. 您必须确保您的AppDelegate类已在系统中注册,然后您必须手动初始化NSApplication并设置其应用程序委托。

  3. 链接在这里非常重要。 您需要一些外部符号,使链接器将Foundation Kit和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: 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@:@" )是不同的。

设置NSApplication

使用Objective-C运行时注册AppDelegate有两种选择:

  1. 使用__attribute__((constructor))声明initAppDel方法,这会强制它在main之前被设置代码调用,或者

  2. 在实例化对象之前自己调用它。

我一般不信任__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运行时

所以,这是问题的真正含义,至少对我而言。 除非您实际在Objective-C运行时中注册了NSObjectNSApplication ,否则上述所有操作都将失败,完全失败。

通常,如果您在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”,那么:

  • 从基本的Cocoa应用程序开始
  • 将main函数重命名为mainC()或其他东西(如果你真的想要,可以从编译器/链接器命令行完成)
  • [理想]移动主线程的所有C goop,这样就不会阻塞主事件循环

如果你想要“纯C”作为你的主要课程,那么:

  • 创建一个实现该类的.m文件
  • 实现类的方法来调用你的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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM