我在 Xcode 中调试 ncurses 时遇到了同样的问题。最后,我找到了一种使用 Terminal.app 管理调试的好方法,它允许调试 ncurses。
我们知道,要初始化和使用 ncurses,我们需要在终端中运行我们的应用程序。但是当我们按下运行按钮时,Xcode 并没有打开终端。所以,如果我们从代码中请求环境变量TERM,我们会得到NULL。这就是应用程序在initscr() 上崩溃的原因。
因此,需要启动 Terminal.app,在那里执行我们的进程并为其附加调试器。可以通过Scheme setup来实现。我在 Xcode 11.4 中做到了。我基于Language:C++ 创建了一个新的 macOS 命令行工具项目。我还在Frameworks and Libraries 中添加了libncurses.tbd 依赖项。
转到Product > Scheme > Edit scheme...,选择Run 方案和Run 操作并导航到Info 选项卡。您会看到 Launch 设置为 Automatically。将其更改为Wait for the executable to be launched。
在Run方案中选择Pre-actions并添加New Run Script Action。将 Provide build settings from 从 None 更改为您的构建目标。在此处添加以下代码:
osascript -e 'tell application "Terminal"' -e 'delay 0.5' -e 'activate' -e "do script (\"$TARGET_BUILD_DIR/$PRODUCT_NAME\")" -e 'end tell' &
要在调试会话结束时选择关闭终端,请在 Run 方案中选择 Post-actions 并添加 New Run Script Action。添加以下代码:
osascript -e 'activate application "Terminal"' -e 'delay 0.5' -e 'tell application "System Events"' -e 'tell process "Terminal"' -e 'keystroke "w" using {command down}' -e 'end tell' -e 'end tell'
实际上 osascript 将始终创建至少两个终端窗口,但如果您将第一个窗口保持打开状态,它将通过 Pre 和 Post 操作自动创建并销毁第二个终端窗口。
您可能还会遇到调试器附加问题。我不知道 Xcode 在外部执行进程时不想附加调试器的确切原因,但我发现了这个问题。我还在调试会话开始期间发现stdin 流处于奇怪的状态。所以,我写了一个基于pselect 调用的解决方法。我向stdin 询问任何数据,直到它没有返回成功。我发现经过这些操作后,调试器会感觉正常,stdin 请求也会正常。这是我的代码示例:
#include <stdlib.h>
#include <string.h>
#include <ncurses.h>
#include <sys/select.h>
#include <unistd.h>
#include <errno.h>
bool g_has_terminal = false; // Check this global variable before ncurses calls
bool ensure_debugger_attached_woraround(int timeout_ms)
{
fd_set fd_stdin;
FD_ZERO(&fd_stdin);
FD_SET(STDIN_FILENO, &fd_stdin);
struct timespec timeout = { timeout_ms / 1000, (timeout_ms % 1000) * 1000000 };
do
{
errno = 0;
}
while (pselect(STDIN_FILENO + 1, &fd_stdin, NULL, NULL, &timeout, NULL) < 0 && errno == EINTR);
if (errno != 0)
{
fprintf(stderr, "Unexpected error %d", errno);
return false;
}
return true;
}
int main(int argc, const char *argv[])
{
if (!ensure_debugger_attached_woraround(700))
return 1;
char *term = getenv("TERM");
g_has_terminal = (term != NULL);
if (g_has_terminal)
g_has_terminal = (initscr() != NULL);
// Some ncurses code. Maybe you should terminate if g_has_terminal is not set
if (g_has_terminal)
{
printw("Press any key to exit...");
refresh();
getch();
endwin();
}
return 0;
}
ensure_debugger_attached_woraround 调用超时 700 毫秒。我尝试了不同的值,发现 500 毫秒是不跳过pselect 的最小时间。也许这个超时取决于机器,我不知道。您可以通过#ifdef ... #endif 或其他一些检查来包装此调用,也许通过特殊的命令行参数检查来排除释放模式下的一点等待开销。