如何在Windows 7中编译NetHack?

4

我喜欢NetHack并想为了好玩而稍微搞一下源代码。 在这之前,我希望能够轻松地将其编译出来,但是我却无法做到。

我从这里下载了源代码,并按照这里的说明进行操作,但是没有成功。

最终我得到了以下结果。

C:\nethack-3.4.3\src>mingw32-make -f Makefile.gcc install
creating directory o
gcc -c -mms-bitfields -I../include  -g -DWIN32CON -oo/makedefs.o ../util/makedefs.c
gcc -c -mms-bitfields -I../include  -g -DWIN32CON -DDLB   -oo/monst.o  ../src/monst.c
gcc -c -mms-bitfields -I../include  -g -DWIN32CON -DDLB   -oo/objects.o      ../src/objects.c
..\util\makedefs -v
Makefile.gcc:655: recipe for target '../include/date.h' failed
mingw32-make: *** [../include/date.h] Error -1073741819

我看了一下它所说的那行代码,但它并没有给我任何提示。我注意到在include目录中创建的date.h文件总是为空,但这对我也没有什么帮助。我阅读了Install.nt README,说明似乎很清晰。然而,由于我没有更改任何内容,我不知道为什么会编译失败...
我认为自己是一个有能力的程序员,但当涉及到makefile和将C代码编译成可执行应用程序时,我几乎一无所知,所以我很迷失。我下载并安装了MinGW...所有东西,我的意思是当我运行MinGW安装程序时没有留下任何未安装的东西。
我在这里做错了什么?
编辑:由于提到了date.h:
#
#  date.h should be remade every time any of the source or include
#  files is modified.
#

$(INCL)/date.h $(OPTIONS_FILE): $(U)makedefs.exe
    $(subst /,\,$(U)makedefs -v)

我注意到它似乎在调用OPTIONS_FILE,但这个部分是被注释了的。我将取消注释并查看结果。

#$(OPTIONS_FILE): $(U)makedefs.exe
#$(subst /,\,$(U)makedefs -v)
编辑2 那不起作用。我需要手动创建/更新date.h文件吗?如果是这样,我应该放什么?听起来像是一个谷歌的问题... 编辑3 我发现this适用于旧版本,尝试进行更改,但也没有起作用... 编辑4 有人提到Makedefs似乎是导致崩溃的原因。我找到了似乎引起问题的C函数:
void
do_date()
{
    long clocktim = 0;
    char *c, cbuf[60], buf[BUFSZ];
    const char *ul_sfx;

    filename[0]='\0';
#ifdef FILE_PREFIX
    Strcat(filename,file_prefix);
#endif
    Sprintf(eos(filename), INCLUDE_TEMPLATE, DATE_FILE);
    if (!(ofp = fopen(filename, WRTMODE))) {
        perror(filename);
        exit(EXIT_FAILURE);
    }
    Fprintf(ofp,"/*\tSCCS Id: @(#)date.h\t3.4\t2002/02/03 */\n\n");
    Fprintf(ofp,Dont_Edit_Code);

#ifdef KR1ED
    (void) time(&clocktim);
    Strcpy(cbuf, ctime(&clocktim));
#else
    (void) time((time_t *)&clocktim);
    Strcpy(cbuf, ctime((time_t *)&clocktim));
#endif
    for (c = cbuf; *c; c++) if (*c == '\n') break;
    *c = '\0';  /* strip off the '\n' */
    Fprintf(ofp,"#define BUILD_DATE \"%s\"\n", cbuf);
    Fprintf(ofp,"#define BUILD_TIME (%ldL)\n", clocktim);
    Fprintf(ofp,"\n");
#ifdef NHSTDC
    ul_sfx = "UL";
#else
    ul_sfx = "L";
#endif
    Fprintf(ofp,"#define VERSION_NUMBER 0x%08lx%s\n",
        version.incarnation, ul_sfx);
    Fprintf(ofp,"#define VERSION_FEATURES 0x%08lx%s\n",
        version.feature_set, ul_sfx);
#ifdef IGNORED_FEATURES
    Fprintf(ofp,"#define IGNORED_FEATURES 0x%08lx%s\n",
        (unsigned long) IGNORED_FEATURES, ul_sfx);
#endif
    Fprintf(ofp,"#define VERSION_SANITY1 0x%08lx%s\n",
        version.entity_count, ul_sfx);
    Fprintf(ofp,"#define VERSION_SANITY2 0x%08lx%s\n",
        version.struct_sizes, ul_sfx);
    Fprintf(ofp,"\n");
    Fprintf(ofp,"#define VERSION_STRING \"%s\"\n", version_string(buf));
    Fprintf(ofp,"#define VERSION_ID \\\n \"%s\"\n",
        version_id_string(buf, cbuf));
    Fprintf(ofp,"\n");
#ifdef AMIGA
    {
    struct tm *tm = localtime((time_t *) &clocktim);
    Fprintf(ofp,"#define AMIGA_VERSION_STRING ");
    Fprintf(ofp,"\"\\0$VER: NetHack %d.%d.%d (%d.%d.%d)\"\n",
        VERSION_MAJOR, VERSION_MINOR, PATCHLEVEL,
        tm->tm_mday, tm->tm_mon+1, tm->tm_year+1900);
    }
#endif
    Fclose(ofp);
    return;
}

