Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 17 additions & 17 deletions sdk/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,33 +12,31 @@ endif()
project(CppSdk LANGUAGES CXX)

# -----------------------------
# Windows-only + compiler guard
# Compiler guard
# -----------------------------
if (NOT WIN32)
message(FATAL_ERROR "CppSdk is Windows-only for now (uses Win32/WIL headers).")
endif()

# Accept MSVC OR clang-cl (Clang in MSVC compatibility mode).
# VS CMake Open-Folder often uses clang-cl by default.
if (NOT (MSVC OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC")))
message(STATUS "CMAKE_CXX_COMPILER_ID = ${CMAKE_CXX_COMPILER_ID}")
message(STATUS "CMAKE_CXX_COMPILER = ${CMAKE_CXX_COMPILER}")
message(STATUS "CMAKE_CXX_SIMULATE_ID = ${CMAKE_CXX_SIMULATE_ID}")
message(FATAL_ERROR "Need MSVC or clang-cl (MSVC-compatible toolchain).")
if (WIN32)
# Accept MSVC OR clang-cl (Clang in MSVC compatibility mode).
if (NOT (MSVC OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC")))
message(STATUS "CMAKE_CXX_COMPILER_ID = ${CMAKE_CXX_COMPILER_ID}")
message(STATUS "CMAKE_CXX_COMPILER = ${CMAKE_CXX_COMPILER}")
message(STATUS "CMAKE_CXX_SIMULATE_ID = ${CMAKE_CXX_SIMULATE_ID}")
message(FATAL_ERROR "On Windows, need MSVC or clang-cl (MSVC-compatible toolchain).")
endif()
endif()

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# Optional: target Windows 10+ APIs (adjust if you need older)
add_compile_definitions(_WIN32_WINNT=0x0A00 WINVER=0x0A00)
if (WIN32)
# Target Windows 10+ APIs
add_compile_definitions(_WIN32_WINNT=0x0A00 WINVER=0x0A00)
endif()

# -----------------------------
# Dependencies (installed via vcpkg)
# -----------------------------
find_package(nlohmann_json CONFIG REQUIRED)
find_package(wil CONFIG REQUIRED)
find_package(Microsoft.GSL CONFIG REQUIRED)
option(BUILD_TESTING "Build unit and end-to-end tests" ON)
if (BUILD_TESTING)
Expand Down Expand Up @@ -70,9 +68,12 @@ target_link_libraries(CppSdk
PUBLIC
nlohmann_json::nlohmann_json
Microsoft.GSL::GSL
WIL::WIL
)

if (UNIX)
target_link_libraries(CppSdk PRIVATE ${CMAKE_DL_LIBS})
Comment thread
nenad1002 marked this conversation as resolved.
Outdated
endif()

# -----------------------------
# Sample executable
# -----------------------------
Expand All @@ -93,7 +94,6 @@ if (BUILD_TESTING)
test/model_variant_test.cpp
test/catalog_test.cpp
test/client_test.cpp
Comment thread
nenad1002 marked this conversation as resolved.
test/live_audio_test.cpp
)

target_include_directories(CppSdkTests
Expand Down
2 changes: 2 additions & 0 deletions sdk/cpp/CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"generator": "Ninja",
"binaryDir": "${sourceDir}/out/build/${presetName}",
"installDir": "${sourceDir}/out/install/${presetName}",
"toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
},
Expand All @@ -66,6 +67,7 @@
"generator": "Ninja",
"binaryDir": "${sourceDir}/out/build/${presetName}",
"installDir": "${sourceDir}/out/install/${presetName}",
"toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
},
Expand Down
131 changes: 112 additions & 19 deletions sdk/cpp/src/core.h
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
Comment thread
nenad1002 marked this conversation as resolved.
Outdated
// Licensed under the MIT License.
//
// Core DLL interop loads Microsoft.AI.Foundry.Local.Core.dll at runtime.
// Core shared library interop loads the Foundry Local Core library at runtime.
// Internal header, not part of the public API.

#pragma once

#include <windows.h>
#include <string>
#include <stdexcept>
#include <filesystem>
#include <memory>

#include <wil/win32_helpers.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <dlfcn.h>
#include <climits>
#include <unistd.h>
#ifdef __APPLE__
#include <mach-o/dyld.h>
#endif
#endif

#include "foundry_local_internal_core.h"
#include "foundry_local_exception.h"
Expand All @@ -22,16 +31,87 @@
namespace foundry_local {

namespace {

// RAII wrapper for a dynamically loaded shared library handle.
struct SharedLibHandle {
void* handle = nullptr;

SharedLibHandle() = default;
explicit SharedLibHandle(void* h) : handle(h) {}
SharedLibHandle(const SharedLibHandle&) = delete;
SharedLibHandle& operator=(const SharedLibHandle&) = delete;
SharedLibHandle(SharedLibHandle&& o) noexcept : handle(o.handle) { o.handle = nullptr; }
SharedLibHandle& operator=(SharedLibHandle&& o) noexcept {
reset();
handle = o.handle;
o.handle = nullptr;
return *this;
}
~SharedLibHandle() { reset(); }

void reset() noexcept {
if (!handle) return;
#ifdef _WIN32
::FreeLibrary(static_cast<HMODULE>(handle));
#else
::dlclose(handle);
#endif
handle = nullptr;
}

explicit operator bool() const noexcept { return handle != nullptr; }
};
Comment thread
nenad1002 marked this conversation as resolved.

inline std::filesystem::path GetExecutableDir() {
auto exePath = wil::GetModuleFileNameW(nullptr);
return std::filesystem::path(exePath.get()).parent_path();
#ifdef _WIN32
wchar_t buf[MAX_PATH];
DWORD len = ::GetModuleFileNameW(nullptr, buf, MAX_PATH);
if (len == 0 || len >= MAX_PATH)
throw std::runtime_error("GetModuleFileNameW failed");
return std::filesystem::path(buf).parent_path();
Comment thread
nenad1002 marked this conversation as resolved.
Outdated
#elif defined(__APPLE__)
char buf[PATH_MAX];
uint32_t size = sizeof(buf);
if (_NSGetExecutablePath(buf, &size) != 0)
throw std::runtime_error("_NSGetExecutablePath failed");
return std::filesystem::canonical(buf).parent_path();
#else
char buf[PATH_MAX];
ssize_t len = ::readlink("/proc/self/exe", buf, sizeof(buf) - 1);
if (len == -1)
throw std::runtime_error("readlink /proc/self/exe failed");
buf[len] = '\0';
return std::filesystem::path(buf).parent_path();
Comment thread
nenad1002 marked this conversation as resolved.
Outdated
#endif
}

inline void* RequireProc(HMODULE mod, const char* name) {
if (void* p = ::GetProcAddress(mod, name))
return p;
throw std::runtime_error(std::string("GetProcAddress failed for ") + name);
inline void* LoadSharedLib(const std::filesystem::path& path) {
#ifdef _WIN32
return static_cast<void*>(::LoadLibraryW(path.c_str()));
#else
return ::dlopen(path.c_str(), RTLD_NOW);
#endif
}

inline void* RequireProc(void* mod, const char* name) {
#ifdef _WIN32
void* p = reinterpret_cast<void*>(::GetProcAddress(static_cast<HMODULE>(mod), name));
#else
void* p = ::dlsym(mod, name);
#endif
if (!p)
throw std::runtime_error(std::string("Symbol not found: ") + name);
Comment thread
nenad1002 marked this conversation as resolved.
Outdated
return p;
}

inline void* OptionalProc(void* mod, const char* name) noexcept {
#ifdef _WIN32
return reinterpret_cast<void*>(::GetProcAddress(static_cast<HMODULE>(mod), name));
#else
return ::dlsym(mod, name);
#endif
}

} // namespace

struct Core : Internal::IFoundryLocalCore {
Expand All @@ -40,7 +120,17 @@ namespace foundry_local {
Core() = default;
~Core() = default;

void LoadEmbedded() { LoadFromPath(GetExecutableDir() / "Microsoft.AI.Foundry.Local.Core.dll"); }
void LoadEmbedded() {
constexpr const char* kCoreLibName =
#ifdef _WIN32
"Microsoft.AI.Foundry.Local.Core.dll";
#elif defined(__APPLE__)
"libMicrosoft.AI.Foundry.Local.Core.dylib";
#else
"libMicrosoft.AI.Foundry.Local.Core.so";
#endif
LoadFromPath(GetExecutableDir() / kCoreLibName);
Comment thread
nenad1002 marked this conversation as resolved.
}

void unload() override {
module_.reset();
Expand All @@ -52,7 +142,7 @@ namespace foundry_local {

CoreResponse call(std::string_view command, ILogger& logger, const std::string* dataArgument = nullptr,
NativeCallbackFn callback = nullptr, void* data = nullptr) const override {
if (!module_ || !execCmd_ || !execCbCmd_ || !freeResCmd_) {
if (!static_cast<bool>(module_) || !execCmd_ || !execCbCmd_ || !freeResCmd_) {
throw Exception("Core is not loaded. Cannot call command: " + std::string(command), logger);
}

Expand Down Expand Up @@ -95,9 +185,12 @@ namespace foundry_local {
CoreResponse callWithBinary(std::string_view command, ILogger& logger,
const std::string* dataArgument,
const uint8_t* binaryData, size_t binaryDataLength) const override {
if (!module_ || !execBinaryCmd_ || !freeResCmd_) {
if (!static_cast<bool>(module_) || !freeResCmd_) {
throw Exception("Core is not loaded. Cannot call command: " + std::string(command), logger);
}
if (!execBinaryCmd_) {
throw Exception("execute_command_with_binary is not available in this version of the Core library.", logger);
}

StreamingRequestBuffer request{};
request.Command = command.empty() ? nullptr : command.data();
Expand Down Expand Up @@ -137,23 +230,23 @@ namespace foundry_local {
}

private:
wil::unique_hmodule module_;
SharedLibHandle module_;
Comment thread
nenad1002 marked this conversation as resolved.
execute_command_fn execCmd_{};
execute_command_with_callback_fn execCbCmd_{};
execute_command_with_binary_fn execBinaryCmd_{};
free_response_fn freeResCmd_{};

void LoadFromPath(const std::filesystem::path& path) {
wil::unique_hmodule m(::LoadLibraryW(path.c_str()));
SharedLibHandle m(LoadSharedLib(path));
if (!m)
throw std::runtime_error("LoadLibraryW failed");
throw std::runtime_error("Failed to load shared library: " + path.string());
Comment thread
nenad1002 marked this conversation as resolved.
Outdated

execCmd_ = reinterpret_cast<execute_command_fn>(RequireProc(m.get(), "execute_command"));
execCmd_ = reinterpret_cast<execute_command_fn>(RequireProc(m.handle, "execute_command"));
execCbCmd_ = reinterpret_cast<execute_command_with_callback_fn>(
RequireProc(m.get(), "execute_command_with_callback"));
RequireProc(m.handle, "execute_command_with_callback"));
execBinaryCmd_ = reinterpret_cast<execute_command_with_binary_fn>(
RequireProc(m.get(), "execute_command_with_binary"));
freeResCmd_ = reinterpret_cast<free_response_fn>(RequireProc(m.get(), "free_response"));
OptionalProc(m.handle, "execute_command_with_binary"));
freeResCmd_ = reinterpret_cast<free_response_fn>(RequireProc(m.handle, "free_response"));

module_ = std::move(m);
}
Expand Down
16 changes: 11 additions & 5 deletions sdk/cpp/src/flcore_native.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
#include <cstdint>
#include <type_traits>

#ifdef _WIN32
#define FL_CDECL __cdecl
#else
#define FL_CDECL
#endif
Comment thread
nenad1002 marked this conversation as resolved.

extern "C"
{
// Layout must match C# structs exactly
Expand All @@ -24,7 +30,7 @@ extern "C"
};

// Callback signature: void(*)(void* data, int length, void* userData)
using UserCallbackFn = void(__cdecl*)(void*, int32_t, void*);
using UserCallbackFn = void(FL_CDECL*)(void*, int32_t, void*);

struct StreamingRequestBuffer {
const void* Command;
Expand All @@ -36,11 +42,11 @@ extern "C"
};

// Exported function pointer types
using execute_command_fn = void(__cdecl*)(RequestBuffer*, ResponseBuffer*);
using execute_command_with_callback_fn = void(__cdecl*)(RequestBuffer*, ResponseBuffer*, void* /*callback*/,
using execute_command_fn = void(FL_CDECL*)(RequestBuffer*, ResponseBuffer*);
using execute_command_with_callback_fn = void(FL_CDECL*)(RequestBuffer*, ResponseBuffer*, void* /*callback*/,
void* /*userData*/);
using execute_command_with_binary_fn = void(__cdecl*)(StreamingRequestBuffer*, ResponseBuffer*);
using free_response_fn = void(__cdecl*)(ResponseBuffer*);
using execute_command_with_binary_fn = void(FL_CDECL*)(StreamingRequestBuffer*, ResponseBuffer*);
using free_response_fn = void(FL_CDECL*)(ResponseBuffer*);

static_assert(std::is_standard_layout<RequestBuffer>::value, "RequestBuffer must be standard layout");
static_assert(std::is_standard_layout<ResponseBuffer>::value, "ResponseBuffer must be standard layout");
Expand Down
1 change: 0 additions & 1 deletion sdk/cpp/vcpkg.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
"version-string": "0.1.0",
"dependencies": [
"nlohmann-json",
"wil",
"ms-gsl",
"gtest"
]
Expand Down
Loading