As I’ve shifted from using macOS as my daily driver to Windows 10, I’ve found myself writing almost no code in Swift, instead finding that I develop almost entirely with C# and a tiny bit of C++.

With the announcement that Swift 5.3 will support Windows, I wanted to explore if it might be possible to use Swift to keep my knowledge of the language fresh.

Swift on Windows Logo

Set up

The path to setting up a successful environment was fraught with issues. This was partly due to my inexperience with the details the Swift runtime but also a lack of documentation made me second guess myself when I hit issues.

To get started, I initially downloaded the latest release of the installer.exe from the releases tab of the GitHub project. This was my first mistake, as the demos I wanted to run require Swift 5.3 and the latest GitHub release is Swift 5.2.1. This took a little bit of head scratching to work out as samples wouldn’t run.

After a little bit of digging, I found the build artifacts in the Azure DevOps pipeline. These artifacts were exactly what I needed to get things moving! I downloaded and ran the installer.exe, which added directory a ‘Library’ to my C drive, (very reminiscent of macOS indeed).

I then moved onto modifying my Visual Studio installation to include the following:

MSVC v142
Windows Universal C Runtime
Windows 10 SDK (10.0.17763.0)
C++ ATL for latest v142 build tools (x86 & x64)
C++ CMake tools for Windows
Python 3 64-bit (3.7.5)

Setting up for a demo project

The first project I wanted to run was a simple build example to ensure that the Swift compiler was working as expected. Before I could do this though, I needed to set some build parameter variables. To do this, I launched (as Administrator) the x64 Native Tools Command Prompt that comes with Visual Studio 2019 and set the following variables.

Variable NameValue
ProgramFilesC:\Program Files
ProgramFilesX86C:\Program Files (x86)
UniversalCRTSdkDir%ProgramFilesX86%\Windows Kits\10
UCRTVersion10.0.18362.0
VCToolsInstallDir%ProgramFilesX86%\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.25.28610
SDKROOTC:\Library\Developer\Platforms\Windows.platform\Developer\SDKs\Windows.sdk
SWIFTROOT-sdk %SDKROOT% -I %SDKROOT%\usr\lib\swift -L %SDKROOT%\usr\lib\swift\windows

The snippet below is something I saved to remind myself exactly what I had set. The echo command simply prints the values to ensure that I’d set everything correctly.

set ProgramFiles=C:\Program Files
set ProgramFilesX86=C:\Program Files (x86)

set UniversalCRTSdkDir = %ProgramFilesX86%\Windows Kits\10
set UCRTVersion = 10.0.18362.0
set VCToolsInstallDir=%ProgramFilesX86%\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.25.28610
set SDKROOT=C:\Library\Developer\Platforms\Windows.platform\Developer\SDKs\Windows.sdk
set SWIFTFLAGS=-sdk %SDKROOT% -I %SDKROOT%\usr\lib\swift -L %SDKROOT%\usr\lib\swift\windows


echo %ProgramFiles%
echo %ProgramFilesX86%
echo %UniversalCRTSdkDir%
echo %UCRTVersion%
echo %VCToolsInstallDir%
echo %SDKROOT%
echo %SWIFTFLAGS%

CMake Configuration

To build the demo project, I needed to supply CMake with options. To do this, I set the directory of the command prompt (still the x64 Native Tools cmd) to the root of the swift-build-examples repository I cloned earlier. I then set CMake options as below.

"%ProgramFiles%/CMake/bin/cmake.exe"  ^
  -B /BinaryCache/HelloMinimal        ^
  -D BUILD_SHARED_LIBS=YES            ^
  -D CMAKE_BUILD_TYPE=Release         ^
  -D CMAKE_Swift_FLAGS="%SWIFTFLAGS%" ^
  -G Ninja                            ^
  -S HelloMinimal-CMake

Which then generated the build files.

Then all that was left to do was build the demo.

cmake --build /BinaryCache/HelloMinimal 

