我正在Mac OSX上使用纯C语言创建一个应用程序。我想要创建一个窗口来显示我的应用程序。
最好的情况是使用纯C语言解决方案,但如果必须使用Objective-C类来初始化窗口,然后将上下文发送到我的C代码中,那也可以。
我没有使用Xcode,只是在一个简单的文本编辑器中尝试导入Cocoa,但它只生成了很多错误。
如何编写简单的纯C代码来在OSX中显示窗口?
我对"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"));
}
您可以使用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应用程序。就像下面的屏幕截图一样
你可以使用NSMenu来添加菜单
id applicationMenuBar = [NSMenu new];
id appMenuItem = [NSMenuItem new];
[applicationMenuBar addItem:appMenuItem];
[NSApp setMainMenu: applicationMenuBar];
你能做到这个吗?可以和不可以(如果你足够坚持,你可以做任何事情)。是的,你可以,但是不,你不应该。无论如何,对于非常坚持的人来说,这是可以完成的。由于编写一个示例需要一些时间,我在网上找到了一个慷慨的人已经做好了。在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特性。虽然按照技术上的角度来说,可以用这种方式实现所有功能,但这会使它比本该的要难得多。
我刚刚发现这个链接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"
/*
*/
来自维基百科:
#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
很不幸,最受欢迎的答案在使用新的苹果芯片电脑时无法工作,因为存在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"));
}
基于纯C的跨平台示例:(Windows/macOS/Linux) https://nappgui.com/en/demo/products.html
关于macOS在纯C中的可移植性(更新支持BigSur和M1): https://nappgui.com/en/start/win_mac_linux.html#h2
tell application "Terminal"
do script "ex /tmp/test; exit"
end tell
这将打开一个终端窗口,显示“ex”,当它退出时将终止 shell 进程(因此无法输入更多命令),但是它不会关闭 Terminal 本身 - 对此你必须更努力。
如果您希望应用程序自己创建窗口,则需要编写自己的简单 TTY 窗口,您可能会找到一些可用的类,或者您可以从开放源代码终端应用程序(如iterm)中借用代码。
希望对你有所帮助。
glutInit
、glutCreateWindow
等函数。 - JWWalker