locked
Why do I get the errors below when I use the /ENTRY option for the linker in a C++ project? RRS feed

  • Question

  • Why do I get the errors below for the following simple code in C++?

    #include<iostream>
    struct A{ int i; A(): i(1){ std::cout << i << '\n'; } };
    
    A a;
    
    int main()
    {
    	return 0;
    }

    Debug/x86 with linker options /SUBSYSTEM = Console (/SUBSYSTEM:CONSOLE) and /ENTRY = main

    1>------ Build started: Project: CPP, Configuration: Debug Win32 ------
    1>main.obj : error LNK2019: unresolved external symbol ___CxxFrameHandler3 referenced in function __unwindfunclet$??$?6U?$char_traits@D@std@@@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@0@AAV10@D@Z$2
    1>MSVCRTD.lib(init.obj) : error LNK2019: unresolved external symbol __CrtDbgReport referenced in function __CRT_RTC_INIT
    1>MSVCRTD.lib(init.obj) : error LNK2019: unresolved external symbol __CrtDbgReportW referenced in function __CRT_RTC_INITW
    1>MSVCRTD.lib(error.obj) : error LNK2019: unresolved external symbol _strcpy_s referenced in function "void __cdecl _RTC_StackFailure(void *,char const *)" (?_RTC_StackFailure@@YAXPAXPBD@Z)
    1>MSVCRTD.lib(error.obj) : error LNK2019: unresolved external symbol _strcat_s referenced in function "void __cdecl _RTC_StackFailure(void *,char const *)" (?_RTC_StackFailure@@YAXPAXPBD@Z)
    1>MSVCRTD.lib(error.obj) : error LNK2019: unresolved external symbol ___stdio_common_vsprintf_s referenced in function __vsprintf_s_l
    1>MSVCRTD.lib(pdblkup.obj) : error LNK2019: unresolved external symbol __wmakepath_s referenced in function "int __cdecl GetPdbDllPathFromFilePath(wchar_t const *,wchar_t *,unsigned int)" (?GetPdbDllPathFromFilePath@@YAHPB_WPA_WI@Z)
    1>MSVCRTD.lib(pdblkup.obj) : error LNK2019: unresolved external symbol __wsplitpath_s referenced in function "int __cdecl GetPdbDllPathFromFilePath(wchar_t const *,wchar_t *,unsigned int)" (?GetPdbDllPathFromFilePath@@YAHPB_WPA_WI@Z)
    1>MSVCRTD.lib(pdblkup.obj) : error LNK2019: unresolved external symbol _wcscpy_s referenced in function "int __cdecl GetPdbDllPathFromFilePath(wchar_t const *,wchar_t *,unsigned int)" (?GetPdbDllPathFromFilePath@@YAHPB_WPA_WI@Z)
    1>MSVCRTD.lib(pdblkup.obj) : error LNK2019: unresolved external symbol ___vcrt_GetModuleFileNameW referenced in function "struct HINSTANCE__ * __cdecl GetPdbDll(void)" (?GetPdbDll@@YAPAUHINSTANCE__@@XZ)
    1>MSVCRTD.lib(pdblkup.obj) : error LNK2019: unresolved external symbol ___vcrt_GetModuleHandleW referenced in function "struct HINSTANCE__ * __cdecl GetPdbDll(void)" (?GetPdbDll@@YAPAUHINSTANCE__@@XZ)
    1>MSVCRTD.lib(pdblkup.obj) : error LNK2019: unresolved external symbol ___vcrt_LoadLibraryExW referenced in function "struct HINSTANCE__ * __cdecl GetPdbDll(void)" (?GetPdbDll@@YAPAUHINSTANCE__@@XZ)
    1>MSVCRTD.lib(chandler4_noexcept.obj) : error LNK2019: unresolved external symbol _terminate referenced in function __except_handler4_noexcept
    1>MSVCRTD.lib(chandler4gs.obj) : error LNK2019: unresolved external symbol __except_handler4_common referenced in function __except_handler4
    1>C:\Users\jabel\Documents\Visual Studio 2017\Projects\Assemblies\Debug\CPP.exe : fatal error LNK1120: 14 unresolved externals
    1>Done building project "CPP.vcxproj" -- FAILED.

    Debug/x86 with linker options /SUBSYSTEM = Not set and /ENTRY = main

    1>------ Build started: Project: CPP, Configuration: Debug Win32 ------
    1>main.obj : error LNK2019: unresolved external symbol ___CxxFrameHandler3 referenced in function __unwindfunclet$??$?6U?$char_traits@D@std@@@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@0@AAV10@D@Z$2
    1>MSVCRTD.lib(init.obj) : error LNK2019: unresolved external symbol __CrtDbgReport referenced in function __CRT_RTC_INIT
    1>MSVCRTD.lib(init.obj) : error LNK2019: unresolved external symbol __CrtDbgReportW referenced in function __CRT_RTC_INITW
    1>MSVCRTD.lib(error.obj) : error LNK2019: unresolved external symbol _strcpy_s referenced in function "void __cdecl _RTC_StackFailure(void *,char const *)" (?_RTC_StackFailure@@YAXPAXPBD@Z)
    1>MSVCRTD.lib(error.obj) : error LNK2019: unresolved external symbol _strcat_s referenced in function "void __cdecl _RTC_StackFailure(void *,char const *)" (?_RTC_StackFailure@@YAXPAXPBD@Z)
    1>MSVCRTD.lib(error.obj) : error LNK2019: unresolved external symbol ___stdio_common_vsprintf_s referenced in function __vsprintf_s_l
    1>MSVCRTD.lib(pdblkup.obj) : error LNK2019: unresolved external symbol __wmakepath_s referenced in function "int __cdecl GetPdbDllPathFromFilePath(wchar_t const *,wchar_t *,unsigned int)" (?GetPdbDllPathFromFilePath@@YAHPB_WPA_WI@Z)
    1>MSVCRTD.lib(pdblkup.obj) : error LNK2019: unresolved external symbol __wsplitpath_s referenced in function "int __cdecl GetPdbDllPathFromFilePath(wchar_t const *,wchar_t *,unsigned int)" (?GetPdbDllPathFromFilePath@@YAHPB_WPA_WI@Z)
    1>MSVCRTD.lib(pdblkup.obj) : error LNK2019: unresolved external symbol _wcscpy_s referenced in function "int __cdecl GetPdbDllPathFromFilePath(wchar_t const *,wchar_t *,unsigned int)" (?GetPdbDllPathFromFilePath@@YAHPB_WPA_WI@Z)
    1>MSVCRTD.lib(pdblkup.obj) : error LNK2019: unresolved external symbol ___vcrt_GetModuleFileNameW referenced in function "struct HINSTANCE__ * __cdecl GetPdbDll(void)" (?GetPdbDll@@YAPAUHINSTANCE__@@XZ)
    1>MSVCRTD.lib(pdblkup.obj) : error LNK2019: unresolved external symbol ___vcrt_GetModuleHandleW referenced in function "struct HINSTANCE__ * __cdecl GetPdbDll(void)" (?GetPdbDll@@YAPAUHINSTANCE__@@XZ)
    1>MSVCRTD.lib(pdblkup.obj) : error LNK2019: unresolved external symbol ___vcrt_LoadLibraryExW referenced in function "struct HINSTANCE__ * __cdecl GetPdbDll(void)" (?GetPdbDll@@YAPAUHINSTANCE__@@XZ)
    1>MSVCRTD.lib(chandler4_noexcept.obj) : error LNK2019: unresolved external symbol _terminate referenced in function __except_handler4_noexcept
    1>MSVCRTD.lib(chandler4gs.obj) : error LNK2019: unresolved external symbol __except_handler4_common referenced in function __except_handler4
    1>C:\Users\jabel\Documents\Visual Studio 2017\Projects\Assemblies\Debug\CPP.exe : fatal error LNK1120: 14 unresolved externals
    1>Done building project "CPP.vcxproj" -- FAILED.
    

    Debug/x64 with linker options /SUBSYSTEM = Console (/SUBSYSTEM:CONSOLE) and /ENTRY = main

    1>------ Build started: Project: CPP, Configuration: Debug x64 ------
    1>main.obj : error LNK2001: unresolved external symbol __CxxFrameHandler3
    1>MSVCRTD.lib(gshandlereh.obj) : error LNK2001: unresolved external symbol __CxxFrameHandler3
    1>MSVCRTD.lib(init.obj) : error LNK2019: unresolved external symbol _CrtDbgReport referenced in function _CRT_RTC_INIT
    1>MSVCRTD.lib(init.obj) : error LNK2019: unresolved external symbol _CrtDbgReportW referenced in function _CRT_RTC_INITW
    1>MSVCRTD.lib(error.obj) : error LNK2019: unresolved external symbol strcpy_s referenced in function "void __cdecl _RTC_StackFailure(void *,char const *)" (?_RTC_StackFailure@@YAXPEAXPEBD@Z)
    1>MSVCRTD.lib(error.obj) : error LNK2019: unresolved external symbol strcat_s referenced in function "void __cdecl _RTC_StackFailure(void *,char const *)" (?_RTC_StackFailure@@YAXPEAXPEBD@Z)
    1>MSVCRTD.lib(error.obj) : error LNK2019: unresolved external symbol __stdio_common_vsprintf_s referenced in function _vsprintf_s_l
    1>MSVCRTD.lib(error.obj) : error LNK2001: unresolved external symbol __C_specific_handler_noexcept
    1>MSVCRTD.lib(pdblkup.obj) : error LNK2019: unresolved external symbol _wmakepath_s referenced in function "int __cdecl GetPdbDllPathFromFilePath(wchar_t const *,wchar_t *,unsigned __int64)" (?GetPdbDllPathFromFilePath@@YAHPEB_WPEA_W_K@Z)
    1>MSVCRTD.lib(pdblkup.obj) : error LNK2019: unresolved external symbol _wsplitpath_s referenced in function "int __cdecl GetPdbDllPathFromFilePath(wchar_t const *,wchar_t *,unsigned __int64)" (?GetPdbDllPathFromFilePath@@YAHPEB_WPEA_W_K@Z)
    1>MSVCRTD.lib(pdblkup.obj) : error LNK2019: unresolved external symbol wcscpy_s referenced in function "int __cdecl GetPdbDllPathFromFilePath(wchar_t const *,wchar_t *,unsigned __int64)" (?GetPdbDllPathFromFilePath@@YAHPEB_WPEA_W_K@Z)
    1>MSVCRTD.lib(pdblkup.obj) : error LNK2019: unresolved external symbol __vcrt_GetModuleFileNameW referenced in function "struct HINSTANCE__ * __cdecl GetPdbDll(void)" (?GetPdbDll@@YAPEAUHINSTANCE__@@XZ)
    1>MSVCRTD.lib(pdblkup.obj) : error LNK2019: unresolved external symbol __vcrt_GetModuleHandleW referenced in function "struct HINSTANCE__ * __cdecl GetPdbDll(void)" (?GetPdbDll@@YAPEAUHINSTANCE__@@XZ)
    1>MSVCRTD.lib(pdblkup.obj) : error LNK2019: unresolved external symbol __vcrt_LoadLibraryExW referenced in function "struct HINSTANCE__ * __cdecl GetPdbDll(void)" (?GetPdbDll@@YAPEAUHINSTANCE__@@XZ)
    1>C:\Users\jabel\Documents\Visual Studio 2017\Projects\Assemblies\x64\Debug\CPP.exe : fatal error LNK1120: 13 unresolved externals
    1>Done building project "CPP.vcxproj" -- FAILED.
    

    Debug/x64 with linker options /SUBSYSTEM = Not set and /ENTRY = main

    1>main.obj : error LNK2001: unresolved external symbol __CxxFrameHandler3
    1>MSVCRTD.lib(gshandlereh.obj) : error LNK2001: unresolved external symbol __CxxFrameHandler3
    1>MSVCRTD.lib(init.obj) : error LNK2019: unresolved external symbol _CrtDbgReport referenced in function _CRT_RTC_INIT
    1>MSVCRTD.lib(init.obj) : error LNK2019: unresolved external symbol _CrtDbgReportW referenced in function _CRT_RTC_INITW
    1>MSVCRTD.lib(error.obj) : error LNK2019: unresolved external symbol strcpy_s referenced in function "void __cdecl _RTC_StackFailure(void *,char const *)" (?_RTC_StackFailure@@YAXPEAXPEBD@Z)
    1>MSVCRTD.lib(error.obj) : error LNK2019: unresolved external symbol strcat_s referenced in function "void __cdecl _RTC_StackFailure(void *,char const *)" (?_RTC_StackFailure@@YAXPEAXPEBD@Z)
    1>MSVCRTD.lib(error.obj) : error LNK2019: unresolved external symbol __stdio_common_vsprintf_s referenced in function _vsprintf_s_l
    1>MSVCRTD.lib(error.obj) : error LNK2001: unresolved external symbol __C_specific_handler_noexcept
    1>MSVCRTD.lib(pdblkup.obj) : error LNK2019: unresolved external symbol _wmakepath_s referenced in function "int __cdecl GetPdbDllPathFromFilePath(wchar_t const *,wchar_t *,unsigned __int64)" (?GetPdbDllPathFromFilePath@@YAHPEB_WPEA_W_K@Z)
    1>MSVCRTD.lib(pdblkup.obj) : error LNK2019: unresolved external symbol _wsplitpath_s referenced in function "int __cdecl GetPdbDllPathFromFilePath(wchar_t const *,wchar_t *,unsigned __int64)" (?GetPdbDllPathFromFilePath@@YAHPEB_WPEA_W_K@Z)
    1>MSVCRTD.lib(pdblkup.obj) : error LNK2019: unresolved external symbol wcscpy_s referenced in function "int __cdecl GetPdbDllPathFromFilePath(wchar_t const *,wchar_t *,unsigned __int64)" (?GetPdbDllPathFromFilePath@@YAHPEB_WPEA_W_K@Z)
    1>MSVCRTD.lib(pdblkup.obj) : error LNK2019: unresolved external symbol __vcrt_GetModuleFileNameW referenced in function "struct HINSTANCE__ * __cdecl GetPdbDll(void)" (?GetPdbDll@@YAPEAUHINSTANCE__@@XZ)
    1>MSVCRTD.lib(pdblkup.obj) : error LNK2019: unresolved external symbol __vcrt_GetModuleHandleW referenced in function "struct HINSTANCE__ * __cdecl GetPdbDll(void)" (?GetPdbDll@@YAPEAUHINSTANCE__@@XZ)
    1>MSVCRTD.lib(pdblkup.obj) : error LNK2019: unresolved external symbol __vcrt_LoadLibraryExW referenced in function "struct HINSTANCE__ * __cdecl GetPdbDll(void)" (?GetPdbDll@@YAPEAUHINSTANCE__@@XZ)
    1>C:\Users\jabel\Documents\Visual Studio 2017\Projects\Assemblies\x64\Debug\CPP.exe : fatal error LNK1120: 13 unresolved externals
    1>Done building project "CPP.vcxproj" -- FAILED.
    
     

    For the other combinations where the /ENTRY option is not specified the code compiles, links and executes normally for both x86 and x64. I have the feeling that this is a bug in VS2017 version 15.9.7, but I would appreciate some confirmation on this.

    I know that the user doesn't need to specify the /ENTRY option in a C++ program, as the compiler already requires the name main for the function to be invoked by the C run-time library (see [basic.start] in the C++ standard). Nevertheless, I would say that the code should compile when the user provides a correct name for this /ENTRY option.

    Wednesday, November 27, 2019 2:10 PM