This then generated the helle.exe executable which I ran in the terminal.

The application ran!! I was now running Swift on Windows. My mind blown, I wanted to continue my exploration!

The repository came with two example projects. The minimal project that I’d just built contained only two Swift files demonstrating the the absolute basics of building a Swift executable. Looking at the second demo project, I could see it was a little more complex, but still easy enough to grasp.

//hikit.swift
private func random() -> Int {
    return 4;
}

public func sayHello() {
    print("Hello world! Your random number is \(random())")
}
//hello.swift
import HiKit

print("Greeting the world")
sayHello()
print("World has been greeted! Congratulations!")

Building the second demo project

The second demo project demonstrates is similar to the minimal project in that it simply prints out a string to the console. The key difference lies in the fact the the Swift code resides in a shared library which is then linked in. The string value that get’s printed lives in the HelloWorldCore.swift file that you can find in the HelloWorldCore directory.

I edited this example slightly as I tore it apart to really understand it. I removed the call to an internal struct that stored the printed string value and then removed the testing fluff entirely.

Source Edits

//HelloWorldCore.swift (edited for simplicity) 
public struct HelloWorldCore {
  public static var string: String {
    return "This is Swift code running on Windows 10. How awesome!"
  }
}

Because I no longer called into _HelloWorldCore.swift, I deleted it and updated the CMakeLists.txt as below:

add_library(HelloWorldCore
  HelloWorldCore.swift)
target_compile_options(HelloWorldCore PRIVATE
  $<$<BOOL:BUILD_TESTING>:-enable-testing>)
set_target_properties(HelloWorldCore PROPERTIES
  INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY})

install(TARGETS HelloWorldCore
  ARCHIVE DESTINATION lib
  LIBRARY DESTINATION lib
  RUNTIME DESTINATION bin
  COMPONENT lib)

Removing the tests

I started by deleting the Tests directory and then removed the last few options in the CMakeLists.txt until I was left with this:

cmake_minimum_required(VERSION 3.15.1)

project(SwiftDemo LANGUAGES C Swift)

# place all modules into `swift` in the root of the build tree
set(CMAKE_Swift_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/swift)

set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)

# enable shared libraries by default (Windows, Darwin)
#
# Windows does not yet support static libraries in Swift, Darwin no longer
# supports static libraries after ABI stability.
if(CMAKE_SYSTEM_NAME STREQUAL Windows OR CMAKE_SYSTEM_NAME STREQUAL Darwin)
  option(BUILD_SHARED_LIBS "Build shared libraries by default" YES)
endif()

add_subdirectory(Source)

After that, I edited the CMakeList.txt from the HelloWorldCore directory, deleting the reference to testing.

add_library(HelloWorldCore
  HelloWorldCore.swift)
set_target_properties(HelloWorldCore PROPERTIES
  INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY})

install(TARGETS HelloWorldCore
  ARCHIVE DESTINATION lib
  LIBRARY DESTINATION lib
  RUNTIME DESTINATION bin
  COMPONENT lib)

CMake options

Much like the first example project, I then passed in the build options to CMake, which generated the build files in the /BinaryCache directory specified. Once this was complete, I called CMake –build on the directory as before.

"%ProgramFiles%/CMake/bin/cmake.exe"  ^
  -B /BinaryCache/HelloWorld        ^
  -D BUILD_SHARED_LIBS=YES            ^
  -D CMAKE_BUILD_TYPE=Release         ^
  -D CMAKE_Swift_FLAGS="%SWIFTFLAGS%" ^
  -G Ninja                            ^
  -S HelloWorld-CMake
  
  cmake --build /BinaryCache/HelloWorld

Fixing a code error

As I was attempting to build the sample, I hit an error that I’ve seen many times when writing Swift on macOS.

