在MacOS上使用纯C语言创建窗口应用程序

29

我正在Mac OSX上使用纯C语言创建一个应用程序。我想要创建一个窗口来显示我的应用程序。

最好的情况是使用纯C语言解决方案,但如果必须使用Objective-C类来初始化窗口,然后将上下文发送到我的C代码中,那也可以。

我没有使用Xcode,只是在一个简单的文本编辑器中尝试导入Cocoa,但它只生成了很多错误。

如何编写简单的纯C代码来在OSX中显示窗口?


Xcode不是编写OS X Cocoa应用程序的必要条件,但它可以大大降低难度。除非您计划编写一个X窗口应用程序并依赖于Quartz或其他窗口管理器进行渲染,否则纯C几乎不可能发生。使用Objective-C的纯粹Cocoa Minimalist Cocoa with Objective-C ,然后将核心逻辑埋藏在C中是可行的,但最终您可能会发现直接使用Objective-C更容易。 - WhozCraig
你想在窗口中展示什么?静态文本、可编辑文本、UI控件、3D图形...? - JWWalker
1
@SevenBits 不是完全正确,我正在尝试在OS X上进行,那个答案是针对iOS的。 - Mago
@JWWalker 我将在其上绘制的画布 - Mago
如果你愿意使用OpenGL进行绘图,那么你可以使用GLUT框架。请参阅glutInitglutCreateWindow等函数。 - JWWalker
显示剩余3条评论
7个回答

26

我对"Pure C"的被接受答案进行了翻译:

// based on https://dev59.com/UV0a5IYBdhLWcg3wfIt-#30269562
// Minimal Pure C code to create a window in Cocoa

// $ clang minimal.c -framework Cocoa -o minimal.app

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

#include <Carbon/Carbon.h>

#define cls objc_getClass
#define sel sel_getUid
#define msg ((id (*)(id, SEL, ...))objc_msgSend)
#define cls_msg ((id (*)(Class, SEL, ...))objc_msgSend)

// poor man's bindings!
typedef enum NSApplicationActivationPolicy {
    NSApplicationActivationPolicyRegular   = 0,
    NSApplicationActivationPolicyAccessory = 1,
    NSApplicationActivationPolicyERROR     = 2,
} NSApplicationActivationPolicy;

typedef enum NSWindowStyleMask {
    NSWindowStyleMaskBorderless     = 0,
    NSWindowStyleMaskTitled         = 1 << 0,
    NSWindowStyleMaskClosable       = 1 << 1,
    NSWindowStyleMaskMiniaturizable = 1 << 2,
    NSWindowStyleMaskResizable      = 1 << 3,
} NSWindowStyleMask;

typedef enum NSBackingStoreType {
    NSBackingStoreBuffered = 2,
} NSBackingStoreType;

int main(int argc, char *argv[])
{
    // id app = [NSApplication sharedApplication];
    id app = cls_msg(cls("NSApplication"), sel("sharedApplication"));

    // [app setActivationPolicy:NSApplicationActivationPolicyRegular];
    msg(app, sel("setActivationPolicy:"), NSApplicationActivationPolicyRegular);

    struct CGRect frameRect = {0, 0, 600, 500};

    // id window = [[NSWindow alloc] initWithContentRect:frameRect styleMask:NSWindowStyleMaskTitled|NSWindowStyleMaskClosable|NSWindowStyleMaskResizable backing:NSBackingStoreBuffered defer:NO];
    id window = msg(cls_msg(cls("NSWindow"), sel("alloc")),
                    sel("initWithContentRect:styleMask:backing:defer:"),
                    frameRect,
                    NSWindowStyleMaskTitled|NSWindowStyleMaskClosable|NSWindowStyleMaskResizable,
                    NSBackingStoreBuffered,
                    false);
    msg(window, sel("setTitle:"), cls_msg(cls("NSString"), sel("stringWithUTF8String:"), "Pure C App"));

    // [window makeKeyAndOrderFront:nil];
    msg(window, sel("makeKeyAndOrderFront:"), nil);

    // [app activateIgnoringOtherApps:YES];
    msg(app, sel("activateIgnoringOtherApps:"), true);

    msg(app, sel("run"));
}

17

您可以使用Objective-C runtime API示例(iOS)在纯C中创建iOS应用程序

