0

Background

I'm working on updating the build system for legacy C/C++ code from the early 90s, the original build system is based on cfront. The code has ~1000 global variables that are scattered all over the ~100 source files (code base ~200k lines).

The CMake based build system that I currently have can build on Linux with GCC, MacOS with Clang, and Windows with MinGW. However, I get linker errors for MSVC on Windows because of the global variables. It turns out that for some reason MSVC also mangles the names of global variables.

Question

It seems that prepending every global variable with extern "C" would work. However, that would be very labor intensive. (Unless someone knows of an IDE or static analyzer that could do the job.) Hence my question, is it possible to somehow force MSVC to not mangle the global variables using some sort of option or file?

Minimal working example

CMakeLists.txt

project(test)

set(SOURCES foo.c)
set(BINNAME main)

foreach(f ${SOURCES})
        set_source_files_properties(${f} PROPERTIES LANGUAGE CXX)
endforeach()

add_executable(${BINNAME}
        main.c
        ${SOURCES})

main.c

#include <stdio.h>

extern void foo();
extern int sv;

int main() {
    if(sv)
        foo();
    return 0;
}

foo.c

#include "foo.h"

int sv = 10;

void foo() {
    printf("\nThe value of global variable sv is:%d\n", sv);
}

foo.h

#include <stdio.h>

extern "C" {
    void foo();
}

More info

  • Running the above example on Windows with MSVC will give you the following output:
CustomBuild:
  Building Custom Rule C:/Users/matya/sandbox/git/cmake/CMakeLists.txt
ClCompile:
  C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.28.29333\bin\HostX64\x64\CL.e
  xe /c /nologo /W3 /WX- /diagnostics:column /O2 /Ob2 /D WIN32 /D _WINDOWS /D NDEBUG /D "CMAKE_INTDIR=\"Releas
  e\"" /D _MBCS /Gm- /EHsc /MD /GS /fp:precise /Zc:wchar_t /Zc:forScope /Zc:inline /GR /Fo"main.dir\Release\\"
   /Fd"main.dir\Release\vc142.pdb" /Gd /TC /errorReport:queue C:\Users\matya\sandbox\git\cmake\main.c
  main.c
  C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.28.29333\bin\HostX64\x64\CL.e
  xe /c /nologo /W3 /WX- /diagnostics:column /O2 /Ob2 /D WIN32 /D _WINDOWS /D NDEBUG /D "CMAKE_INTDIR=\"Releas
  e\"" /D _MBCS /Gm- /EHsc /MD /GS /fp:precise /Zc:wchar_t /Zc:forScope /Zc:inline /GR /Fo"main.dir\Release\\"
   /Fd"main.dir\Release\vc142.pdb" /Gd /TP /errorReport:queue C:\Users\matya\sandbox\git\cmake\foo.c
  foo.c
Link:
  C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.28.29333\bin\HostX64\x64\link
  .exe /ERRORREPORT:QUEUE /OUT:"C:\Users\matya\sandbox\git\cmake\build\Release\main.exe" /INCREMENTAL:NO /NOLO
  GO kernel32.lib user32.lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib a
  dvapi32.lib /MANIFEST /MANIFESTUAC:"level='asInvoker' uiAccess='false'" /manifest:embed /PDB:"C:/Users/matya
  /sandbox/git/cmake/build/Release/main.pdb" /SUBSYSTEM:CONSOLE /TLBID:1 /DYNAMICBASE /NXCOMPAT /IMPLIB:"C:/Us
  ers/matya/sandbox/git/cmake/build/Release/main.lib" /MACHINE:X64  /machine:x64 main.dir\Release\main.obj
  main.dir\Release\foo.obj
main.obj : error LNK2019: unresolved external symbol sv referenced in function main [C:\Users\matya\sandbox\gi
t\cmake\build\main.vcxproj]
    Hint on symbols that are defined and could potentially match:
      "int sv" (?sv@@3HA)
C:\Users\matya\sandbox\git\cmake\build\Release\main.exe : fatal error LNK1120: 1 unresolved externals [C:\User
s\matya\sandbox\git\cmake\build\main.vcxproj]
Done Building Project "C:\Users\matya\sandbox\git\cmake\build\main.vcxproj" (default targets) -- FAILED.

