PAYATHON 2026

nmake Makefile 报错:LNK1104 无法打开 LIBCMT.lib

排障赵师傅

结论

问题通常出在 Makefile 的这行定义:

LIB = lib

Visual Studio 开发环境使用 LIB 环境变量保存默认的库搜索路径,其中包括 LIBCMT.lib 所在的 MSVC 库目录。NMAKE 会将环境变量作为宏读取。如果 Makefile 又把同名宏设为 lib,子进程使用的 LIB 搜索路径就可能被覆盖。

把这个构建工具变量改成其他名称,例如 LIBTOOL,避免占用 LIB。运行 nmake 前,还要通过 VsDevCmd.bat 或 vcvars64.bat 初始化编译环境。

为什么手动执行正常

“Developer Command Prompt for Visual Studio 2019” 启动时会设置一组环境变量,包括:

  • PATH:用于查找 cl.exe、link.exe、lib.exe
  • INCLUDE:用于查找 MSVC 和 Windows SDK 头文件
  • LIB:用于查找 MSVC 和 Windows SDK 库文件
  • VCToolsInstallDir、WindowsSdkDir 等工具链目录变量

使用 /MT 编译后,目标文件需要链接静态 C 运行库 LIBCMT.lib。链接器通常会在 LIB 环境变量指定的目录中查找这个文件。

手动执行命令时,原有的 LIB 环境变量仍然有效。通过 NMAKE 执行时,下面的宏会与该环境变量发生冲突:

LIB = lib

这里本来只是想为 lib.exe 定义一个变量,却占用了工具链已经使用的 LIB。

NMAKE 的工作是展开 Makefile 并启动命令。如果最终使用的可执行文件、参数、环境变量和当前目录完全相同,手动执行和通过 NMAKE 执行的结果也应相同。修改变量名后仍然报错,就需要对比两种执行方式的实际环境和完整命令。

修改 Makefile

将 LIB 改为不会与 Visual Studio 环境变量冲突的名称,例如:

CC = cl
LIBTOOL = lib

CFLAGS = /MT /I include /nologo /D_CRT_SECURE_NO_WARNINGS /W4 /Od /DDEBUG
LIBFLAGS = /OUT:my_library.lib /nologo

SRC_DIR = src
INCLUDE_DIR = include
LIB_NAME = my_library
OBJ = my_library.obj
TARGET = my_program.exe

all: $(TARGET)

$(OBJ): $(SRC_DIR)\$(LIB_NAME).c
	@echo Compiling $(SRC_DIR)\$(LIB_NAME).c
	$(CC) $(CFLAGS) /c $(SRC_DIR)\$(LIB_NAME).c /Fo$(OBJ)

$(LIB_NAME).lib: $(OBJ)
	@echo Creating library $(LIB_NAME).lib
	$(LIBTOOL) $(LIBFLAGS) $(OBJ)

$(TARGET): main.obj $(LIB_NAME).lib
	@echo Linking $(TARGET)
	$(CC) /nologo /Fe$(TARGET) main.obj $(LIB_NAME).lib

main.obj: main.c
	@echo Compiling main.c
	$(CC) $(CFLAGS) /c main.c /Fomain.obj

clean:
	@echo Cleaning up...
	-del /Q $(OBJ) $(LIB_NAME).lib main.obj $(TARGET)

只要 Developer Command Prompt 已正确初始化,通常不必手动设置 /LIBPATH。cl.exe 会调用链接器,链接器再通过 Visual Studio 配置好的 LIB 环境变量查找以下文件:

  • LIBCMT.lib
  • UCRT 库
  • Windows SDK 的 UM 库

这样也不必在 Makefile 中写死 14.29.30133、10.0.19041.0 等版本号。

正确启动构建环境

打开与目标架构匹配的 Visual Studio 命令提示符,例如 x64 Native Tools Command Prompt,然后执行:

nmake /f Makefile

从普通 cmd.exe、脚本或 CI 环境构建时,可以先初始化环境:

call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\Common7\Tools\VsDevCmd.bat" -arch=x64 -host_arch=x64
nmake /f Makefile

也可以调用对应的 vcvars64.bat。安装路径会随 Visual Studio 版本和版本类型变化,应以实际安装目录为准。

这里不能省略 call,否则批处理脚本结束后,后面的 nmake 命令可能不会继续执行。

检查 LIB 是否被覆盖

运行 NMAKE 前,先查看开发环境设置的库路径:

echo %LIB%

正常情况下,输出应包含多个以分号分隔的目录,例如:

...\VC\Tools\MSVC\14.29.30133\lib\x64
...\Windows Kits\10\Lib\10.0.19041.0\ucrt\x64
...\Windows Kits\10\Lib\10.0.19041.0\um\x64

也可以临时在 Makefile 的链接规则中加入:

	@set LIB
	$(CC) /nologo /Fe$(TARGET) main.obj $(LIB_NAME).lib

如果输出为:

LIB=lib

说明 Makefile 中的同名宏确实破坏了 LIB 环境变量。将宏改为 LIBTOOL 后,输出应恢复为实际的库目录列表。

还可以检查当前使用的工具是否来自同一套 Visual Studio 环境:

where cl
where link
where lib

如果仍然出现 LNK1104

先确认文件是否存在:

dir "C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30133\lib\x64\LIBCMT.lib"

如果文件不存在,常见原因有:

  1. Makefile 中写死的 MSVC 版本与实际安装版本不一致。
  2. Visual Studio Installer 中没有安装相应的“使用 C++ 的桌面开发”组件。
  3. 当前使用的是 x86 环境,却指定了 x64 目标,或反过来。
  4. cl.exe、link.exe 和库文件来自不同版本的工具链。
  5. NMAKE 从尚未初始化 Visual Studio 环境的终端或 IDE 中启动。

如需查看链接器搜索了哪些目录,可以临时启用:

	$(CC) /nologo /Fe$(TARGET) main.obj $(LIB_NAME).lib /link /VERBOSE:LIB

/VERBOSE:LIB 会输出库搜索过程,可以用来确认链接器查找 LIBCMT.lib 的路径。

关于 /LIBPATH

确实需要时,可以显式指定库目录:

VCTOOLSLIB = C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30133\lib\x64
UCRTLIB = C:\Program Files (x86)\Windows Kits\10\lib\10.0.19041.0\ucrt\x64
UMLIB = C:\Program Files (x86)\Windows Kits\10\lib\10.0.19041.0\um\x64

$(TARGET): main.obj $(LIB_NAME).lib
	$(CC) /nologo /Fe$(TARGET) main.obj $(LIB_NAME).lib /link \
		/LIBPATH:"$(VCTOOLSLIB)" \
		/LIBPATH:"$(UCRTLIB)" \
		/LIBPATH:"$(UMLIB)"

这种写法依赖具体的安装目录和版本,升级 MSVC 或 Windows SDK 后容易失效。更稳妥的做法是使用 Visual Studio 提供的开发环境脚本,并保留脚本设置的 LIB 环境变量。

问题中的链接命令已经显式提供了 MSVC x64 库目录。如果该目录存在,其中也有 LIBCMT.lib,而且 NMAKE 展开后的命令与手动命令完全相同,那么“手动运行”和“NMAKE 运行”本身无法解释这个错误。此时应启用 /VERBOSE:LIB,并对比 set LIB、where cl、where link 的输出以及实际展开的命令,找出两种执行方式在环境或参数上的差异。

备注:内容仅供参考。