此外,我应该提到的是,在编译过程中到达这一点时,立即会出现这个图像: enter image description here 因此,我们已经缩小了问题的范围(我想是这样),问题在于makedefs助手程序正在破坏事物,所以接下来的步骤就是要找出原因是什么?
编辑5: 有人建议在编译Makedefs.c时使用特殊参数。 我查看了Makefile以找出编译发生的位置,我想我已经找到了,但我不知道这里到底发生了什么。
$(U)makedefs.exe: $(MAKEOBJS)
    @$(link) $(LFLAGSU) -o$@ $(MAKEOBJS)

$(O)makedefs.o: $(CONFIG_H) $(INCL)/monattk.h $(INCL)/monflag.h \
     $(INCL)/objclass.h $(INCL)/monsym.h $(INCL)/qtext.h \
     $(INCL)/patchlevel.h $(U)makedefs.c $(O)obj.tag
    $(cc) $(CFLAGSU) -o$@ $(U)makedefs.c

我知道$(*)是一个变量或者Makefile中的等价变量。 $(U)指向$(UTIL)/,而$(UTIL)指向../util$(MAKEOBJS)指向$(O)makedefs.o $(O)monst.o $(O)objects.o$(O)指向$(OBJ)/,而$(OBJ)指向o,这样$(O)makedefs.o就相当于o/makedefs.o,考虑到我观察到的半成功运行的行为,这是有意义的(在大冻结之前编译了几个文件)。
总之,$(link)指向gcc$(LFLAGSU)指向$(LFLAGSBASEC),而$(LFLAGSBASEC)指向$(linkdebug)$(linkdebug)指向-g$(CONFIG_H)指向一大堆头文件:
CONFIG_H = $(INCL)/config.h $(INCL)/config1.h $(INCL)/tradstdc.h \
           $(INCL)/global.h $(INCL)/coord.h $(INCL)/vmsconf.h \
           $(INCL)/system.h $(INCL)/unixconf.h $(INCL)/os2conf.h \
           $(INCL)/micro.h $(INCL)/pcconf.h $(INCL)/tosconf.h \
           $(INCL)/amiconf.h $(INCL)/macconf.h $(INCL)/beconf.h \
           $(INCL)/ntconf.h $(INCL)/nhlan.h

$(INCL)指向../include$(CFLAGSU)指向$(CFLAGSBASE) $(WINPFLAG)$(CFLAGSBASE)指向-c $(cflags) -I$(INCL) $(WINPINC) $(cdebug) $(cflags)指向-mms-bitfields $(WINPINC)指向-I$(WIN32) $(WIN32)指向../win/win32 $(cdebug)指向-g $(WINPFLAG)指向-DTILES -DMSWIN_GRAPHICS -D_WIN32_IE=0x0400 . . . 就是这样。我认为这就是我需要修改的内容,以使其与RossRidge提到的-D_USE_32BIT_TIME_T兼容。