Done Building Project "C:\Users\matya\sandbox\git\cmake\build\ALL_BUILD.vcxproj" (default targets) -- FAILED.


Build FAILED.

"C:\Users\matya\sandbox\git\cmake\build\ALL_BUILD.vcxproj" (default target) (1) ->
"C:\Users\matya\sandbox\git\cmake\build\main.vcxproj" (default target) (3) ->
(Link target) ->
  main.obj : error LNK2019: unresolved external symbol sv referenced in function main [C:\Users\matya\sandbox\
git\cmake\build\main.vcxproj]
  C:\Users\matya\sandbox\git\cmake\build\Release\main.exe : fatal error LNK1120: 1 unresolved externals [C:\Us
ers\matya\sandbox\git\cmake\build\main.vcxproj]

  • As I mentioned earlier the above runs fine on Windows with MinGW (and also Linux GCC, etc.)

  • In the above example, if I simply prepend int sv = 10 in foo.c with extern "C", it also compiles with MSVC.

  • I use the following commands to run cmake:

mkdir build
cd build
cmake ..
cmake --build . --config Release -v

Thanks!

gomfy
  • 637
  • 8
  • 16
  • You have a mix of `/TC` (compile as C) and `/TP` (compile as C++). Pick one. Or pick none, and let the compiler choose based on the file extension. – 1201ProgramAlarm Feb 02 '21 at 00:38
  • There's no such option, it must be done for each extern variabe individually. – πάντα ῥεῖ Feb 02 '21 at 00:38
  • @πάνταῥεῖ Thanks! BTW moderator, the linked post does not answer this question. I think it would be interesting to know as to why MSVC mangles globals? – gomfy Feb 02 '21 at 00:39
  • @gomfy As mentioned, the answer you're looking for doesn't exist. – πάντα ῥεῖ Feb 02 '21 at 00:39
  • Makes sense and thanks for the answer. I'm just saying that the two questions are not duplicates of one another. – gomfy Feb 02 '21 at 00:44
  • The name mangling is part of how MSVC handles type safety in C++. Compile all your sources as C files and there will be a minimal amount of mangling. – 1201ProgramAlarm Feb 02 '21 at 00:48
  • @1201ProgramAlarm thanks. Unfortunately, that doesn't work as some of the code uses C++ classes so I have to compile those parts as C++. – gomfy Feb 02 '21 at 01:05
  • It goes back to what @1201ProgramAlarm said initially. `main.c` is compiled as c, `/TC` while `foo.c` is compiled as cpp `/TP` from the `set_source_files_properties(${f} PROPERTIES LANGUAGE CXX)`. This is why `main.c` can't find `sv` and the compiler says that `sv@@3HA` (from `foo.c`) might be the potential match. Is there a reason `main.c` can't also be compiled as cpp? Pretty sure that would fix your problem. – mydisplayname Feb 02 '21 at 05:11
  • @mydisplayname So this is only a minimal working example that reproduces the error message. I talk in more detail about the situation in the background section. Parts of the massive code base is C++ and other parts are C. The way they got around this in the past was to pass the code through cfront which then allowed you to compile the code as plain C. My goal is to get rid of cfront. It's doable but I'm looking for the "most clever/least labor intensive" way of doing it ;-) – gomfy Feb 02 '21 at 15:09
  • 1
    There's no shortcut for this. Everything that is used by both C and C++ sources needs to be `extern "C"` when compiled by the C++ compiler. Your standard C headers (like stdio.h) will have things in them to do this for the standard C library. Possibly the simplest thing would be to declare any variables used by C in C sources (with extern "C" in headers as necessary), using the block scope `extern "C" { /* declarations here */ }` in headers (conditionally applied only for C++). – 1201ProgramAlarm Feb 02 '21 at 15:44
  • Yes, that's the conclusion I'm coming to as well. BTW your comment would be the appropriate answer to this question. Thanks! – gomfy Feb 02 '21 at 18:46

0 Answers0