Answers

  • NOTE, any library reference in this post implicitly includes any debug and static version of the library. I.e. MSVCRT.lib also includes MSVCRTD.lib, LIBCMT.lib and LIBCMTD.lib.

    The reason why you get these linker errors is simple. By removing the startup code you remove the only linker reference to certain object files in MSVCRT.lib. The actual entrypoint name that the linker defines isn't main, but actually mainCRTStartup. (There is some variation on this, if you use wmain as the entrypoint then it uses wmainCRTStartup, if you use WinMain then it uses WinMainCRTStartup).

    The VC++ compiler only ever specifies MSVCRT.lib as the default library so it uses some trickery to pull in references to vcruntime.lib and ucrt.lib too. If you look in the startup code that comes along with Visual Studio you should find the source initializers.cpp and this is where it has the references to vcruntime.lib and ucrt.lib. However this is only referenced via the startup code. This is the source in which the C and C++ initialisers and terminators are declared. These would be referenced in __scrt_common_main_seh in exe_common.inl. But since the global initialisation is only ever run through the default entry point, the C and C++ initialisation never occurs and so the ucrt.lib and vcruntime.lib are never referenced.

    Oh, and it is because there is no global initialisation that std::cout will not work. Part of the initialisation is setting up the stdin, stdout and stderr variables. If you look in the source file cin.cpp, cout.cpp and cerr.cpp which is also part of the source provided by Visual Studio, you should find how cin, cout and cerr are initialised. These rely on the __acrt_iob_func that stdin, stdout and stderr are defined as. The io table that __acrt_iob_func is also only properly initialised in the global variable initialisation which is skipped when you change the entry point.

    You would have to reference ucrt.lib and vcruntime.lib explicitly. But in general don't be surprised if things don't work. You skipped all of the global initialisation and are actually running more akin to a freestanding environment now rather than a hosted environment. This means that less features are guaranteed to work.


    This is a signature. Any samples given are not meant to have error checking or show best practices. They are meant to just illustrate a point. I may also give inefficient code or introduce some problems to discourage copy/paste coding. This is because the major point of my posts is to aid in the learning process.


    • Proposed as answer by RLWA32 Wednesday, November 27, 2019 3:12 PM
    • Edited by Darran Rowe Wednesday, November 27, 2019 3:14 PM
    • Marked as answer by Belloc Thursday, November 28, 2019 5:38 PM
    Wednesday, November 27, 2019 3:02 PM
  • Oh, and just to cover all bases.

    I would suggest you reread [basic.start]. It doesn't say that main has to be the first thing executed. To quote paragraph 1 without all of the references:

    "Executing a program starts a main thread of execution in which the main function is invoked, and in which variables of static storage duration might be initialized and destroyed."

    Notice how it only states that the main function has to be invoked. There is nothing stating how it gets there. This means that using a function that then calls main is permitted. If you look at the call stack for a Visual C++ application when it gets to main:

    Notice how it does eventually invoke main? This conforms to the standard. What's more the rest of the code is used to initialise and destroy globals which is allowed by the standard.

    *EDIT*

    I just thought of something and it is something that I think is obvious but I just thought that it may not be obvious for everyone.

    The /ENTRY doesn't refer to the entrypoint of a specific language. It is an operating system concept which basically defines at what address the executable image will start executing. 

    The operating system has to be language agnostic. This means that the operating system isn't what initialises things, it calls into the language runtime and this is what does the initialisation. It is this initialisation code that the executable's entrypoint points to. It has to be done this way since only a module is able to properly initialise itself.

    This also allows any language to be used in Windows, all you have to do is provide a runtime layer and there you go.


    This is a signature. Any samples given are not meant to have error checking or show best practices. They are meant to just illustrate a point. I may also give inefficient code or introduce some problems to discourage copy/paste coding. This is because the major point of my posts is to aid in the learning process.

    • Edited by Darran Rowe Wednesday, November 27, 2019 6:44 PM
    • Marked as answer by Belloc Thursday, November 28, 2019 5:37 PM
    Wednesday, November 27, 2019 4:09 PM
  • Well, the PE format documentation has a field AddressOfEntryPoint. This is the address that the Windows internal BaseThreadInitThunk in Kernel32.dll calls. This is what the VC linker sets for the /ENTRY option. It is possible to verify this.

    What the paragraph you quoted talks about is how the linker will automatically set /ENTRY. It means that if you do define /DLL then it will automatically use DllMainCRTStartup, no questions asked. If you provide /SUBSYSTEM then it will automatically reject the opposing subsystem functions. So if you use /SUBSYSTEM:CONSOLE then the linker will only look for main or wmain. If you use /SUBSYSTEM:WINDOWS then the linker will only look for WinMain or wWinMain. If it finds one of these then it will then use the <entrypoint name>CRTStartup function corresponding to the main function.

    The reason behind this is that the linker tries to ease the burden on the developer a little. There is 4 possible entrypoints for a Windows Desktop application and what occurs in the startup code has to change depending on what you select. For example if you choose wmain over main then it sets up the wide version of the environment immediately but for main it will set up the narrow version of the environment. The startup code also has to get the argc and argv variables prepared.

    If you use the (w)WinMain functions then it doesn't need to prepare argc and argv, but instead it has to get the HINSTANCE, prepare the command line in the form that WinMain uses and then finally get the CmdShow parameter out of the startup information for the process.

    If you don't set /SUBSYSTEM in the linker options then the linker will not limit itself to the console main functions or the Windows main functions and choose what type of application and startup code is needed based purely upon the entrypoint you define in the code.

    The /SUBSYSTEM is also responsible for determining if a console window is attached to the process automatically or not. So the function found in your code will determine this if you don't set the /SUBSYSTEM option.

    You must remember that there are conflicting terms here. The PE format entrypoint is not the same as the C/C++ entrypoint. If this was a VB application then the VB compiler could set this to the startup code for the VB runtime. If this is a Pascal/Delphi application then the Pascal/Delphi compiler could set this to the startup code for the Pascal/Delphi runtime. The executable modules entrypoint has to be language agnostic.


    This is a signature. Any samples given are not meant to have error checking or show best practices. They are meant to just illustrate a point. I may also give inefficient code or introduce some problems to discourage copy/paste coding. This is because the major point of my posts is to aid in the learning process.

    • Edited by Darran Rowe Wednesday, November 27, 2019 7:39 PM
    • Marked as answer by Belloc Thursday, November 28, 2019 5:37 PM
    Wednesday, November 27, 2019 7:24 PM
  • Well, in the second paragraph of the Remarks section it states:

    "The function must be defined to use the __stdcall calling convention. The parameters and return value depend on if the program is a console application, a windows application or a DLL. It is recommended that you let the linker set the entry point so that the C run-time library is initialized correctly, and C++ constructors for static objects are executed."

    The third sentence is quite telling.

    The default startup code is responsible for initialising some aspects of the CRT and it is also responsible for initialising more complex global/static objects.


    This is a signature. Any samples given are not meant to have error checking or show best practices. They are meant to just illustrate a point. I may also give inefficient code or introduce some problems to discourage copy/paste coding. This is because the major point of my posts is to aid in the learning process.

    • Marked as answer by Belloc Thursday, November 28, 2019 5:36 PM
    Thursday, November 28, 2019 12:58 PM
  • There is also the fact that the linker itself is only responsible for binary operations, and the PE executable format (which I have linked already) has nothing on complex initialisation code.

    The only location that the binary format has for saying "do this set of operations" is the binary entrypoint. As I stated already, this is what the linker's /ENTRY option sets.

    This really does mean that if the linker doesn't set it to a function that doesn't do the process initialisation then it doesn't get done.


    This is a signature. Any samples given are not meant to have error checking or show best practices. They are meant to just illustrate a point. I may also give inefficient code or introduce some problems to discourage copy/paste coding. This is because the major point of my posts is to aid in the learning process.

    • Marked as answer by Belloc Thursday, November 28, 2019 5:36 PM
    Thursday, November 28, 2019 1:04 PM
  • Yes, but the entrypoint that you specify must do all the work.

    One of the easiest ways is to just get your entrypoint to call the startup code explicitly.

    #include <cstdio>
    #ifdef _DEBUG
    #pragma comment(lib, "msvcrtd.lib")
    #else
    #pragma comment(lib, "msvcrt.lib")
    #endif
    
    extern"C"
    int mainCRTStartup();
    
    extern"C"
    int entrypoint()
    {
    	//call the CRT startup code
    	mainCRTStartup();
    	return 0;
    }
    
    //you need the function with this name
    //since the startup code will call it
    int main()
    {
    	printf("Hello World!");
    
    	return 0;
    }

    If you set entrypoint as the entrypoint function using /ENTRY then it will initialise everything properly and call through to main.

    However it is important to note that in this case everything will only be initialised in main, and you can't return from main because the C++ runtime will call exit as soon as it leaves main:

    //
    // Initialization is complete; invoke main...
    //
    
    int const main_result = invoke_main();
    
    //
    // main has returned; exit somehow...
    //
    
    if (!__scrt_is_managed_app())
        exit(main_result);

    This is taken from the startup code. A managed app is defined as a .NET framework application, so anything not built with /CLR will exit with that exit statement.

    The invoke_main function just calls the main function for the given settings:

    static int __cdecl invoke_main()
    {
        return main(__argc, __argv, _get_initial_narrow_environment());
    }

    As an example.

    If you wanted to do a more complex setup, the startup code is available in the Visual C++ directory along side the compiler. But obviously this is unsupported and subject to change.


    This is a signature. Any samples given are not meant to have error checking or show best practices. They are meant to just illustrate a point. I may also give inefficient code or introduce some problems to discourage copy/paste coding. This is because the major point of my posts is to aid in the learning process.

    • Marked as answer by Belloc Thursday, November 28, 2019 5:35 PM
    Thursday, November 28, 2019 2:20 PM
  • The entry point name that Darran Rowe's post refers to is documented here - https://docs.microsoft.com/en-us/cpp/build/reference/entry-entry-point-symbol?view=vs-2019
    • Marked as answer by Belloc Thursday, November 28, 2019 5:38 PM
    Wednesday, November 27, 2019 3:13 PM
  • Although this was written to focus on WinMain, the discussion of entry points that it contains is also applicable to console applications.  I think it might help clarify things, especially with respect to the CRT.

    WinMain is just the conventional name for the Win32 process entry point

    • Marked as answer by Belloc Thursday, November 28, 2019 5:37 PM
    • Unmarked as answer by Belloc Thursday, November 28, 2019 5:38 PM
    • Marked as answer by Belloc Thursday, November 28, 2019 5:39 PM
    Wednesday, November 27, 2019 7:08 PM
  • > where the "is defined" above means to me, to be defined in the /ENTRY linker option.
    > Maybe the "is defined" here means defined in the code and not in the /ENTRY option.

    Right.  It just means that the symbol is present and externally visible in one of the object modules.

    The C runtime library startup code, which is provided in your Visual Studio installation, does "weak external" references to all the possible app entry points: main, wmain, WinMain and wWinMain.  "Weak" means there is no error if they are not found.  Which ever one gets linked will be called after the runtime library finishes its setup work.


    Tim Roberts | Driver MVP Emeritus | Providenza &amp; Boekelheide, Inc.

    • Marked as answer by Belloc Thursday, November 28, 2019 5:37 PM
    Wednesday, November 27, 2019 11:36 PM

