[Windows] lld trying to link private C++ headers

Hey guys,
I'm getting link errors on Windows and I need some help figuring out why. This seems like something that is probably known but I was unable to find an existing issue on it.

I have a C++ library being compiled with SPM. The library has C conforming headers.
For some reason on windows the linker is attempting to link to private headers which contain C++ code.
On Linux and Darwin the code compiles and links as expected.

Here is an example of one of the many link errors:

lld-link: error: undefined symbol: struct EffectStateFactory * __cdecl NullStateFactory_getFactory(void)
>>> referenced by C:\\Espionage\\Espionage\\Espionage\\.build\\x86_64-unknown-windows-msvc\\debug\\OpenALSoft.build\\UnmodifiedSource\\al\\auxeffectslot.cpp.o:(struct `anonymous namespace'::FactoryItem const *const `anonymous namespace'::FactoryList)

Also, and this seems related. I have always gotten warnings on windows for what seems like every single link.

lld-link: warning: C:\\Espionage\\Espionage\\Espionage\\.build\\x86_64-unknown-windows-msvc\\debug\\UniversalGraphics.build\\SourceAPI\\UserInterface\\Windows\\Win32\\Win32Window.swift.o: locally defined symbol imported: $s10Direct3D1217DGISwapChainFlagsVs10SetAlgebraAAMc (defined in C:\\Espionage\\Espionage\\Espionage\\.build\\x86_64-unknown-windows-msvc\\debug\\Direct3D12.build\\dxgi_h\\Enumerations\\DXGI_SWAP_CHAIN_FLAG.swift.o) [LNK4217]

This seems like the same issue, but because it's C++ and not compatible with Swift it's now an error.
Can someone give me some guidance?

Static linking is not supported on Windows yet, particularly with SPM. You might be able to get away with this with CMake or bazel. Otherwise, you should create a shared library for the C++ code, which should fix this.

Ohhh. I left my library types undefined because I thought on windows it automatically ended up as dynamic. I swear my libraries used to become dlls before, but now they are just partially built products. I don't think I've changed my packages, but I don't build my windows versions as often as I should...

I see in SPM the default is not .automatic, but just nil which is kind of confusing since a library must have a type. For my own sanity, did the default library type on windows get changed at some point?

It never really occurred to me, but a "target" in spm is really just a static library isn't it? So it looks like every single target in my graph needs to be a single target dynamic library product for Windows?

My package graph has like 20 targets. Is the best practice for this to separate an entire Package object in Package.swift with #if os(Windows)?

I would say that the best practice would be to make all the targets dynamic libraries on all the platforms.

CC: @tomerd @abertelrud

Yeah, it looks like that's the best option as SPM doesn't appear to allow you to use a product as a target dependency within the same package even if the product has no dependencies itself.

So in addition to being single target products they have to be in separate packages. Thank goodness we can nest packages within packages now, but RIP static executable on Linux.

For anyone that runs across this in search, the following we're all required to get my own project working. Your results may vary.

  • Made all library products into single target dynamic libraries.
  • Added __declspec(dllexport) to public declarations in a C/C++ library not already designed to be dynamic.
  • Removed 2 dependencies from a C/C++ library that are C++ and are therefore not in WinSDK yet (dsound & wasapi).
  • Added .linkedLibrary("swiftCore", .when(platforms: [.windows])) to C/C++ packages as a workaround for SR-14728.

Well shit. Apparently you can't embed a package in any package that is from a repository if it's included by another package. The error is
error: invalidManifestFormat("\'Your/Local/Package\' is not a valid path for path-based dependencies; use relative or absolute path instead.
So after you change the path 1000 times trying to get realtive to work, you'll google and find this.

So on windows every target that is not in the root package must be a stand alone repository. I would very much appreciate a solution that doesn't require me to make every target into a repository :slightly_frowning_face:

oh, and in Xcode if you have a local checkout of the package in your workspace the error doesn't happen so you won't find out your package graph is completely broken until you try to build from command line. The VSCode extension does not perfrom this functionality so it still doesn't work on windows at all.

Another thing I ran across is @exported import does not get linked on windows. You'll have to add the packages that are exported to every package that uses the exported symbols. Not a big deal.

Home stretch! I'm down to a single link error on the executable:

lld-link: error: undefined symbol: WinMain
>>> referenced by d:\a01\_work\6\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:102
>>>               msvcrt.lib(exe_winmain.obj):(int __cdecl invoke_main(void))
>>> referenced by d:\a01\_work\6\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288
>>>               msvcrt.lib(exe_winmain.obj):(int __cdecl __scrt_common_main_seh(void)) 

I'm not sure where to go with this one. Maybe my Windows SDK is not setup right?

I wonder if the latest snapshots help with the path issue - there were a number of improvements that were made to SPM on main that should help with path processing.

As to the undefined WinMain that implies that you are trying to build a GUI executable but have not provided a main entry point. I don't know how you managed to trigger this. The linker will consider all entry points (main, wmain, WinMain, wWinMain). It seems that it believes that the target is a GUI subsystem non-Unicode application.

compnerd/swift-win32-application: Template Project for using Swift/Win32 (github.com) has an example of how to force the subsystem/entry point if you are using @main. That may or may not be interesting for you.

I'll try it with the Xcode beta tonight. It didn't end up being too bad though. As long as the target has no dependencies it can share a repository with other targets. I only ended needing 2 new repositories, so it wasn't as messy as I originally thought it would be.

I have this setup from back when SPM was first added to windows in 5.4.x I think. I am currently using that workaround from your link:

.executableTarget(name: "Espionage",
            dependencies: ["EspionageCore", "GateEngine"],
            swiftSettings: [
                .unsafeFlags(["-Xfrontend", "-entry-point-function-name", "-Xfrontend", "-wWinMain"], .when(platforms: [.windows])),
            linkerSettings: [
                .unsafeFlags(["-Xlinker", "/SUBSYSTEM:WINDOWS"], .when(platforms: [.windows])),

And my entry point is the default generated from main.swift.
I think this was the only way to do it back then, though at this point I'm not even sure why or how my project was compiling and running on Windows before... But it was :man_shrugging:

My main currently looks like this in main.swift:

let game = Game(library: EspionageLibrary())
DispatchQueue.main.async {

Swift should be wrapping my code in a main() automatically right?
Swift Version: swift-5.6.3-RELEASE
Tools Version: 5.5

Okay looking through the example in your link I see I forgot to declare the wWinMain function.

func wWinMain(_ hInstance: HINSTANCE, _ hPrevInstance: HINSTANCE, _ pCmdLine: PWSTR, _ nCmdShow: CInt) -> CInt {
    let game = Game(library: EspionageLibrary())
    DispatchQueue.main.async {

It compiles fine now, but it runs differently. The debugger won't attach, but I'm guessing this entry point is doing some setup for threading and I shouldn't be using dispatchMain() anymore?

I skipped working on the entry point for now in order to work on many other issues.
I was finally able to get everything working for debug builds.

For the entry point I started looking at compnerd's Swift Win32 library. The main implementation is easy enough to understand. But I'm confused. I only see @main and static main(). How does Swift know to use a GUI entry point and not a command entry point?