或者使用相同的代码在Objective-C中:

echo '#import <Cocoa/Cocoa.h>
int main ()
    {
        @autoreleasepool{
            [NSApplication sharedApplication];
            [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
            id applicationName = [[NSProcessInfo processInfo] processName];
            id window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 120, 120)
                styleMask:NSTitledWindowMask backing:NSBackingStoreBuffered defer:NO];
            [window cascadeTopLeftFromPoint:NSMakePoint(20,20)];
            [window setTitle: applicationName];
            [window makeKeyAndOrderFront:nil];
            [NSApp activateIgnoringOtherApps:YES];
            [NSApp run];
        }
        return 0;
}' | gcc -fobjc-arc -framework Cocoa -x objective-c -o MicroApp - ; ./MicroApp

这将运行具有1个窗口的Cocoa应用程序。就像下面的屏幕截图一样

enter image description here

你可以使用NSMenu来添加菜单

    id applicationMenuBar = [NSMenu new];
    id appMenuItem        = [NSMenuItem new];
    [applicationMenuBar addItem:appMenuItem];
    [NSApp setMainMenu: applicationMenuBar];

8
这个答案是错误的。你不需要使用Objective-C来创建UI界面。你可以使用Objective-C运行时API从C语言中调用大多数Objective-C类,并以编程方式创建UI元素。虽然这样做不太实际,但确实是可行的。 - SevenBits
请能否给一个例子? - CryingHippo
1
这是针对iOS的,但同样适用于OS X。 - SevenBits
哦,是的 :) 运行时 API... 我会更新我的答案,感谢您的提示! - CryingHippo
2
那个示例代码并不是“混合使用C/C++和Objective-C”,它是纯粹的Objective-C。 - nobody
6
这个答案不应该被接受。它并不是问题的正确答案 - 这个程序是纯的Objective-C,而发帖者声明他想要用纯C编写程序(即,不使用Objective-C)。 - SevenBits

13

你能做到这个吗?可以和不可以(如果你足够坚持,你可以做任何事情)。是的,你可以,但是不,你不应该。无论如何,对于非常坚持的人来说,这是可以完成的。由于编写一个示例需要一些时间,我在网上找到了一个慷慨的人已经做好了。在GitHub上查看这个存储库以获取完整的代码和解释。以下是一些片段:

cmacs_simple_msgSend((id)objc_getClass("NSApplication"), sel_getUid("sharedApplication"));

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

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

cmacs_void_msgSend1(NSApp, sel_getUid("setDelegate:"), appDelObj);
cmacs_void_msgSend(NSApp, sel_getUid("run"));

注意到这段代码使用了 Objective-C 运行时 API 来创建一个伪造的 AppDelegate。而创建窗口是一个复杂的过程:

self->window = cmacs_simple_msgSend((id)objc_getClass("NSWindow"), sel_getUid("alloc"));

/// Create an instance of the window.
self->window = cmacs_window_init_msgSend(self->window, sel_getUid("initWithContentRect:styleMask:backing:defer:"), (CMRect){0,0,1024,460}, (NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask), 0, false);

/// Create an instance of our view class.
///
/// Relies on the view having declared a constructor that allocates a class pair for it.
id view = cmacs_rect_msgSend1(cmacs_simple_msgSend((id)objc_getClass("View"), sel_getUid("alloc")), sel_getUid("initWithFrame:"), (CMRect){ 0, 0, 320, 480 });

// here we simply add the view to the window.
cmacs_void_msgSend1(self->window, sel_getUid("setContentView:"), view);
cmacs_simple_msgSend(self->window, sel_getUid("becomeFirstResponder"));

// Shows our window in the bottom-left hand corner of the screen.
cmacs_void_msgSend1(self->window, sel_getUid("makeKeyAndOrderFront:"), self);
return YES;

所以,是的。你可以用纯C编写Cocoa应用程序。但我不建议这样做。90%的代码可以被一个xib文件替换掉,这样做确实限制了你的应用程序,因为苹果开发堆栈的更高级特性真正依赖于Objective-C特性。虽然按照技术上的角度来说,可以用这种方式实现所有功能,但这会使它比本该的要难得多。