All replies

  • NOTE, any library reference in this post implicitly includes any debug and static version of the library. I.e. MSVCRT.lib also includes MSVCRTD.lib, LIBCMT.lib and LIBCMTD.lib.

    The reason why you get these linker errors is simple. By removing the startup code you remove the only linker reference to certain object files in MSVCRT.lib. The actual entrypoint name that the linker defines isn't main, but actually mainCRTStartup. (There is some variation on this, if you use wmain as the entrypoint then it uses wmainCRTStartup, if you use WinMain then it uses WinMainCRTStartup).

    The VC++ compiler only ever specifies MSVCRT.lib as the default library so it uses some trickery to pull in references to vcruntime.lib and ucrt.lib too. If you look in the startup code that comes along with Visual Studio you should find the source initializers.cpp and this is where it has the references to vcruntime.lib and ucrt.lib. However this is only referenced via the startup code. This is the source in which the C and C++ initialisers and terminators are declared. These would be referenced in __scrt_common_main_seh in exe_common.inl. But since the global initialisation is only ever run through the default entry point, the C and C++ initialisation never occurs and so the ucrt.lib and vcruntime.lib are never referenced.

    Oh, and it is because there is no global initialisation that std::cout will not work. Part of the initialisation is setting up the stdin, stdout and stderr variables. If you look in the source file cin.cpp, cout.cpp and cerr.cpp which is also part of the source provided by Visual Studio, you should find how cin, cout and cerr are initialised. These rely on the __acrt_iob_func that stdin, stdout and stderr are defined as. The io table that __acrt_iob_func is also only properly initialised in the global variable initialisation which is skipped when you change the entry point.

    You would have to reference ucrt.lib and vcruntime.lib explicitly. But in general don't be surprised if things don't work. You skipped all of the global initialisation and are actually running more akin to a freestanding environment now rather than a hosted environment. This means that less features are guaranteed to work.


    This is a signature. Any samples given are not meant to have error checking or show best practices. They are meant to just illustrate a point. I may also give inefficient code or introduce some problems to discourage copy/paste coding. This is because the major point of my posts is to aid in the learning process.


    • Proposed as answer by RLWA32 Wednesday, November 27, 2019 3:12 PM
    • Edited by Darran Rowe Wednesday, November 27, 2019 3:14 PM
    • Marked as answer by Belloc Thursday, November 28, 2019 5:38 PM
    Wednesday, November 27, 2019 3:02 PM
  • The entry point name that Darran Rowe's post refers to is documented here - https://docs.microsoft.com/en-us/cpp/build/reference/entry-entry-point-symbol?view=vs-2019
    • Marked as answer by Belloc Thursday, November 28, 2019 5:38 PM
    Wednesday, November 27, 2019 3:13 PM
  • Oh, and just to cover all bases.

    I would suggest you reread [basic.start]. It doesn't say that main has to be the first thing executed. To quote paragraph 1 without all of the references:

    "Executing a program starts a main thread of execution in which the main function is invoked, and in which variables of static storage duration might be initialized and destroyed."

    Notice how it only states that the main function has to be invoked. There is nothing stating how it gets there. This means that using a function that then calls main is permitted. If you look at the call stack for a Visual C++ application when it gets to main:

    Notice how it does eventually invoke main? This conforms to the standard. What's more the rest of the code is used to initialise and destroy globals which is allowed by the standard.

    *EDIT*

    I just thought of something and it is something that I think is obvious but I just thought that it may not be obvious for everyone.

    The /ENTRY doesn't refer to the entrypoint of a specific language. It is an operating system concept which basically defines at what address the executable image will start executing. 

    The operating system has to be language agnostic. This means that the operating system isn't what initialises things, it calls into the language runtime and this is what does the initialisation. It is this initialisation code that the executable's entrypoint points to. It has to be done this way since only a module is able to properly initialise itself.

    This also allows any language to be used in Windows, all you have to do is provide a runtime layer and there you go.


    This is a signature. Any samples given are not meant to have error checking or show best practices. They are meant to just illustrate a point. I may also give inefficient code or introduce some problems to discourage copy/paste coding. This is because the major point of my posts is to aid in the learning process.

    • Edited by Darran Rowe Wednesday, November 27, 2019 6:44 PM
    • Marked as answer by Belloc Thursday, November 28, 2019 5:37 PM
    Wednesday, November 27, 2019 4:09 PM
  • The reason why you get these linker errors is simple. By removing the startup code you remove the only linker reference to certain object files in MSVCRT.lib. The actual entrypoint name that the linker defines isn't main, but actually mainCRTStartup.

    Let's analyse this by parts. My interpretation of this paragraph is that when I define an entry point with the linker option /ENTRY, I'm automatically removing the entry point mainCRTStartup from the start up code. My problem is that I can't find this interpretation in /ENTRY. For instance, when I read the paragraph below, on the linked page, I have the opposite meaning of what you're saying:

    "If the /DLL or /SUBSYSTEM option is not specified, the linker selects a subsystem and entry point depending on whether main or WinMain is defined."

    where the "is defined" above means to me, to be defined in the /ENTRY linker option. Maybe the "is defined" here means defined in the code and not in the /ENTRY option.

    I'm really confused with all this. Could you clarify?


    Wednesday, November 27, 2019 7:03 PM
  • Although this was written to focus on WinMain, the discussion of entry points that it contains is also applicable to console applications.  I think it might help clarify things, especially with respect to the CRT.

    WinMain is just the conventional name for the Win32 process entry point

    • Marked as answer by Belloc Thursday, November 28, 2019 5:37 PM
    • Unmarked as answer by Belloc Thursday, November 28, 2019 5:38 PM
    • Marked as answer by Belloc Thursday, November 28, 2019 5:39 PM
    Wednesday, November 27, 2019 7:08 PM
  • Well, the PE format documentation has a field AddressOfEntryPoint. This is the address that the Windows internal BaseThreadInitThunk in Kernel32.dll calls. This is what the VC linker sets for the /ENTRY option. It is possible to verify this.

    What the paragraph you quoted talks about is how the linker will automatically set /ENTRY. It means that if you do define /DLL then it will automatically use DllMainCRTStartup, no questions asked. If you provide /SUBSYSTEM then it will automatically reject the opposing subsystem functions. So if you use /SUBSYSTEM:CONSOLE then the linker will only look for main or wmain. If you use /SUBSYSTEM:WINDOWS then the linker will only look for WinMain or wWinMain. If it finds one of these then it will then use the <entrypoint name>CRTStartup function corresponding to the main function.

    The reason behind this is that the linker tries to ease the burden on the developer a little. There is 4 possible entrypoints for a Windows Desktop application and what occurs in the startup code has to change depending on what you select. For example if you choose wmain over main then it sets up the wide version of the environment immediately but for main it will set up the narrow version of the environment. The startup code also has to get the argc and argv variables prepared.

    If you use the (w)WinMain functions then it doesn't need to prepare argc and argv, but instead it has to get the HINSTANCE, prepare the command line in the form that WinMain uses and then finally get the CmdShow parameter out of the startup information for the process.

    If you don't set /SUBSYSTEM in the linker options then the linker will not limit itself to the console main functions or the Windows main functions and choose what type of application and startup code is needed based purely upon the entrypoint you define in the code.

    The /SUBSYSTEM is also responsible for determining if a console window is attached to the process automatically or not. So the function found in your code will determine this if you don't set the /SUBSYSTEM option.

    You must remember that there are conflicting terms here. The PE format entrypoint is not the same as the C/C++ entrypoint. If this was a VB application then the VB compiler could set this to the startup code for the VB runtime. If this is a Pascal/Delphi application then the Pascal/Delphi compiler could set this to the startup code for the Pascal/Delphi runtime. The executable modules entrypoint has to be language agnostic.


    This is a signature. Any samples given are not meant to have error checking or show best practices. They are meant to just illustrate a point. I may also give inefficient code or introduce some problems to discourage copy/paste coding. This is because the major point of my posts is to aid in the learning process.

    • Edited by Darran Rowe Wednesday, November 27, 2019 7:39 PM
    • Marked as answer by Belloc Thursday, November 28, 2019 5:37 PM
    Wednesday, November 27, 2019 7:24 PM
  • > where the "is defined" above means to me, to be defined in the /ENTRY linker option.
    > Maybe the "is defined" here means defined in the code and not in the /ENTRY option.

    Right.  It just means that the symbol is present and externally visible in one of the object modules.

    The C runtime library startup code, which is provided in your Visual Studio installation, does "weak external" references to all the possible app entry points: main, wmain, WinMain and wWinMain.  "Weak" means there is no error if they are not found.  Which ever one gets linked will be called after the runtime library finishes its setup work.


    Tim Roberts | Driver MVP Emeritus | Providenza &amp; Boekelheide, Inc.

    • Marked as answer by Belloc Thursday, November 28, 2019 5:37 PM
    Wednesday, November 27, 2019 11:36 PM
  • By removing the startup code you remove the only linker reference to certain object files in MSVCRT.lib. The actual entrypoint name that the linker defines isn't main, but actually mainCRTStartup.

    I have to insist on this, as I think my previous question was not answered yet. If you look at /ENTRY, there is nothing in there saying that whenever I provide an entry point using the link option /ENTRY, the startup code is not inserted, or is removed, from my the code. As far as I understand, this is vital for anyone to grasp how this whole scheme works. Please, point me to the point in /ENTRY, or in any other page, in Visual Studio docs, where I could confirm that this is actually the case. If you can't do this, please say so. This way, I will be able to confirm that my understanding is correct and the problem lies with MS docs.
    Thursday, November 28, 2019 12:27 PM
  • I previously posted the link to the documentation of mainCRTStartup.

    "The entry point name that Darran Rowe's post refers to is documented here - https://docs.microsoft.com/en-us/cpp/build/reference/entry-entry-point-symbol?view=vs-2019"

    Thursday, November 28, 2019 12:36 PM
  • I previously posted the link to the documentation of mainCRTStartup.

    "The entry point name that Darran Rowe's post refers to is documented here - https://docs.microsoft.com/en-us/cpp/build/reference/entry-entry-point-symbol?view=vs-2019"

    I know that. The link I provided in my previous post with /ENTRY is exactly the one you posted above. But where in this link, does it say that whenever I provide an entry point using /ENTRY, the default start up code will not be inserted in my code?
    Thursday, November 28, 2019 12:50 PM
  • Well, in the second paragraph of the Remarks section it states:

    "The function must be defined to use the __stdcall calling convention. The parameters and return value depend on if the program is a console application, a windows application or a DLL. It is recommended that you let the linker set the entry point so that the C run-time library is initialized correctly, and C++ constructors for static objects are executed."

    The third sentence is quite telling.

    The default startup code is responsible for initialising some aspects of the CRT and it is also responsible for initialising more complex global/static objects.


    This is a signature. Any samples given are not meant to have error checking or show best practices. They are meant to just illustrate a point. I may also give inefficient code or introduce some problems to discourage copy/paste coding. This is because the major point of my posts is to aid in the learning process.

    • Marked as answer by Belloc Thursday, November 28, 2019 5:36 PM
    Thursday, November 28, 2019 12:58 PM
  • There is also the fact that the linker itself is only responsible for binary operations, and the PE executable format (which I have linked already) has nothing on complex initialisation code.

    The only location that the binary format has for saying "do this set of operations" is the binary entrypoint. As I stated already, this is what the linker's /ENTRY option sets.

    This really does mean that if the linker doesn't set it to a function that doesn't do the process initialisation then it doesn't get done.


    This is a signature. Any samples given are not meant to have error checking or show best practices. They are meant to just illustrate a point. I may also give inefficient code or introduce some problems to discourage copy/paste coding. This is because the major point of my posts is to aid in the learning process.

    • Marked as answer by Belloc Thursday, November 28, 2019 5:36 PM
    Thursday, November 28, 2019 1:04 PM
  • "It is recommended that you let the linker set the entry point so that the C run-time library is initialized correctly, and C++ constructors for static objects are executed."

    Does that mean that it is possible (although probably complicated) to initialize the C run-time library and to execute C++ constructors for static objects, and at the same time to define an entry point with the /ENTRY linker option, for a C++ code?
    Thursday, November 28, 2019 1:55 PM
  • Yes, but the entrypoint that you specify must do all the work.

    One of the easiest ways is to just get your entrypoint to call the startup code explicitly.

    #include <cstdio>
    #ifdef _DEBUG
    #pragma comment(lib, "msvcrtd.lib")
    #else
    #pragma comment(lib, "msvcrt.lib")
    #endif
    
    extern"C"
    int mainCRTStartup();
    
    extern"C"
    int entrypoint()
    {
    	//call the CRT startup code
    	mainCRTStartup();
    	return 0;
    }
    
    //you need the function with this name
    //since the startup code will call it
    int main()
    {
    	printf("Hello World!");
    
    	return 0;
    }

    If you set entrypoint as the entrypoint function using /ENTRY then it will initialise everything properly and call through to main.

    However it is important to note that in this case everything will only be initialised in main, and you can't return from main because the C++ runtime will call exit as soon as it leaves main:

    //
    // Initialization is complete; invoke main...
    //
    
    int const main_result = invoke_main();
    
    //
    // main has returned; exit somehow...
    //
    
    if (!__scrt_is_managed_app())
        exit(main_result);

    This is taken from the startup code. A managed app is defined as a .NET framework application, so anything not built with /CLR will exit with that exit statement.

    The invoke_main function just calls the main function for the given settings:

    static int __cdecl invoke_main()
    {
        return main(__argc, __argv, _get_initial_narrow_environment());
    }

    As an example.

    If you wanted to do a more complex setup, the startup code is available in the Visual C++ directory along side the compiler. But obviously this is unsupported and subject to change.


    This is a signature. Any samples given are not meant to have error checking or show best practices. They are meant to just illustrate a point. I may also give inefficient code or introduce some problems to discourage copy/paste coding. This is because the major point of my posts is to aid in the learning process.

    • Marked as answer by Belloc Thursday, November 28, 2019 5:35 PM
    Thursday, November 28, 2019 2:20 PM
  • @Darran Rowe

    Superb. Great answer. Again, thanks a lot for your kind attention.

    Thursday, November 28, 2019 5:35 PM