caulk is an up-to-date, functional, NON-DEPRECATED wrapper for Valve's Steamworks API for use with plain C instead of C++ as intended. Capische?
Refer to the usage section and the code example for a quick rundown.
✔️ Schwungus-certified.
The Steamworks SDK provides the header steam_api_flat.h which declares interoperable interface functions. However, it is written in C++, which leads to grotesque build errors if you include it in your plain-C code. This library mitigates that by generating a compatibility layer, gluing C++ classes, structures, functions, and methods defined in the SDK to plain-C code, with the help of steam_api.json, which is a repository of all classes and methods used in Steamworks designed for this specific purpose (thanks Valve!).
Gluing the C++ SDK to C objects nonetheless requires using a C++ linker to produce the final binary. This means you will have to use a C++ toolchain to build your game.
The key takeaways from going on through with this all are twofold:
- You get to use Steamworks inside a plain-C game. Doesn't matter whether it's being used for personal amusement or due to technical limitations.
- Other programming languages that can interface with C native libraries won't have to reinvent a whole new wrapper generator to bind the C++ Steamworks SDK to their C glue module. caulk reduces the friction of porting Steamworks to other programming languages by a whole step.
caulk requires a ZIP of the Steamworks SDK in your project's root. Click that link to semi-legally download it.
caulk uses CMake for the build pipeline. Since CMake is the de-facto standard for cross-platform C/C++ compilation, you shouldn't be afraid to use it - here's a CMakeLists.txt example:
cmake_minimum_required(VERSION 3.24.0 FATAL_ERROR)
project(myProject)
# REQUIRED: point this to your Steamworks SDK archive.
set(STEAMWORKS_SDK_ZIP ${CMAKE_SOURCE_DIR}/steamworks_sdk_163.zip)
include(FetchContent)
FetchContent_Declare(caulk
GIT_REPOSITORY https://github.com/Schwungus/caulk.git
GIT_TAG <release tag or commit SHA>) # edit this line to your liking
FetchContent_MakeAvailable(caulk)
add_executable(myProject main.c)
target_link_libraries(myProject PRIVATE caulk)
# call this to automatically copy steam_appid.txt and shared library objects after build:
caulk_populate(myProject)You will also need to include a steam_appid.txt in your project's root. You should use the caulk_populate(targetName) CMake convenience function: it copies steam_appid.txt and steamapi.dll over to the passed target's binary output directory.
To actually use the Steamworks SDK from your C code, add #include <caulk.h> and prefix each Steamworks function call with caulk_:
#include <stdlib.h>
#include <caulk.h>
int main(int argc, char* argv[]) {
if (!caulk_Init())
return EXIT_FAILURE;
/* Do Steamworks stuff here... */
caulk_Shutdown();
return EXIT_SUCCESS;
}Again, see test.c for a more complete example.
The API is designed to be self-documenting. Once you look up a Steamworks object you need to use, calling methods on it is simple: just pass a pointer to your object to a function named caulk_ClassName_MethodName(). "Interface" types from the Steamworks SDK are even easier to use: you don't need to make an object for them; just call caulk_InterfaceName_MethodName()! (The I prefix is absent from InterfaceName in this call signature: e.g. ISteamMatchmaking becomes just SteamMatchmaking.)
If a method returns SteamAPICall_t instead of giving the desired result immediately, it must be an asynchronous call which will spit out a value eventually. To use this value, you will have to define a handler function using caulk_Resolve(); that function will be called by caulk_Dispatch() whenever your result is ready.
Also, you'll be receiving a lot of events from Steamworks. To make use of them, you'll have to register handlers using caulk_Register(). These also rely on calls to caulk_Dispatch() to trigger.
See the example below for both caulk_Resolve() and caulk_Register():
#include <stdlib.h>
#include <caulk.h>
static void resolveCreateLobby(void* pData, bool ioFail) {
LobbyCreated_t* data = pData;
if (!ioFail && data->m_eResult == k_EResultOK)
printf("Created lobby ID=%d!!!\n", data->m_ulSteamIDLobby);
}
static void onEnterLobby(void* pData) {
LobbyEnter_t* data = pData;
// Do cool stuff with `data`.
}
int main(int argc, char* argv[]) {
if (!caulk_Init())
return EXIT_FAILURE;
// Call `onEnterLobby()` every time we enter a lobby.
caulk_Register(LobbyEnter_t_iCallback, onEnterLobby);
// Let a lobby be created in the background, and run `resolveCreateLobby()` when it's done.
SteamAPICall_t cb = caulk_SteamMatchmaking_CreateLobby(k_ELobbyTypeFriendsOnly, 2);
caulk_Resolve(cb, resolveCreateLobby);
for (;;)
// Dispatch the registered handlers:
caulk_Dispatch(); // should put a `Sleep` here or smth...
caulk_Shutdown();
return EXIT_SUCCESS;
}Please note that the compatibility-layer generator compiles to a native binary that has to be run in order for caulk to even compile. This means you cannot (currently) cross-compile the library from scratch (e.g. from Linux targeting Windows), since the resulting generator binary will be a Windows executable that cannot run natively on the builder Linux.
As a workaround, you'll have to use one of the releases, where the glue-code generator is compiled to a cross-platform APE binary. Instruct caulk to use the prebuilt generator binary by changing your FetchContent_Declare block to something along the lines of:
FetchContent_Declare(caulk
URL https://github.com/Schwungus/caulk/releases/download/rolling/caulk-rolling.tar.gz)
set(CAULK_PREBUILT_GENERATOR ${caulk_SOURCE_DIR}/ape$<IF:$<BOOL:WIN32>,.exe,>)
FetchContent_MakeAvailable(caulk)