10
我记得大约一年前看到过这个问题,当时我非常希望能够打开一个窗口,在谷歌上搜索了好几天,只找到了你在这篇文章之前看到的那种答案。
我正在阅读 Mac 操作系统构建的伯克利软件分发系统。http://codex.cs.yale.edu/avi/os-book/OS9/appendices-dir/a.pdf 在第17页上,短语“……在麻省理工学院开发的 X 窗口系统”让我想起了我以前无法打开窗口的经历,以及我对此的愤怒,我想也许这是最终的解决方案!
我谷歌搜索了“BSD X 窗口编程”,最终成功地用纯 C 打开了一个窗口。

我刚刚发现这个链接https://en.wikibooks.org/wiki/X_Window_Programming/Xlib,虽然我还不是专家,但是请查看示例,并确保按照顶部的注释进行编译,其中包括如何使用X11库进行编译(只要你有-lX11,就可以忽略-Wall和-O命令)。

如果无法编译,或找不到头文件,则需要帮助其找到头文件。

您的系统上可能有几个不同的位置存放X11 include文件。很可能你会在 /opt/X11/include 找到所有所需的头文件定义。

您可以在C程序中包含完整路径,例如:

#include "/opt/X11/include/X11/Xlib.h"

但我们希望它看起来像这样:#include <X11/Xlib.h>,所以您可以在编译时将此开关添加到GCC中:-I /opt/X11/include 或者进入您的主目录下的.profile.bashrc.bash_profile文件并添加:
export C_INCLUDE_PATH="$C_INCLUDE_PATH:/opt/X11/include"
/*

简单的Xlib应用程序,在窗口中绘制一个框。

*/

来自维基百科:

#include<X11/Xlib.h>
#include<stdio.h>
#include<stdlib.h> // prevents error for exit on line   18 when compiling with gcc
int main() {
  Display *d;
  int s;
  Window w;
  XEvent e;

                    /* open connection with the server */
  d=XOpenDisplay(NULL);
  if(d==NULL) {
    printf("Cannot open display\n");
    exit(1);
  }
  s=DefaultScreen(d);

                    /* create window */
  w=XCreateSimpleWindow(d, RootWindow(d, s), 10, 10, 100, 100, 1,
                     BlackPixel(d, s), WhitePixel(d, s));

  // Process Window Close Event through event handler so      XNextEvent does Not fail
  Atom delWindow = XInternAtom( d, "WM_DELETE_WINDOW", 0 );
  XSetWMProtocols(d , w, &delWindow, 1);

                    /* select kind of events we are interested in */
  XSelectInput(d, w, ExposureMask | KeyPressMask);

                    /* map (show) the window */
  XMapWindow(d, w);

                    /* event loop */
  while(1) {
    XNextEvent(d, &e);
                    /* draw or redraw the window */
    if(e.type==Expose) {
      XFillRectangle(d, w, DefaultGC(d, s), 20, 20, 10, 10);
    }
                    /* exit on key press */
    if(e.type==KeyPress)
      break;

    // Handle Windows Close Event
    if(e.type==ClientMessage)
       break;
  }

                    /* destroy our window */
  XDestroyWindow(d, w);

                    /* close connection to server */
  XCloseDisplay(d);

  return 0;
}

编译:

 gcc -O2 -Wall -o test test.c -L /usr/X11R6/lib -lX11 -lm

1
这需要XQuartz吗?我正在使用XCB来开发我的纯C应用程序,但在我的M1 Mac上它无法工作,直到我从Homebrew安装了XQuartz 2.8.1。 - Ignis Incendio

4

很不幸,最受欢迎的答案在使用新的苹果芯片电脑时无法工作,因为存在ABI不匹配的问题。基本上,在ARM64架构下,您无法使用带有可变参数的objc_msgSend声明,您必须为每个调用指定正确的参数类型。下面是可以在苹果芯片电脑上运行的版本:

// based on https://dev59.com/UV0a5IYBdhLWcg3wfIt-#59596600
// Minimal Pure C code to create a window in Cocoa
// Adapted to work on ARM64

// $ clang minimal.c -framework Cocoa -o minimal.app

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

#include <Carbon/Carbon.h>

