请检查下面我的短代码。
pwrapper.h
#include <stdio.h>
#include <stdarg.h>
extern"C" int mm_printfA(const char *fmt, ...);
extern"C" int mm_printfW(const wchar_t *fmt, ...);
pwrapper.cpp
#include "pwrapper.h"
int mm_printfA(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
int ret = vprintf(fmt, args);
va_end(args);
return ret;
}
int mm_printfW(const wchar_t *fmt, ...)
{
va_list args;
va_start(args, fmt);
int ret = vwprintf(fmt, args);
va_end(args);
return ret;
}
main.cpp
#include "pwrapper.h"
// cl /MT /D _NO_CRT_STDIO_INLINE main.cpp pwrapper.cpp
void main()
{
mm_printfA("What is %d?\n", 123);
}
#if 0
void usedull()
{
vprintf(NULL, NULL);
vwprintf(NULL, NULL);
}
#endif
由于某种原因,我需要使用 _NO_CRT_STDIO_INLINE
编译它,如下所示:
cl /MT /D _NO_CRT_STDIO_INLINE main.cpp pwrapper.cpp
但是链接阶段失败,提示无法解析的外部符号 vwprintf
和 vprintf
。
我发现的一个非常奇怪的解决方法是:启用usedull()
函数体——尽管永远不会被调用,并且通过链接pwrapper.lib
,使用下面的 bb.bat:
@setlocal EnableDelayedExpansion
@set CFLAGS=/D _NO_CRT_STDIO_INLINE
cl /nologo /c /MT %CFLAGS% pwrapper.cpp
@if errorlevel 1 exit /b 4
lib /nologo /out:pwrapper.lib pwrapper.obj
@if errorlevel 1 exit /b 4
cl /nologo /c /MT main.cpp
@if errorlevel 1 exit /b 4
link /nologo main.obj pwrapper.lib
@if errorlevel 1 exit /b 4
嗯,这确实有效,但为什么呢?
这不是一个令人愉快的解决方法,因为每个 exe 项目都需要包含一个“无用的”usedull()
函数。那么,有没有更好的办法呢?
我真的不知道为什么这个解决方法有效,非常欢迎对其进行解释。
==== 一些澄清 ====
我原来的帖子中有两个 main.cpp
。让我分别命名它们以供以后参考,以防有人愿意回答这个奇怪的问题。
-
main.0.cpp
指的是没有 usedull()
的那个。
-
main.1.cpp
指的是带有usedul()
的那个。
在这个问题中,我将 VC++ 标头和库用于应用程序(不适用于内核),并且
- 编译
main.0.cpp
和 main.1.cpp
而不使用 _NO_CRT_STDIO_INLINE
。
- 我总是用
_NO_CRT_STDIO_INLINE
编译 pwrapper.cpp。
在此问题中,让 pwrapper.obj 通过 pwrapper.lib 是否会产生相同的结果。
Please check my short code below.
pwrapper.h
#include <stdio.h>
#include <stdarg.h>
extern"C" int mm_printfA(const char *fmt, ...);
extern"C" int mm_printfW(const wchar_t *fmt, ...);
pwrapper.cpp
#include "pwrapper.h"
int mm_printfA(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
int ret = vprintf(fmt, args);
va_end(args);
return ret;
}
int mm_printfW(const wchar_t *fmt, ...)
{
va_list args;
va_start(args, fmt);
int ret = vwprintf(fmt, args);
va_end(args);
return ret;
}
main.cpp
#include "pwrapper.h"
// cl /MT /D _NO_CRT_STDIO_INLINE main.cpp pwrapper.cpp
void main()
{
mm_printfA("What is %d?\n", 123);
}
#if 0
void usedull()
{
vprintf(NULL, NULL);
vwprintf(NULL, NULL);
}
#endif
For some reason, I need to compile it with _NO_CRT_STDIO_INLINE
, like this:
cl /MT /D _NO_CRT_STDIO_INLINE main.cpp pwrapper.cpp
But link stage fails saying unresolved external symbol vwprintf
and vprintf
.
A very weird workaround I find out is: Enable the usedull()
function body -- although never be called, and, link through pwrapper.lib
, using bb.bat below:
@setlocal EnableDelayedExpansion
@set CFLAGS=/D _NO_CRT_STDIO_INLINE
cl /nologo /c /MT %CFLAGS% pwrapper.cpp
@if errorlevel 1 exit /b 4
lib /nologo /out:pwrapper.lib pwrapper.obj
@if errorlevel 1 exit /b 4
cl /nologo /c /MT main.cpp
@if errorlevel 1 exit /b 4
link /nologo main.obj pwrapper.lib
@if errorlevel 1 exit /b 4
Well, this really works, but why?
This is not a pleasant workaround, because each exe project needs to include a "useless" usedull()
function. So, is there any better way?
I really can't tell why this workaround works, an explanation of it is very welcome.
==== Some Clarification ====
There were two main.cpp
in my original post. Let me name them separately for later reference in case someone would bother to answer this weird question.
main.0.cpp
refers to the one without usedull()
.
main.1.cpp
refers to the one with usedull()
.
In this question, I use VC++ headers and libs for application(not for kernel), and
- I compile
main.0.cpp
and main.1.cpp
without _NO_CRT_STDIO_INLINE
.
- I always compile pwrapper.cpp with
_NO_CRT_STDIO_INLINE
.
Whether having pwrapper.obj go through pwrapper.lib produce the same result in this issue.
发布评论
评论(1)
简而言之:
-D _NO_CRT_STDIO_INLINE
编译pwrapper.cpp
告诉编译器您将提供您自己的vprintf
和vwprintf 实现
在链接时,并main.cpp
没有-D _NO_CRT_STDIO_INLINE
告诉编译器包含vprintf
和vwprintf
的实现,它们在链接时使用,以满足来自usedull
和mm_printfA< 的引用/code>/
mm_printfW
因此,这个特定的组合可以在链接时解析所有未定义的符号。不过,请参阅下文了解更多讨论。
讨论
在
stdio.h
中,vprintf
(我将重点讨论它,但vwprintf
以相同的方式配置)定义如下:请注意,
另外,在
corecrt_stdio_config.h
中是否定义_NO_CRT_STDIO_INLINE
决定了_CRT_STDIO_INLINE
的值;如果已定义,则_CRT_STDIO_INLINE
定义为空,否则定义为__inline
。将这些放在一起,
_NO_CRT_STDIO_INLINE
,这些函数将成为 内联扩展,默认编译(无
/O1
、/O2
、无_NO_CRT_STDIO_INLINE
)以上适用于您正在使用的特定编译和链接调用,就像没有优化后,编译器将简单地将函数体包含在
main.1.obj
的编译中。您可以使用 < 查看此内容代码>dumpbin;运行 dumpbin -symbols main.1.obj | find "| vprintf" 打印:显示
main.1.obj
提供vprintf
作为外部可用符号。检查
pwrapper.obj
,我们得到:显示
vprintf
在此目标文件中未定义,需要在链接时提供。内联扩展的优化
但是,如果我们更改 内联扩展的优化选项,我们得到不同的结果。即使使用第一级优化(
-Ob1
,包含在-O1
和-O2
中),如下所示:使编译器合并主体将
vprintf
直接转化为usedull
,并去掉vprintf
的单独实现,可以使用dumpbin
来确认。因此,正如您现在所期望的那样,尝试将main.1.obj
和pwrapper.obj
链接在一起将再次给出您原来的错误:多个实现?
因此,接下来很明显,使用 -D _NO_CRT_STDIO_INLINE 编译这两个文件将会失败,因为不会实现相关方法。如果编译两者时都没有这个定义怎么办?
如果我们检查目标文件,就会发现两者都定义了
vprintf
:和: 的
符号,在正常情况下,这两个符号都会由于 符号的多个定义和违反一个定义规则。然而,当执行内联扩展时,编译器和链接器会为你提供支持。根据 2:
In short:
pwrapper.cpp
with-D _NO_CRT_STDIO_INLINE
tells the compiler you are going to provide your own implementation ofvprintf
andvwprintf
at link time, andmain.cpp
without-D _NO_CRT_STDIO_INLINE
tells the compiler to include implementations ofvprintf
andvwprintf
which are used at link time to satisfy both the references fromusedull
andmm_printfA
/mm_printfW
so, this particular combination works to resolve all undefined symbols at link time. See below for more discussion however.
Discussion
In
stdio.h
,vprintf
(which I'll focus on, butvwprintf
is configured in the same way) is defined like so:Note that
_NO_CRT_STDIO_INLINE
is defined, this becomes a forward declarationAdditionally, in
corecrt_stdio_config.h
whether_NO_CRT_STDIO_INLINE
is defined determines the value of_CRT_STDIO_INLINE
; if it is defined,_CRT_STDIO_INLINE
is defined as empty, otherwise it is defined as__inline
.Putting these together,
_NO_CRT_STDIO_INLINE
is not defined, these functions will be candidates for inline expansion,Default Compilation (no
/O1
,/O2
, no_NO_CRT_STDIO_INLINE
)The above works with the specific compile and link invocations you are using, as without optimization the compiler will simply include the function body in the compilation of
main.1.obj
. You can see this usingdumpbin
; runningdumpbin -symbols main.1.obj | find "| vprintf"
prints:showing that
main.1.obj
providesvprintf
as an externally available symbol.Checking
pwrapper.obj
, we get:showing that
vprintf
is undefined in this object file, and will need to be provided at link time.Optimisation for Inline Expansion
However, if we change the optimisation option for inline expansion, we get different results. Using even the first level of optimisation (
-Ob1
, included in-O1
and-O2
) like so:causes the compiler to incorporate the body of
vprintf
directly intousedull
, and remove the separate implementation ofvprintf
, which can be confirmed usingdumpbin
. So, as you would now expect, attempting to linkmain.1.obj
andpwrapper.obj
together will once again give your original error:Multiple Implementations?
So, following on from that it is apparent that compiling both files with
-D _NO_CRT_STDIO_INLINE
will fail as there will be no implementations of the relevant methods. What about if both are compiled without this definition?If we check the object files, both have defined symbols for
vprintf
:and:
which under normal circumstances would result in errors due both to multiple definitions of a symbol and violations of the One Definition Rule. However, when performing inline expansion, the compiler and linker have your back. As per 2: