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.exeINCLUDE:用于查找 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"
如果文件不存在,常见原因有:
- Makefile 中写死的 MSVC 版本与实际安装版本不一致。
- Visual Studio Installer 中没有安装相应的“使用 C++ 的桌面开发”组件。
- 当前使用的是 x86 环境,却指定了 x64 目标,或反过来。
cl.exe、link.exe和库文件来自不同版本的工具链。- 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 的输出以及实际展开的命令,找出两种执行方式在环境或参数上的差异。
备注:内容仅供参考。