#define cls objc_getClass
#define sel sel_getUid
#define msg ((id (*)(id, SEL))objc_msgSend)
#define msg_int ((id (*)(id, SEL, int))objc_msgSend)
#define msg_id  ((id (*)(id, SEL, id))objc_msgSend)
#define msg_ptr ((id (*)(id, SEL, void*))objc_msgSend)
#define msg_cls ((id (*)(Class, SEL))objc_msgSend)
#define msg_cls_chr ((id (*)(Class, SEL, char*))objc_msgSend)

// poor man's bindings!
typedef enum NSApplicationActivationPolicy {
    NSApplicationActivationPolicyRegular   = 0,
    NSApplicationActivationPolicyAccessory = 1,
    NSApplicationActivationPolicyERROR     = 2,
} NSApplicationActivationPolicy;

typedef enum NSWindowStyleMask {
    NSWindowStyleMaskBorderless     = 0,
    NSWindowStyleMaskTitled         = 1 << 0,
    NSWindowStyleMaskClosable       = 1 << 1,
    NSWindowStyleMaskMiniaturizable = 1 << 2,
    NSWindowStyleMaskResizable      = 1 << 3,
} NSWindowStyleMask;

typedef enum NSBackingStoreType {
    NSBackingStoreBuffered = 2,
} NSBackingStoreType;

int main(int argc, char *argv[])
{
    // id app = [NSApplication sharedApplication];
    id app = msg_cls(cls("NSApplication"), sel("sharedApplication"));

    // [app setActivationPolicy:NSApplicationActivationPolicyRegular];
    msg_int(app, sel("setActivationPolicy:"), NSApplicationActivationPolicyRegular);

    struct CGRect frameRect = {0, 0, 600, 500};

    // id window = [[NSWindow alloc] initWithContentRect:frameRect styleMask:NSWindowStyleMaskTitled|NSWindowStyleMaskClosable|NSWindowStyleMaskResizable backing:NSBackingStoreBuffered defer:NO];
    id window = ((id (*)(id, SEL, struct CGRect, int, int, int))objc_msgSend)(
        msg_cls(cls("NSWindow"), sel("alloc")),
        sel("initWithContentRect:styleMask:backing:defer:"),
        frameRect,
        NSWindowStyleMaskTitled|NSWindowStyleMaskClosable|NSWindowStyleMaskResizable,
        NSBackingStoreBuffered,
        false
    );
    msg_id(window, sel("setTitle:"), msg_cls_chr(cls("NSString"), sel("stringWithUTF8String:"), "Pure C App"));

    // [window makeKeyAndOrderFront:nil];
    msg_ptr(window, sel("makeKeyAndOrderFront:"), nil);

    // [app activateIgnoringOtherApps:YES];
    msg_int(app, sel("activateIgnoringOtherApps:"), true);

    msg(app, sel("run"));
}

0

-1
我正在Mac OSX上使用纯C创建应用程序。我想要创建一个窗口来存储我的应用程序。
你是在寻找TTY窗口吗?
如果是的话,你的应用程序需要创建窗口吗?
如果不需要,那么你可以简单地编写你的纯C程序,并从终端中执行它-这是一个“纯C”的TTY环境。
如果你想要一个可双击的应用程序,你可以编写一个AppleScript,它将打开终端并运行你的C程序,类似于:
tell application "Terminal"
   do script "ex /tmp/test; exit"
end tell

这将打开一个终端窗口,显示“ex”,当它退出时将终止 shell 进程(因此无法输入更多命令),但是它不会关闭 Terminal 本身 - 对此你必须更努力。

如果您希望应用程序自己创建窗口,则需要编写自己的简单 TTY 窗口,您可能会找到一些可用的类,或者您可以从开放源代码终端应用程序(如iterm)中借用代码。

希望对你有所帮助。


他明确表示他希望使用C代码来生成一个OS X(即Cocoa)窗口。因此,我认为使用基于终端的应用程序对他的需求来说并不合适。 - SevenBits
@SevenBits - 自从我写下这个答案后,OP确实写道他正在寻找一个“画布来绘制”,所以除非他们指的是VT100图形,但我怀疑不是,那么这个答案就不适用了,正如你所正确观察到的那样。然而,未来的读者可能会想在OS X上的窗口中运行“TTY代码”,因此我保留了这个答案。 - CRD

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接