HelloWorld\HelloWorld.swift:17:55: error: value of optional type 'String?' must be unwrapped to a value of type 'String' guard let message = try? accessor.access(), display(message) else { ^ C:\repos\swift-build-examples\HelloWorld-CMake\Source\HelloWorld\HelloWorld.swift:17:55: note: coalesce using '??' to provide a default when the optional value contains 'nil' guard let message = try? accessor.access(), display(message) else { ^ ?? <#default value#> C:\repos\swift-build-examples\HelloWorld-CMake\Source\HelloWorld\HelloWorld.swift:17:55: note: force-unwrap using '!' to abort execution if the optional value contains 'nil' guard let message = try? accessor.access(), display(message) else { ^ !

The error here is to do with an optional value not being unwrapped. If you’re a C# developer then you can think of optional values as Nullable<T>. The syntax is pretty similar as we can both use the ? operator rather than writing Nullable<T> for C# and Optional<T> for Swift.

Knowing the problem was a optional value, I inspected the source and noticed the access function returns String?. This is because it HelloWorldCore might not exist, which might result in a nil value.

struct HelloWorldStringAccessor {
  func access() throws -> String? {
    return HelloWorldCore.string
  }
}

To fix this, I added the ‘crash’ operator to force unwrap the value and submitted a PR to the project.

guard let message = try? accessor.access(), display(message!) else {
    print("unable to display message")
    return
}

I was then ready to get back to building! I reran the CMake options snippet and executed CMake build. Now the errors were fixed, I had a successful build! Navigating to the bin directory I found two files, the HelloWorld executable and the HelloWorldCore shared library dll.

Running the executable produced the following output:


Running a Windows UI Swift app

Running a command line application in Swift, whilst extremely exiting for the nerdiest of induvials, doesn’t provide the level of excitement that a UI does!

For macOS apps, Swift developers use Cocoa whilst iOS developers use Cocoa Touch. Both can now use or Marzipan, but the apps are frankly horrible! But most Swift developers aren’t building apps with the super old-school Win32 APIs.

Thanks to the amazing work of Saleem, I was able to rock the classic VB6 look in my first Swift Windows UI app by running another example.


Thoughts on the future

After much exploration into how this was all pieced together, I’ve come away with a huge sense of excitement for the project. It reminds me of how I felt when I first discovered Xamarin, before quitting my job and joining them! I’ve also found myself endlessly researching and thinking about how best to create a Swift projection a more modern Windows UI technology, to allow Swift developers to build beautiful Windows apps using technologies like WinUI.

My current thinking is it’d be wise to to leverage work like xlang in a similar way to the Rust WinRT language projection. This could possibly be made by creating a utility that generates Swift modules from Windows Metadata (WinMD) files, but right now I’m only in the thinking / tinkering stage.

The only real work other than poking the sample has been to fork an existing project that creates a WinRT language projection for TypeScript. My plan is to dig into the depths of this tool as a possible basis for use in generating Swift types for use in Windows development.


Learning More

To learn more about Swift on Windows, I highly recommend following Saleem Abdulrasool on GitHub. I’ve also included some links below to things I’ve found useful over the last few days.

GitHub Repos

Swift Builds for Windows
Swift console example apps
Swift Win32 UI example app

Microsoft Docs

Windows & Messages
System Defined Messages
Control Library
WinMD
WinUI 3.0

3 Comments

  • Thanks a lot for your good article! 😀👍

    Do you know if it is possible – at this point – to make calls to a Swift-library from C-Sharp code and vice versa? Have you seen any examples of this?

    Cheers!

    • Mike J says:

      I’ve played around with this recently! It depends on what you’re looking to do, but if you own the Swift library and it’s not using any Apple specific APIs then you can use RemObjects to build .NET libraries with Swift. These libraries are usable from C# and vis-versa. If you’re looking to call into iOS specific APIs then Xamarin have a tool which allows for consuming these APIs when using Xamarin.iOS (but this wont work for Windows).

  • Jens Schwarzer says:

    Thanks a lot Mike 👍 So it sounds like this enables more cross platform for Swift then – happy days 🤠

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.