然而,既然我已经走到这一步,我确实想了解一些这些东西的含义。看着第一行,我看到了$(U)makedefs.exe :。对我来说,这似乎是编译输出文件目标的声明?是这样吗?此外,在$(link) $(LFLAGSU)之前和-o$之后的@的含义是什么?在-o之后的$的含义是什么?
无论如何,我想尝试我所想出的方法,并查看是否有效。...添加-D_USE_32BIT_TIME_TWINPFLAG中并没有起作用。

最终(大概)编辑: 原来RossRidge的建议使用-D_USE_32BIT_TIME_T标志是正确的。我的错误是把它放错了位置。如果你查看盒子里附带的Makefile.gcc文件,看一下第165行(在IF语句中)。你想在那个末尾加上-D_USE_32BIT_TIME_T。但你也要在176行的ELSE末尾加上它。所以整个代码块看起来会像这样(变化不是很大,但如果你不这样做并且在我的情况下运行,仍然足以导致崩溃):

################################################
#                                              #
# Nothing below here should have to be changed.#
#                                              #
################################################

ifeq  "$(GRAPHICAL)" "Y"
WINPORT  = $(O)tile.o $(O)mhaskyn.o $(O)mhdlg.o \
    $(O)mhfont.o $(O)mhinput.o $(O)mhmain.o $(O)mhmap.o \
    $(O)mhmenu.o $(O)mhmsgwnd.o $(O)mhrip.o $(O)mhsplash.o \
    $(O)mhstatus.o $(O)mhtext.o $(O)mswproc.o $(O)winhack.o
WINPFLAG   = -DTILES -DMSWIN_GRAPHICS -D_WIN32_IE=0x0400 -D_USE_32BIT_TIME_T
NHRES   = $(O)winres.o
WINPINC = -I$(WIN32)
WINPHDR = $(WIN32)/mhaskyn.h $(WIN32)/mhdlg.h $(WIN32)/mhfont.h \
    $(WIN32)/mhinput.h $(WIN32)/mhmain.h $(WIN32)/mhmap.h \
    $(WIN32)/mhmenu.h $(WIN32)/mhmsg.h $(WIN32)/mhmsgwnd.h \
    $(WIN32)/mhrip.h $(WIN32)/mhstatus.h \
    $(WIN32)/mhtext.h $(WIN32)/resource.h $(WIN32)/winMS.h
WINPLIBS =  -lcomctl32 -lwinmm
else
WINPORT = $(O)nttty.o
WINPFLAG= -DWIN32CON -D_USE_32BIT_TIME_T
WINPHDR =
NHRES   = $(O)console.o
WINPINC =
WINPLIBS = -lwinmm
endif

找到 date.h 应该是一个不错的开始 :) - Martin James
@MartinJames:我的理解是,在makefile尝试创建date.h时发生了错误。 - Harry Johnston
@Will: 问题是在 util\makedefs.cctime(&clocktim) 返回的是NULL,所以 Strcpy(cbuf, ctime(&clocktim)); 导致程序崩溃。不幸的是,我没有时间去找出原因...... 如果谷歌上没有容易的答案,也许在这里提问会更好。通过硬编码一个字符串来解决它!搜索并替换所有 ctime(...) 的副本为 "Mon Aug 4 16:00:00 2014"。例如:Strcpy(cbuf,"Mon Aug 4 16:00:00 2014"); - indiv
@indiv 如果我找不到,我就跟你一起运行。那是一个很好的发现,谢谢。 - Will
尝试使用-D_USE_32BIT_TIME_T编译。这会告诉MinGW头文件使用32位的time_t - Ross Ridge
显示剩余9条评论
1个回答

4
(I不知道我是否应该得到答案的荣誉,因为如果没有Harry Johnston和indiv的评论,我可能不会意识到问题是什么,但我会尝试将评论扩展为完整的答案。)
正如indiv所解释的那样,makedefs.exe崩溃的原因是ctime返回NULL。通常情况下,您不会期望ctime这样做,因此我们需要检查文档以了解在什么情况下它会返回错误。由于使用MinGW进行编译,因此我们需要查看Microsoft的Visual C++文档。这是因为MinGW没有自己的C运行时,它只使用Microsoft的。
查看Visual Studio C运行时库参考条目 ctime ,我们发现:

返回值

一个指向字符字符串结果的指针。如果:

  • 时间表示1970年1月1日午夜之前的日期,UTC。
  • 如果使用_ctime32_wctime32,并且时间表示2038年1月19日03:14:07之后的日期。
  • 如果使用_ctime64_wctime64,并且时间表示3000年12月31日23:59:59之后的日期,UTC。

NULL将被返回。

现在可以相当安全地假设原帖作者没有将系统时钟设置为远未来或久远的时间。那么为什么ctime会使用错误的时间呢?Harry Johnston指出,代码使用long而不是time_t来存储时间值。这并不太令人惊讶。Nethack是非常古老的代码,最初Unix将其时间存储在long值中,使用time_t来存储时间值是后来才出现的。Nethack在其活跃开发期的很大一部分时间里都必须处理没有time_t的旧系统。
这就解释了为什么Nethack源代码使用了错误的类型,但并没有完全解释为什么将错误的值传递给ctime。我们没有看到ctime本身的返回值描述,只有_ctime32_ctime64,这给了我们一个线索。如果time_t是一个64位类型,那么使用long将会是一个问题。在Windows上,long只有32位,这意味着ctime被传递了一个既包含时间值又包含随机位的数字。继续阅读文档可以确认这一点,并给出了可能的解决方案:

ctime是一个内联函数,它计算为_ctime64,而time_t等效于__time64_t。如果您需要强制编译器将time_t解释为旧的32位time_t,则可以定义_USE_32BIT_TIME_T。这样做会导致ctime计算为_ctime32。这不是推荐的方法,因为在2038年1月18日之后,您的应用程序可能会失败,并且在64位平台上不允许。

现在,由于定义_USE_32BIT_TIME_T仅影响C头文件的编译方式,并且MinGW提供自己的C头文件,因此可能MinGW不支持此功能。快速检查MinGW的time.h发现它支持,因此使用-D_USE_32BIT_TIME_T编译选项来定义此宏是简单的解决方案。


问题的描述非常好,点赞。然而,可能有一个值得扩展的潜在复杂性,尽管在这种情况下它并不是一个问题:随Windows一起提供的C运行时(msvcrt.dll)没有正式文档,因为第三方应用程序实际上不应该再使用它了。最接近的是Visual Studio 6的文档,因为在VS6中编译的应用程序确实使用msvcrt.dll,并且需要保持向后兼容性。但是,VS6文档省略了对"_ctime32"和"_ctime64"的所有提及,并且没有指定"time_t"的大小。 - Harry Johnston
所以,对于msvcrt.dll,我不确定当前VS文档中给出的细节是否准确。获取此信息的另一种选择是MinGW头文件,正如您所描述的那样;这些文件可能基于来自VS6头文件的信息,结合必要的试验和/或逆向工程。 (当然,仍然存在某些风险,即这些特定函数可能没有被仔细研究,因此头文件可能不准确,但显然情况并非如此。从我听到的情况来看,它们通常相当可靠。) - Harry Johnston
官方上说,MinGW头文件仅基于微软的公共文档。微软会在每个操作系统发布时更新MSVCRT.DLL的新版本,因此它会获得一些旧的VS6 DLL没有的功能,例如64位时间值。 MinGW的time.h曾经有版本宏,允许与旧的VS6 DLL兼容,但现在它们似乎已经失效了。目前,MinGW似乎假定使用较新版本的MSCVRT.DLL之一。 - Ross Ridge
啊,这可以解释为什么 msvcrt.dll 导出了 ctime 以及 _ctime32_ctime64。很可能后两者由 Windows 自身(和 MinGW)使用,而前者是为了 VS6 的兼容性。(在更新头文件时仍然需要一些猜测,即使只是猜测特定操作系统的运行时基于哪个版本的 Visual Studio。):-) - Harry Johnston

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