Beyond C: wrapping Dear ImGui in Swift with zero FFI

LucidLynx1 pts0 comments

Beyond C: wrapping Dear ImGui in Swift with zero FFI | A journey into a wild pointer

~/Posts/<br>Beyond C: wrapping Dear ImGui in Swift with zero FFI

A few days ago I wrote and published a blog post about how Swift interacts with C projects, using Raylib.

Somehow, much to my delight, it has been posted on multiple social networks, like Reddit and Hackernews.

Comments were interesting and constructive.<br>Many people praised how Swift manages interactivity with Raylib, but a few expressed their disappointment about the lack of demonstration on how to interact with a C++ project.

Yesterday, again, I was bored.<br>So I tried.

Like the previous one this article is for demonstration purpose only.

I will not demonstrate this time how to build a WASM version of the finished project, most particularly because some exciting changes are coming in the Swift WASM ecosystem and I am waiting for those changes before doing the WASM build.

Swift #

This time I decided to integrate Dear ImGui into my (very simple) project.

Raylib will be used to initialize the window, initialize the screen, clear it, and draw some text.<br>ImGui, compared to Raylib, will be used to draw some GUI (used mainly for very useful menus to interact with the core of your program).

Last time I discussed how easy integrating Raylib was: dropping the main header file of Raylib and its static library, and let the compiler and package manager do their magic.

But how is it different compared to Dear ImGui, a C++ project?

The project structure

The project structure does not change that much.

I included a static build of Dear ImGui (including rlImGui, which is a Raylib integration with DearImGui), and the headers of both Dear ImGui and rlImGui.

That folder (ImGui) will be dedicated to being a Clang module, as Raylib was previously.

Package.swift<br>Sources/<br>ImGui/<br>macOS/<br>dearimgui.a<br>imconfig.h<br>imgui.h<br>imgui_internal.h<br>...<br>module.modulemap<br>CRaylib/<br>macOS/<br>libraylib.a<br>WASM/<br>libraylib.a<br>raylib.h<br>MyGame/<br>main.swift<br>copy

Again, I declare a module.modulemap in this folder, to help the compiler make the bridge between the Clang module and Swift:

module ImGui [system] {<br>header "imgui.h"<br>header "rlImGui.h"<br>link "imgui"<br>export *<br>copy

Compared to Raylib I exported two headers here: imgui.h to interact with Dear ImGui, and rlImGui.h for rlImGui (as a reminder: the bridge between Raylib and Dear ImGui).

The Package.swift file

As with Raylib, I explicitly define a new target called imgui, which has a dependency to raylib:

// swift-tools-version: 6.2<br>import PackageDescription

let package = Package(<br>name: "MyGame",<br>targets: [<br>// Raylib<br>.target(<br>name: "craylib",<br>path: "Sources/CRaylib",<br>publicHeadersPath: ".",<br>linkerSettings: [<br>.unsafeFlags(["-L", "Sources/CRaylib/macOS"], .when(platforms: [.macOS])),<br>// Link required macOS graphics frameworks<br>.linkedFramework("OpenGL", .when(platforms: [.macOS])),<br>.linkedFramework("Cocoa", .when(platforms: [.macOS])),<br>.linkedFramework("IOKit", .when(platforms: [.macOS])),<br>.linkedFramework("CoreVideo", .when(platforms: [.macOS])),

// WASM only<br>.unsafeFlags(["-L", "Sources/CRaylib/WASM"], .when(platforms: [.wasi])),<br>),<br>// NEW: ImGui integration<br>.target(<br>name: "imgui",<br>dependencies: ["craylib"], // Usage of rlImGui, which needs a dependency to the Raylib Clang module<br>path: "Sources/ImGui",<br>publicHeadersPath: ".",<br>linkerSettings: [<br>.unsafeFlags(["-L", "Sources/ImGui/macOS"], .when(platforms: [.macOS])),<br>.linkedLibrary("c++", .when(platforms: [.macOS])), // This time I use a C++ library, so I want the C++ standard library linked to that module<br>),<br>.executableTarget(<br>name: "MyGame",<br>dependencies: ["craylib", "imgui"], // Now my project has two dependencies: Raylib and Dear ImGui<br>),

copy

Let&rsquo;s run it

Ok, now, I have to change the Swift main.swift source to use our new dependencies.

import CRaylib<br>import ImGui

let screenWidth: Int32 = 1200<br>let screenHeight: Int32 = 800

InitWindow(screenWidth, screenHeight, "Swift + Raylib + ImGui")

SetTargetFPS(60)

let rayWhite = Color(r: 245, g: 245, b: 245, a: 255)<br>let darkGray = Color(r: 80, g: 80, b: 80, a: 255)

rlImGuiSetup(true)<br>var showDemoWindow = true<br>while !WindowShouldClose() {<br>BeginDrawing()

ClearBackground(rayWhite)<br>DrawText("It's alive... ALIVE!", 300, 300, 20, darkGray)

rlImGuiBegin()

if showDemoWindow {<br>ImGui.ShowDemoWindow(&showDemoWindow)

rlImGuiEnd()

EndDrawing()

rlImGuiShutdown()<br>CloseWindow()

copy

The code is very simple: I spawn a native window, setup using Raylib, and then draw some text and display the Dear ImGui demo window.

Let&rsquo;s run that make run-mac command and…

[...]<br>:0: error: too many errors emitted, stopping now<br>/Users/me/Devel/craylib/Sources/MyGame/main.swift:4:12: error: could not build Objective-C module 'ImGui'<br>2 |<br>3 | #if os(macOS)<br>4 | import ImGui<br>| `- error: could not build Objective-C module 'ImGui'<br>5 | #endif<br>6 |

make: *** [Makefile:17: run-mac] Error 1

copy

Oh no.

Upgrading the Clang Importer

What happened?

Swift uses a...

imgui raylib swift macos dear module

Related Articles