Background
I am using CL and LINK from the command line to build a project composed of native C++, mixed C++/CLI, and safe C++/CLI. I need to target .NET Framework 2.0-3.5 so I am using the VC90 toolchain (I have VS2010 but installed VS2008 C++ express and the SDK for Windows 7 and .NET 3.5 SP1 to get the complete VC90 toolchain).
If I try my script with the VS100 toolchain it works fine but targets .NET v4.0. If I don't use /GL for my native and mixed code it works. I know of these solutions, so don't suggest them. I am trying to understand why this is occurring.
Problem
If I use /GL and /LTCG I get many of the following warnings in the native code:
xxx.cpp(###) : warning C4748: /GS can not protect parameters and local variables from local buffer overrun because optimizations are disabled in function
And the following error in the mixed code:
c:\program files (x86)\microsoft visual studio 9.0\vc\include\vcclr.h(47) : error C4801: Return by reference is not verifiable: definition of returned local not found in basic block
The second one is for PtrToStringChars from a Microsoft header file that allows you to use a String^ as const wchar_t* and is a function that MUST be inlined (and is marked inline). The "return by reference" won't actually be a return when inlined, thus no error.
The issue is that I do use optimizations while compiling! I use the standard /O2 which has the highest level of inlining (/Ob2) and normally allows /GS.
Script
Here is my abridged compilation script
set TARGET=x86
call "%VS90COMNTOOLS%\..\..\VC\vcvarsall.bat" %TARGET%
set CL=/nologo /Zl /Zi /W4 /O2 /Oy- /GL /GS /EHa /MP /D NDEBUG /D _UNICODE /D UNICODE /D INTEGRATED /Fdout\ /Foout\
set LINK=/nologo /LTCG /CLRIMAGETYPE:IJW /MACHINE:%TARGET% /SUBSYSTEM:WINDOWS,6.0 /OPT:REF /OPT:ICF /DEFAULTLIB:msvcrt.lib /DEFAULTLIB:msvcmrt.lib
set CSC=/nologo /w:4 /d:INTEGRATED /o+ /target:module
set CL_NATIVE=/c /FI"stdafx-native.h"
set CL_MIXED=/c /clr /LN /FI"stdafx-mixed.h"
set CL_PURE=/c /clr:safe /LN /GL /FI"stdafx-pure.h"
set NATIVE=a.cpp b.cpp ...
set MIXED=c.cpp d.cpp ...
set PURE=e.cpp f.cpp ...
cl %CL_NATIVE% %NATIVE%
IF %ERRORLEVEL% NEQ 0 EXIT /B %ERRORLEVEL%
cl %CL_MIXED% %MIXED%
IF %ERRORLEVEL% NEQ 0 EXIT /B %ERRORLEVEL%
cl %CL_PURE% %PURE%
IF %ERRORLEVEL% NEQ 0 EXIT /B %ERRORLEVEL%
link /LTCG /NOASSEMBLY /DLL /OUT:"out\x.netmodule" out\*.obj
IF %ERRORLEVEL% NEQ 0 EXIT /B %ERRORLEVEL%
Short Version: Inlining in MSIL takes place during JIT, after the code verifier performs accessibility checking.
Long Version:
Whole-Program Optimization doesn't apply to MSIL, because MSIL always gets optimized across compile units, and even across assemblies (unless optimizations are turned off by debug). I think this is the source of your warnings.
Many optimizations are the responsibility of the JIT, including inlining. Coupled with accessibility checks, this prevents the C++ compiler from running an inlining pass on MSIL. (What happens if a member function using private members get inlined in native C++? Not much. What happens if a member function using private members gets inlined in MSIL into a function that has no access to those private members? The .NET runtime throws an absolute fit. Everything you think you know about compiler settings for inlining just doesn't apply to MSIL.) This is why...
...use of PtrToStringChars is incompatible with /clr:safe because it isn't verifiable. It should be fine with /clr though, and also with /clr:pure since neither of those try to create verifiable assemblies. As mentioned, the compiler is not allowed to inline when compiling in /clr modes. This is the source of your error.
A final problem is creation of a netmodule. I'm pretty sure that mixed-mode code has to be created as full-blown assemblies and not netmodules.
Related
Normally, I use check_cxx_compiler_flag to check whether a given flag is recognized by the currently used compiler. However, I have run into issues with MSVC where some default flags are causing the test to fail due to incompatible flags.
MWE:
cmake_minimum_required(VERSION 3.5)
project(MWE)
include(CheckCXXCompilerFlag)
set(CMAKE_CXX_FLAGS "" CACHE INTERNAL "" FORCE)
set(CMAKE_CXX_FLAGS_DEBUG "" CACHE INTERNAL "" FORCE)
set(CMAKE_CXX_FLAGS_RELEASE "" CACHE INTERNAL "" FORCE)
set(CMAKE_CXX_FLAGS_MINSIZEREL "" CACHE INTERNAL "" FORCE)
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "" CACHE INTERNAL "" FORCE)
check_cxx_compiler_flag("/O2" FLAG_USABLE)
if (NOT FLAG_USABLE)
message(FATAL_ERROR "Flag not usable")
endif()
When this is run on Windows in a developer command prompt, the test fails and the error log contains
Run Build Command(s):C:/Program Files/Microsoft Visual Studio/2022/Enterprise/MSBuild/Current/Bin/amd64/MSBuild.exe cmTC_e3d37.vcxproj /p:Configuration=Debug /p:Platform=x64 /p:VisualStudioVersion=17.0 /v:m && MSBuild version 17.3.1+2badb37d1 for .NET Framework
Microsoft (R) C/C++ Optimizing Compiler Version 19.33.31630 for x64
Copyright (C) Microsoft Corporation. All rights reserved.
cl /c /Zi /W1 /WX- /diagnostics:column /O2 /Ob0 /D _MBCS /D FLAG_USABLE /D "CMAKE_INTDIR=\"Debug\"" /Gm- /RTC1 /MDd /GS /fp:precise /Zc:wchar_t /Zc:forScope /Zc:inline /Fo"cmTC_e3d37.dir\Debug\\" /Fd"cmTC_e3d37.dir\Debug\vc143.pdb" /external:W1 /Gd /TP /errorReport:queue "D:\a\cmake-compiler-flags\cmake-compiler-flags\test\build\CMakeFiles\CMakeTmp\src.cxx"
cl : command line error D8016: '/O2' and '/RTC1' command-line options are incompatible
which clearly shows that the flag is recognized, but it just does not play nicely with the /RTC1 flag, which somehow still gets added to the build.
How can I get rid of these default options (or at least all that are not essential for a build to complete), regardless of the used build configuration, so that my test really checks whether the compiler understands the given flag?
I guess your IDE - Visual Studio is adding this compilation flag. Try running Cmake from command line and the problem should be solved. In order to change VS behavior go to Solution -> Properties -> Configuration Properties -> C/C++ -> Code Generation -> Basic Runtime Checks and change the value of this flag to default or disabled.
I am trying to get a Visual Studio 2017 project to link, but I'm stuck on the following linker error:
LINK : fatal error C1007: unrecognized flag '-Ot' in 'p2'
I've read questions on what the cause could be, but I couldn't come to a solution for my project.
The details are that, due to an external component we have no control over (component A), this Visual Studio 2017 project is forced to use the v14.13 version of the C++ toolchain, i.e. not the latest one (v14.14). However, the latest release of another external precompiled static lib we have no control over either (component B), is built with the v14.14 version (I checked via a dumpbin extract of the debug version of the lib). Switching my project over to the v14.14 toolchain indeed makes the link error go away on component B, but this unfortunately isn't a solution for me due to component A. Taking an earlier version of component B isn't desirable either, since we need the functionality in the latest release...
However, what strikes me, is that the /Ot ("optimize for speed") flag has been around since the middle ages... Why wouldn't v14.13 recognize it? Or is it just an (awkwardly manifested) matter of a mismatched obj file layout due to the version differences? And, probably related, what does the 'p2' stand for anyway?
Update
I've checked the linker output by using the /verbose flag, and all seems normal (3600 lines of Searching <lib>, Found <function>, Referenced in <obj> and Loaded <lib>).
Right up until the end that is, where I get the following 6 lines:
1> Searching C:\PathToExternalLib\TheirStatic.lib:
1> Found UsedFunctionName
1> Referenced in MyOwnStatic.lib(MyOwnCompileUnit.obj)
1>LINK : fatal error C1007: unrecognized flag '-Ot' in 'p2'
1>LINK : fatal error LNK1257: code generation failed
1>Done building project "MyProject.vcxproj" -- FAILED.
And that's that.
When visiting the command line setting of the link properties of the project, the only thing listed is (broken up in separate lines for convenience):
/OUT:"MyProject.dll"
/MANIFEST
/NXCOMPAT
/PDB:"MyProject.pdb"
/DYNAMICBASE "C:\PathToMyStatic.lib"
/IMPLIB:"MyProject.lib"
/DLL
/MACHINE:X64
/PGD:"MyProject.pgd"
/MANIFESTUAC:"level='asInvoker' uiAccess='false'"
/ManifestFile:"MyProject.prm.intermediate.manifest"
/ERRORREPORT:PROMPT
/NOLOGO
/LIBPATH:"C:\PathToExternalStaticLib"
/LIBPATH:"C:\PathToAnotherExternalStaticLib"
/TLBID:1
So no trace of any -Ot flag there as well...?
I had this problem. LINK : fatal error C1007: unrecognized flag '-Ot' in 'p2'
while building a project with Visual Studio 2015.
I had to rebuild any library or sub library the project linked to which were built with Visual Studio 2017.
Once I rebuild the dependent libraries with Visual Studio 2015 the first project was able to link against them.
project
--------\
---------lib1(unable to rebuild lib1 until its dependencies were also rebuilt with VS2015
--------------\lib_linked_by_lib1_which_was_build_with_VS2017_and_had_to_be_rebuilt
--------------\another_lib_which_had_to_be_rebuilt_for_lib1_with_VS2015
--------\lib2
--------\lib3
Visual Studio 2017 comes with full CMake integration. To learn about this combination, I was starting with this basic sample:
# CMakeLists.txt
cmake_minimum_required(VERSION 3.8)
project(foo)
add_executable(foo foo.cpp)
and
// foo.cpp
int main() {}
This properly generates build scripts, and compiles and links with no issues. That was easy.
Trying to set compiler options, on the other hand, turned out to be anything but trivial. In my case I was attempting to set the warning level to 4.
The obvious solution
add_compile_options("/W4")
didn't pan out as expected. The command line passed to the compiler now contains both /W4 (as intended) as well as /W3 (picked up from somewhere else), producing the following warning:
cl : Command line warning D9025: overriding '/W3' with '/W4'
To work around this, I would need to replace any incompatible compiler option(s) instead of just adding one. CMake does not provide any immediate support for this, and the standard solution (as this Q&A suggests) seems to be:
if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]")
string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4")
endif()
This, however, has two issues:
It sets the global CMAKE_CXX_FLAGS, applying to all C++ targets. This may not be intended (not an issue for me right now).
It doesn't scale. For every compiler option to add, you would have to read up on incompatible options, and manually strip those first. This will inevitably fail1.
My question is two-fold:
Where does the CMake integration pick up default settings from, and can this be controlled?
How do you set compiler options in general? (If this is too broad a topic, I'd be happy for help on setting the warning level only.)
1 Incidentally, the solution I replicated fails to account for the /Wall option, that is incompatible with /W4 as well.
The default settings for the compiler are picked up from standard module files located in the Modules directory of the CMake installation. The actual module file used depends on both the platform and the compiler. E.g., for Visual Studio 2017, CMake will load the default settings from the file Windows-MSVC.cmake and language specific settings from Windows-MSVC-C.cmake or Windows-MSVC-CXX.cmake.
To inspect the default settings, create a file CompilerOptions.cmake in the project directory with the following contents:
# log all *_INIT variables
get_cmake_property(_varNames VARIABLES)
list (REMOVE_DUPLICATES _varNames)
list (SORT _varNames)
foreach (_varName ${_varNames})
if (_varName MATCHES "_INIT$")
message(STATUS "${_varName}=${${_varName}}")
endif()
endforeach()
Then initialize the CMAKE_USER_MAKE_RULES_OVERRIDE variable in your CMakeLists.txt:
# CMakeLists.txt
cmake_minimum_required(VERSION 3.8)
set (CMAKE_USER_MAKE_RULES_OVERRIDE "${CMAKE_CURRENT_LIST_DIR}/CompilerOptions.cmake")
project(foo)
add_executable(foo foo.cpp)
When the project is configured upon opening the directory using Open Folder in Visual Studio 2017, the following information will be shown in the IDE's output window:
...
-- CMAKE_CXX_FLAGS_DEBUG_INIT= /MDd /Zi /Ob0 /Od /RTC1
-- CMAKE_CXX_FLAGS_INIT= /DWIN32 /D_WINDOWS /W3 /GR /EHsc
-- CMAKE_CXX_FLAGS_MINSIZEREL_INIT= /MD /O1 /Ob1 /DNDEBUG
-- CMAKE_CXX_FLAGS_RELEASE_INIT= /MD /O2 /Ob2 /DNDEBUG
-- CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT= /MD /Zi /O2 /Ob1 /DNDEBUG
...
So the warning setting /W3 is picked up from the CMake variable CMAKE_CXX_FLAGS_INIT which then applies to all CMake targets generated in the project.
To control the warning level on the CMake project or target level, one can alter the CMAKE_CXX_FLAGS_INIT variable in the CompilerOptions.cmake by adding the following lines to the file:
if (MSVC)
# remove default warning level from CMAKE_CXX_FLAGS_INIT
string (REGEX REPLACE "/W[0-4]" "" CMAKE_CXX_FLAGS_INIT "${CMAKE_CXX_FLAGS_INIT}")
endif()
The warning flags can then be controlled by setting the target compile options in CMakeLists.txt:
...
add_executable(foo foo.cpp)
target_compile_options(foo PRIVATE "/W4")
For most CMake projects it makes sense to control the default compiler options in a rules override file instead of manually tweaking variables like CMAKE_CXX_FLAGS.
When making changes to the CompilerOptions.cmake file, it is necessary to recreate the build folder. When using Visual Studio 2017 in Open Folder mode, choose the command Cache ... -> Delete Cache Folders from the CMake menu and then Cache ... -> Generate from the CMake menu to recreate the build folder.
Turning my comment into an answer
CMake does come with some compiler switches preset. For visual studio those are mainly standard link libraries, warning levels, optimization levels, exception handling, debug information and platform specific defines.
What you now have to differentiate when you want to change a CMake generated compiler settings are the following use cases:
Additional compiler flags CMake does not define vs. changing CMake's preset settings
Project default settings vs. project user defined settings
So let's discuss common solutions for those cases.
User changes/adds to Project/CMake Compiler Flags Defaults
The standard way would be to modify the cached compiler flags variables by using tools shipped with CMake like cmake-gui and ccmake.
To achieve this in Visual Studio you would have to:
CMake / Cache / View CMakeCache
Manually change e.g. CMAKE_CXX_FLAGS to /Wall
CMakeCache.txt
//Flags used by the compiler during all build types.
CMAKE_CXX_FLAGS:STRING= /DWIN32 /D_WINDOWS /Wall /GR /EHsc
CMake / Cache / Generate
Or you preset the CMAKE_CXX_FLAGS cache variable via a CMakeSettings.json file:
CMake / Change CMake Settings
Force the cache entry with -DCMAKE_CXX_FLAGS:STRING=... in cmakeCommandArgs
CMakeSettings.json
{
// See https://go.microsoft.com//fwlink//?linkid=834763 for more information about this file.
"configurations": [
{
"name": "x86-Debug (all warnings)",
"generator": "Visual Studio 15 2017",
"configurationType": "Debug",
"buildRoot": "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}",
"cmakeCommandArgs": "-DCMAKE_CXX_FLAGS:STRING=\"/DWIN32 /D_WINDOWS /Wall /GR /EHsc\"",
"buildCommandArgs": "-m -v:minimal"
}
]
}
If you deliver this CMakeSettings.json file with your CMake project it gets permanent
Project changes to CMake Compiler Flags Defaults
If you want to keep most of CMake's compiler flags in place, #sakra's answer is definitely the way to go.
For my VS projects I've put the CXX flag settings into a toolchain file coming with the project itself. Mainly to freeze those settings and don't have a dependency the CMake version used or any environment variables set.
Taking the example from above that would look like:
VS2017Toolchain.cmake
set(CMAKE_CXX_FLAGS "/DWIN32 /D_WINDOWS /Wall /GR /EHsc" CACHE INTERNAL "")
CMakeSettings.json
{
// See https://go.microsoft.com//fwlink//?linkid=834763 for more information about this file.
"configurations": [
{
"name": "x86-Debug (all warnings)",
"generator": "Visual Studio 15 2017",
"configurationType": "Debug",
"buildRoot": "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}",
"cmakeCommandArgs": "-DCMAKE_TOOLCHAIN_FILE:FILEPATH=\"${projectDir}\\VS2017Toolchain.cmake\"",
"buildCommandArgs": "-m -v:minimal"
}
]
}
References
Visual C++ Team Blog: CMake support in Visual Studio – the Visual Studio 2017 RC update
set diagnostics:caret from CMakeLists.txt
Is Cmake set variable recursive?
Passing compiler options cmake
In CMake 3.15, CMake introduced a fix for this MSVC-specific warning:
cl : Command line warning D9025: overriding '/W3' with '/W4'
and the compiler warning flags (like /W3) are no longer automatically added. So by upgrading to CMake 3.15 or greater, this warning should no longer appear. From the docs:
When using MSVC-like compilers in CMake 3.14 and below, warning flags like /W3 are added to CMAKE_<LANG>_FLAGS by default. This is problematic for projects that want to choose a different warning level programmatically. In particular, it requires string editing of the CMAKE_<LANG>_FLAGS variables with knowledge of the CMake builtin defaults so they can be replaced.
CMake 3.15 and above prefer to leave out warning flags from the value of CMAKE_<LANG>_FLAGS by default.
Along with this fix, CMake introduced policy CMP0092, which allows you to switch back to the OLD behavior (adding the warning flags by default) if necessary.
I get the linker error fatal error C1107: could not find assembly 'platform.winmd': please specify the assembly search path using /AI or by setting the LIBPATH environment variable when I try to compile a C++/CX program on the command line.
The error is the same after I followed the instructions on this page: https://msdn.microsoft.com/en-us/library/dn769142.aspx (to summarize: run cl /ZW /EHsc source.cpp from the Developer Command Prompt for VS2015)
I also tried running vcvarsall.bat x86 store from the Developer Command Prompt for VS2015 but I still get the same error (the same error also happens when running vcvarsall.bat x86 store from a plain command prompt).
UPDATE: Apparently this bug has been fixed in VS2015 Update 1, I have not been able to test myself yet though.
As it turns out some command line parameters are missing from the documentation mentioned in the question, here is the full command line required to compile a small program:
cl /ZW
/ZW:nostdlib
/D WINAPI_FAMILY=WINAPI_FAMILY_APP
/D __WRL_NO_DEFAULT_LIB__
/Gm-
/EHsc
/MDd
/FU"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\lib\store\references\platform.winmd"
/FU"C:\Program Files (x86)\Windows Kits\10\References\Windows.Foundation.FoundationContract\1.0.0.0\Windows.Foundation.FoundationContract.winmd"
/FU"C:\Program Files (x86)\Windows Kits\10\References\Windows.Foundation.UniversalApiContract\1.0.0.0\Windows.Foundation.UniversalApiContract.winmd"
smurf.cpp
/link /SUBSYSTEM:CONSOLE
Where smurf.cpp contains:
using namespace Platform;
int main(Platform::Array<Platform::String^>^ args)
{
Platform::Details::Console::WriteLine("This is a C++/CX program.");
}
Will successfully print:
C:\Users\Mikael>smurf.exe
This is a C++/CX program.
I'm compiling a 64 bit c++ project on visual studio pro 2010 and I'm testing the size of pointers. It shows that sizeof(any pointer) such as void*, char* etc is 4 bytes. This seems wrong for a 64 bit system. However, sizeof(UINT_PTR) is 8 which is correct for 64 bit.
Here's my preprocessor definition: _WIN64;_AMD64;_WINDOWS;_DEBUG;_USRDLL;
Target machine is MachineX64 (/MACHINE:X64).
Is there someplace where the sizeof() of things is defined? Otherwise, how can I find out why it's giving me the wrong size?
Thanks.
Edit:
Compiler command line:
/Zi /nologo /W4 /WX- /Od /D "_WIN64" /D "_AMD64" /D "_WINDOWS" /D "_DEBUG" /D "_USRDLL" /D "_WINDLL" /D "_MBCS" /D "_AFXDLL" /Gm /EHsc /RTC1 /GS /fp:precise /Zc:wchar_t /Zc:forScope /Fp"x64\Debug\S2TalkerDLL.pch" /Fa"x64\Debug\" /Fo"x64\Debug\" /Fd"x64\Debug\vc100.pdb" /Gd /errorReport:queue
Linker command line:
/OUT:"C:\Users\xxx\Documents\Visual Studio 2010\Projects\S2TalkerDLL\x64\Debug\S2TalkerDLL.dll" /INCREMENTAL /NOLOGO /DLL "WINMM.lib" /DEF:".\S2TalkerDLL.def" /MANIFEST /ManifestFile:"x64\Debug\S2TalkerDLL.dll.intermediate.manifest" /ALLOWISOLATION /MANIFESTUAC:"level='asInvoker' uiAccess='false'" /DEBUG /PDB:"C:\Users\xxx\Documents\Visual Studio 2010\Projects\S2TalkerDLL\x64\Debug\S2TalkerDLL.pdb" /SUBSYSTEM:WINDOWS /PGD:"C:\Users\xxx\Documents\Visual Studio 2010\Projects\S2TalkerDLL\x64\Debug\S2TalkerDLL.pgd" /TLBID:1 /DYNAMICBASE /NXCOMPAT /MACHINE:X64 /ERRORREPORT:QUEUE
Hold on a minute, are you getting sizeof() values from Intellisense or from the compiler? That is, have you tried compiling and running something like this:
#include <cstdio>
int main()
{
::printf("%d\n", sizeof(void*));
return 0;
}
I ask because it appears from the screenshots you posted that you're using Intellisense to determine what sizeof() evaluates to.
Intellisense is something completely separate from the compiler. It's just a tool that attempts to parse your (likely incomplete) code for autocompletion purposes. It may or may not be aware of specific compiler/linker settings like /MACHINE:X64, so sizeof(void*) may give incorrect values.
On the other hand, UINT_PTR is defined via #ifdef macros and integral types like unsigned long or unsigned __int64, which in this case Intellisense will be able to give correct sizes.
The compiler and linker are the programs that actually generates the code and thus has the final say on what sizeof() actually evaluates to. You should compile and run the above code snippet and see the actual output. I get 8 under 64-bits and 4 under 32-bits. Intellisense is only a convenience tool and does not have any bearing on the final executable output.
If you don't have immediate access to a 64-bit machine to test the above code, you can instead try compiling this:
template<unsigned long Size> struct TestSize; // #1
template<> struct TestSize<8> {}; // #2
int main()
{
// If sizeof(void*) == 8, then #2 will be used.
// Otherwise, #1 will be used. Since #1 hasn't
// been completely defined, this line will fail
// to compile if sizeof(void*) != 8.
TestSize<sizeof(void*)>();
}
Thanks to template magic (i.e. template specialization), the above snippet should compile only when sizeof(void*) is equal to 8. You don't need to run the resulting executable; the fact that it compiles means that sizeof(void*) == 8.