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.
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
main.0.cpp
and main.1.cpp
without _NO_CRT_STDIO_INLINE
._NO_CRT_STDIO_INLINE
.Whether having pwrapper.obj go through pwrapper.lib produce the same result in this issue.
In short:
pwrapper.cpp
with -D _NO_CRT_STDIO_INLINE
tells the compiler you are going to provide your own implementation of vprintf
and vwprintf
at link time, andmain.cpp
without -D _NO_CRT_STDIO_INLINE
tells the compiler to include implementations of vprintf
and vwprintf
which are used at link time to satisfy both the references from usedull
and mm_printfA
/mm_printfW
so, this particular combination works to resolve all undefined symbols at link time. See below for more discussion however.
In stdio.h
, vprintf
(which I'll focus on, but vwprintf
is configured in the same way) is defined like so:
_Check_return_opt_
_CRT_STDIO_INLINE int __CRTDECL vprintf(
_In_z_ _Printf_format_string_ char const* const _Format,
va_list _ArgList
)
#if defined _NO_CRT_STDIO_INLINE
;
#else
{
return _vfprintf_l(stdout, _Format, NULL, _ArgList);
}
#endif
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,/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 using dumpbin
; running dumpbin -symbols main.1.obj | find "| vprintf"
prints:
01D 00000000 SECT8 notype () External | vprintf
showing that main.1.obj
provides vprintf
as an externally available symbol.
Checking pwrapper.obj
, we get:
00A 00000000 UNDEF notype () External | vprintf
showing that vprintf
is undefined in this object file, and will need to be provided at link time.
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:
cl -c -Ob1 main.1.cpp
causes the compiler to incorporate the body of vprintf
directly into usedull
, and remove the separate implementation of vprintf
, which can be confirmed using dumpbin
. So, as you would now expect, attempting to link main.1.obj
and pwrapper.obj
together will once again give your original error:
pwrapper.obj : error LNK2019: unresolved external symbol vwprintf referenced in function mm_printfW
pwrapper.obj : error LNK2019: unresolved external symbol vprintf referenced in function mm_printfA
main.exe : fatal error LNK1120: 2 unresolved externals
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
:
01D 00000000 SECT8 notype () External | vprintf
and:
01A 00000000 SECT7 notype () External | vprintf
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:
Rather than expand an inline function defined in a header file, the compiler may create it as a callable function in more than one translation unit. The compiler marks the generated function for the linker to prevent one-definition-rule (ODR) violations.