Unverified Commit 92d94da8 authored by Jake Stine's avatar Jake Stine Committed by GitHub
Browse files

msvc: fixup for printf behavior and makefile/sln improvements (#184)

* msvc: forcedincludes added to fix/workaround MSC specific issues

 - printf interception allows redirecting printf() into the windows debugger "OutputDebugStringA" function.
 - forcedincludes allow doing this in a way that does not require any changes to the regular codebase or macro obfuscation for non-windows developers.

* vcxproj: add MSC_SOURCES_H to the makefile to improve MSVC IDE search results.

* vcxproj: add makefile-generated lutro_sources.props

We can use CI steps to guard against errant human-modification of this file in the future, if warranted.
parent 75c2b205
......@@ -387,14 +387,16 @@ all: $(TARGET)
vcxproj: msbuild/lutro_sources.props
msbuild/lutro_sources.props: Makefile Makefile.common
@printf "msbuild/lutro_sources.props: Generating with %d sources, %d includes\n" $(words $(SOURCES_C)) $(words $(MSVC_SOURCES_H))
@> msbuild/lutro_sources.props echo '<?xml version="1.0" encoding="utf-8"?>'
@>> msbuild/lutro_sources.props echo '<!-- AUTO_GENERATED FILE - generated by 'make vcxproj' -->'
@>> msbuild/lutro_sources.props echo '<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">'
@>> msbuild/lutro_sources.props echo ' <ItemGroup>'
@>> msbuild/lutro_sources.props printf " <ClCompile Include=\"../%s\" />\n" $(SOURCES_C)
@>> msbuild/lutro_sources.props echo ' </ItemGroup>'
@[[ -z "$(SOURCES_H)" ]] || \
>> msbuild/lutro_sources.props echo ' <ItemGroup>' \
>> msbuild/lutro_sources.props printf " <ClInclude Include=\"../%s\" />\n" $(SOURCES_H) \
@[[ -z "$(MSVC_SOURCES_H)" ]] || \
>> msbuild/lutro_sources.props echo ' <ItemGroup>' && \
>> msbuild/lutro_sources.props printf " <ClInclude Include=\"../%s\" />\n" $(MSVC_SOURCES_H) && \
>> msbuild/lutro_sources.props echo ' </ItemGroup>'
@>> msbuild/lutro_sources.props echo ' <ItemDefinitionGroup>'
@>> msbuild/lutro_sources.props echo ' <ClCompile>'
......
......@@ -209,3 +209,10 @@ VORBIS_SOURCES_C := \
$(CORE_DIR)/deps/vorbis/vorbisenc.c \
$(CORE_DIR)/deps/vorbis/vorbisfile.c \
$(CORE_DIR)/deps/vorbis/window.c
# special item for Visual Studio vcxproj generation, to allow MSVC to search contents of headers from IDE.
# (it's ok to be inclusive here, the headers in this list do not need to accurately reflect those actually
# used by each project)
MSVC_SOURCES_H = $(shell find -iname "*.h")
......@@ -3,12 +3,14 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31005.135
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lutro", "msbuild\lutro.vcxproj", "{18CEB57F-9D09-4FAB-AEEE-08B094055423}"
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lutro_libretro", "msbuild\lutro.vcxproj", "{18CEB57F-9D09-4FAB-AEEE-08B094055423}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lua", "msbuild\lua.vcxproj", "{A5743DF4-3D23-4D9F-A01E-6903F364601B}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vorbis", "msbuild\vorbis.vcxproj", "{B4A07901-6615-4460-B907-5F0A2838AC34}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "msw_fixups", "msw_fixups", "{B603A35A-DE46-4B9D-BBD3-42888CD0ADA9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
......
# custom rule to match icystdlib and gists from github.com/jstine35
[*.{c,cpp,h,inl}]
indent_style = tab
insert_final_newline = true
indent_size = 4
#pragma once
#define PLATFORM_MSW 1
#pragma once
#if !defined(REDEFINE_PRINTF)
# if defined(_MSC_VER)
# define REDEFINE_PRINTF 1
# endif
#endif
#if !defined(__verify_fmt)
# if defined(_MSC_VER)
# define __verify_fmt(fmtpos, vapos)
# else
# define __verify_fmt(fmtpos, vapos) __attribute__ ((format (printf, fmtpos, vapos)))
# endif
#endif
// On Windows it's handy to have printf() automatically redirect itself into the Visual Studio Output window
// and the only sane way to accomplish that seems to be by replacing printf() with a macro.
#if REDEFINE_PRINTF
#include <stdio.h> // must include before macro definition
#if defined(__cplusplus)
# include <cstdint>
# define _extern_c extern "C"
#else
# include <stdint.h>
# define _extern_c
#endif
_extern_c int _fi_redirect_printf (const char* fmt, ...);
_extern_c int _fi_redirect_vfprintf (FILE* handle, const char* fmt, va_list args);
_extern_c int _fi_redirect_fprintf (FILE* handle, const char* fmt, ...);
_extern_c int _fi_redirect_puts (char const* _Buffer);
_extern_c int _fi_redirect_fputs (char const* _Buffer, FILE* _Stream);
_extern_c intmax_t _fi_redirect_fwrite(void const* src, size_t, size_t, FILE* fp);
#define printf(fmt, ...) _fi_redirect_printf (fmt, ## __VA_ARGS__)
#define fprintf(fp, fmt, ...) _fi_redirect_fprintf (fp, fmt, ## __VA_ARGS__)
#define vfprintf(fp, fmt, args) _fi_redirect_vfprintf(fp, fmt, args)
#define puts(msg) _fi_redirect_puts (msg);
#define fputs(msg, fp) _fi_redirect_fputs (msg, fp);
#define fwrite(buf, sz, nx, fp) _fi_redirect_fwrite (buf, sz, nx, fp)
#undef _extern_c
#endif
#pragma once
// ENABLE_PRINTF_VERIFY_CHECK_ON_MSVC
//
// Microsoft doesn't provide a way to annoate custom-rolled user printf() functions for static analysis.
// Well, ok -- it does provide _Printf_format_string_ but that's only effective when using the Code Analysis
// tool which is both incrediously slow and generates a hundred false positives for every valid issue. In
// other words, useless.
//
// The upside is MSVC does perform automatic lightweight static analysis on printf() builtin functions as part
// of the regular build process. So I did a horrible thing and I built a macro that fake-calls MSVC's snprintf()
// on the input string, implemented as the second half of a nullified conditional such that it never _actually_
// get run. All tokens from the format macro do get pasted twice as part of this process, though only one of
// the pastes is reachable. Behavior of the __COUNTER__ macro in this case would change. No other program
// behavior should be affected. --jstine
//
#if !defined(ENABLE_PRINTF_VERIFY_CHECK_ON_MSVC)
# if defined(_MSC_VER)
# define ENABLE_PRINTF_VERIFY_CHECK_ON_MSVC 1
# else
# define ENABLE_PRINTF_VERIFY_CHECK_ON_MSVC 0
# endif
#endif
#if !defined(VERIFY_PRINTF_ON_MSVC)
# if ENABLE_PRINTF_VERIFY_CHECK_ON_MSVC
# define VERIFY_PRINTF_ON_MSVC(...) (0 && snprintf(nullptr, 0, ## __VA_ARGS__))
# if defined(__cplusplus)
// in C++ we can allow macros that optionally take a format parameter, typical of macros that
// translate an empty format into a newline or such.
static inline int snprintf(const char* fmt, int len) {
return 0;
}
# endif
# else
# define VERIFY_PRINTF_ON_MSVC(...) (0)
# endif
#endif
......@@ -18,6 +18,9 @@
<Project>{b4a07901-6615-4460-b907-5f0a2838ac34}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ClCompile Include="msw-printf-stdout.cpp" />
</ItemGroup>
<!-- TODO : we could add a target that performs error checking and print a friendly message to the user -->
<Import Project="lutro_sources.props" />
<PropertyGroup Label="Globals">
......@@ -64,6 +67,10 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
</ClCompile>
</ItemDefinitionGroup>
<Import Project="lutro_build.props" />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
......
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ClCompile Include=".././libretro.c" />
<ClCompile Include=".././lutro.c" />
<ClCompile Include=".././runtime.c" />
<ClCompile Include=".././image.c" />
<ClCompile Include=".././graphics.c" />
<ClCompile Include=".././input.c" />
<ClCompile Include=".././audio.c" />
<ClCompile Include=".././decoder.c" />
<ClCompile Include=".././event.c" />
<ClCompile Include=".././keyboard.c" />
<ClCompile Include=".././sound.c" />
<ClCompile Include=".././filesystem.c" />
<ClCompile Include=".././system.c" />
<ClCompile Include=".././timer.c" />
<ClCompile Include=".././lutro_math.c" />
<ClCompile Include=".././joystick.c" />
<ClCompile Include=".././mouse.c" />
<ClCompile Include=".././lutro_window.c" />
<ClCompile Include=".././painter.c" />
<ClCompile Include=".././libretro-common/formats/png/rpng_decode.c" />
<ClCompile Include=".././libretro-common/formats/png/rpng_fbio.c" />
<ClCompile Include=".././libretro-common/file/file_path.c" />
<ClCompile Include=".././libretro-common/features/features_cpu.c" />
<ClCompile Include=".././libretro-common/audio/conversion/float_to_s16.c" />
<ClCompile Include=".././libretro-common/audio/conversion/s16_to_float.c" />
<ClCompile Include=".././libretro-common/audio/audio_mix.c" />
<ClCompile Include=".././libretro-common/compat/compat_posix_string.c" />
<ClCompile Include=".././libretro-common/memmap/memmap.c" />
<ClCompile Include=".././libretro-common/compat/compat_strcasestr.c" />
<ClCompile Include=".././libretro-common/compat/compat_strl.c" />
<ClCompile Include=".././libretro-common/encodings/encoding_utf.c" />
<ClCompile Include=".././libretro-common/streams/file_stream.c" />
<ClCompile Include=".././libretro-common/vfs/vfs_implementation.c" />
<ClCompile Include=".././libretro-common/compat/fopen_utf8.c" />
<ClCompile Include=".././deps/luautf8/lutf8lib.c" />
<ClCompile Include=".././deps/zlib/ioapi.c" />
<ClCompile Include=".././deps/zlib/compress.c" />
<ClCompile Include=".././deps/zlib/deflate.c" />
<ClCompile Include=".././deps/physfs/physfs.c" />
<ClCompile Include="msw-printf-stdout.cpp">
<Filter>msw_fixups</Filter>
</ClCompile>
<ClCompile Include=".././deps/zlib/adler32.c">
<Filter>zip</Filter>
</ClCompile>
<ClCompile Include=".././deps/zlib/crc32.c">
<Filter>zip</Filter>
</ClCompile>
<ClCompile Include=".././deps/physfs/physfs_archiver_7z.c">
<Filter>physfs</Filter>
</ClCompile>
<ClCompile Include=".././deps/physfs/physfs_archiver_dir.c">
<Filter>physfs</Filter>
</ClCompile>
<ClCompile Include=".././deps/physfs/physfs_archiver_grp.c">
<Filter>physfs</Filter>
</ClCompile>
<ClCompile Include=".././deps/physfs/physfs_archiver_hog.c">
<Filter>physfs</Filter>
</ClCompile>
<ClCompile Include=".././deps/physfs/physfs_archiver_iso9660.c">
<Filter>physfs</Filter>
</ClCompile>
<ClCompile Include=".././deps/physfs/physfs_archiver_mvl.c">
<Filter>physfs</Filter>
</ClCompile>
<ClCompile Include=".././deps/physfs/physfs_archiver_qpak.c">
<Filter>physfs</Filter>
</ClCompile>
<ClCompile Include=".././deps/physfs/physfs_archiver_slb.c">
<Filter>physfs</Filter>
</ClCompile>
<ClCompile Include=".././deps/physfs/physfs_archiver_unpacked.c">
<Filter>physfs</Filter>
</ClCompile>
<ClCompile Include=".././deps/physfs/physfs_archiver_vdf.c">
<Filter>physfs</Filter>
</ClCompile>
<ClCompile Include=".././deps/physfs/physfs_archiver_wad.c">
<Filter>physfs</Filter>
</ClCompile>
<ClCompile Include=".././deps/physfs/physfs_archiver_zip.c">
<Filter>physfs</Filter>
</ClCompile>
<ClCompile Include=".././deps/physfs/physfs_byteorder.c">
<Filter>physfs</Filter>
</ClCompile>
<ClCompile Include=".././deps/physfs/physfs_platform_os2.c">
<Filter>physfs</Filter>
</ClCompile>
<ClCompile Include=".././deps/physfs/physfs_platform_posix.c">
<Filter>physfs</Filter>
</ClCompile>
<ClCompile Include=".././deps/physfs/physfs_platform_qnx.c">
<Filter>physfs</Filter>
</ClCompile>
<ClCompile Include=".././deps/physfs/physfs_platform_unix.c">
<Filter>physfs</Filter>
</ClCompile>
<ClCompile Include=".././deps/physfs/physfs_platform_windows.c">
<Filter>physfs</Filter>
</ClCompile>
<ClCompile Include=".././deps/physfs/physfs_unicode.c">
<Filter>physfs</Filter>
</ClCompile>
<ClCompile Include=".././deps/zlib/gzclose.c">
<Filter>zip</Filter>
</ClCompile>
<ClCompile Include=".././deps/zlib/gzlib.c">
<Filter>zip</Filter>
</ClCompile>
<ClCompile Include=".././deps/zlib/gzread.c">
<Filter>zip</Filter>
</ClCompile>
<ClCompile Include=".././deps/zlib/gzwrite.c">
<Filter>zip</Filter>
</ClCompile>
<ClCompile Include=".././deps/zlib/inffast.c">
<Filter>zip</Filter>
</ClCompile>
<ClCompile Include=".././deps/zlib/inflate.c">
<Filter>zip</Filter>
</ClCompile>
<ClCompile Include=".././deps/zlib/inftrees.c">
<Filter>zip</Filter>
</ClCompile>
<ClCompile Include=".././deps/zlib/trees.c">
<Filter>zip</Filter>
</ClCompile>
<ClCompile Include=".././deps/zlib/uncompr.c">
<Filter>zip</Filter>
</ClCompile>
<ClCompile Include=".././deps/zlib/unzip.c">
<Filter>zip</Filter>
</ClCompile>
<ClCompile Include=".././deps/zlib/zutil.c">
<Filter>zip</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Filter Include="msw_fixups">
<UniqueIdentifier>{319cb567-2e5b-45dc-8f6c-8dc0d58def3c}</UniqueIdentifier>
</Filter>
<Filter Include="zip">
<UniqueIdentifier>{51c9e21e-88aa-4263-9e34-5690f8d63a2e}</UniqueIdentifier>
</Filter>
<Filter Include="physfs">
<UniqueIdentifier>{f4123f3d-989b-4f9b-9a6d-fee64abd84b1}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="fi-printf-redirect.h">
<Filter>msw_fixups</Filter>
</ClInclude>
<ClInclude Include="fi-verify-printf-msvc.h">
<Filter>msw_fixups</Filter>
</ClInclude>
<ClInclude Include="fi-msw-buildconf.h">
<Filter>msw_fixups</Filter>
</ClInclude>
</ItemGroup>
</Project>
\ No newline at end of file
......@@ -5,6 +5,10 @@
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(SolutionDir)/msbuild;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<ForcedIncludeFiles>fi-msw-buildconf.h;%(ForcedIncludeFiles)</ForcedIncludeFiles>
<ForcedIncludeFiles>%(ForcedIncludeFiles);fi-verify-printf-msvc.h</ForcedIncludeFiles>
<ForcedIncludeFiles>%(ForcedIncludeFiles);fi-printf-redirect.h</ForcedIncludeFiles>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
......
This diff is collapsed.
#if PLATFORM_MSW
#define NOMINMAX
#include <Windows.h>
#include "fi-printf-redirect.h"
#include <ios>
#include <io.h>
#include <fcntl.h>
#include <algorithm>
bool msw_IsDebuggerPresent()
{
return ::IsDebuggerPresent() == TRUE;
}
void msw_OutputDebugString(const char* fmt)
{
::OutputDebugStringA(fmt);
}
void msw_OutputDebugStringV(const char* fmt, va_list args)
{
// vsnprintf has what is arguably unexpected behavior: it returns the length of the formatted string,
// not the # of chars written. It also doesn't null-terminate strings if it runs out of buffer space.
char buf[2048];
int sizeof_buf = sizeof(buf);
int len = vsnprintf(buf, sizeof_buf, fmt, args);
buf[sizeof_buf-1] = '\0'; // in case vsnprintf overflowed.
::OutputDebugStringA(buf);
if (len >= sizeof_buf) {
// Things piped to stderr shouldn't be excessively verbose anyway
::OutputDebugStringA("\n");
::OutputDebugStringA("(*OutputDebugStringA*) previous message was truncated, see stderr console for full content");
}
}
#if REDEFINE_PRINTF
# undef printf
# undef vfprintf
# undef fprintf
# undef puts
# undef fputs
# undef fwrite
extern "C" {
int _fi_redirect_printf(const char* fmt, ...)
{
va_list argptr;
va_start(argptr, fmt);
auto result = _fi_redirect_vfprintf(stdout, fmt, argptr);
va_end(argptr);
return result;
}
int _fi_redirect_fprintf(FILE* handle, const char* fmt, ...)
{
va_list argptr;
va_start(argptr, fmt);
auto result = _fi_redirect_vfprintf(handle, fmt, argptr);
va_end(argptr);
return result;
}
int _fi_redirect_vfprintf(FILE* handle, const char* fmt, va_list args)
{
int result;
if (1) {
// flush stdout before writing stderr, otherwise the context of stderr will be misleading.
if (handle == stderr) {
fflush(stdout);
}
va_list argptr;
va_copy(argptr, args);
result = vfprintf(handle, fmt, argptr);
va_end(argptr);
}
if (handle == stdout || handle == stderr)
{
if (msw_IsDebuggerPresent()) {
va_list argptr;
va_copy(argptr, args);
msw_OutputDebugStringV(fmt, argptr);
va_end(argptr);
}
}
return result;
}
int _fi_redirect_puts(char const* buffer) {
// since puts appends a newline and OutputDebugString doesn't have a formatted version,
// it's easier to just fall back on our printf implementation.
return _fi_redirect_printf("%s\n", buffer);
}
int _fi_redirect_fputs(char const* buffer, FILE* handle) {
int result = fputs(buffer, handle);
if (handle == stdout || handle == stderr)
{
if (msw_IsDebuggerPresent()) {
msw_OutputDebugString(buffer);
}
}
return result;
}
intmax_t _fi_redirect_fwrite(void const* buffer, size_t size, size_t nelem, FILE* handle)
{
auto result = fwrite(buffer, size, nelem, handle);
if (handle == stdout || handle == stderr)
{
if (msw_IsDebuggerPresent()) {
char mess[1024];
auto nsize = size * nelem;
auto* chbuf = (const char*)buffer;
while(nsize) {
auto chunksize = std::min(nsize, 1023ULL);
memcpy(mess, chbuf, chunksize);
mess[chunksize] = 0;
msw_OutputDebugString(mess);
nsize -= chunksize;
chbuf += chunksize;
}
}
}
return result;
}
}
#endif
#endif
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment