I'm trying to implement a game engine in C++.
I'm using macOS for development (arm-64 architecture). I want to create a window using Foundation framework.
CMakeLists.txt utilizes C++ and Objective-C languages, and I want to build a library containing only Objective-C code and then use this library in my C++ code (probably including the header). The end result should be a C++ executable.
[src]
├── [Draw]
├── Window.cpp
├── Window.h
└── [WindowBuilder]
├── WindowBuilder.cpp
├── WindowBuilder.h
└── [macOS]
├── WindowBuilderObjC.h
└── WindowBuilderObjC.mm
├── [Utils]
└── Size.h
├── main.cpp
└── CMakeLists.txt
src/Draw/Window.h
#include <iostream>
#include <Utils/Size.h>
#include "WindowBuilder/WindowBuilder.h"
#pragma once
namespace Draw
{
class Window
{
private:
bool _isPresented;
Utils::Size _size = Utils::Size(0, 0);
WindowBuilder::WindowBuilder *_builder;
Window();
~Window() = default;
public:
// Public static member function to access the singleton instance
static Window &Instance()
{
// Guaranteed to be initialized once
static Window instance;
return instance;
}
// Delete the copy constructor and assignment operator
Window(const Window &) = delete;
Window &operator=(const Window &) = delete;
void SetSize(Utils::Size size);
void DrawWindow();
};
}
src/Draw/Window.cpp
#include "Window.h"
namespace Draw
{
Window::Window()
{
_builder = &WindowBuilder::WindowBuilder::Instance();
}
void Window::SetSize(Utils::Size size)
{
_size = size;
}
void Window::DrawWindow()
{
std::cout << "Draw window with size: [" << _size.GetWidth() << ":" << _size.GetHeight() << "]" << std::endl;
_builder->BuildWindow(_size);
}
}
src/WindowBuilder/WindowBuilder.h
// General
#include <iostream>
// Internal
#include <Utils/Size.h>
#include <Draw/WindowBuilder/macOS/WindowBuilderObjC.h>
#pragma once
namespace WindowBuilder
{
class WindowBuilder
{
private:
bool _isPresented;
WindowBuilder() = default;
~WindowBuilder() = default;
public:
static WindowBuilder &Instance()
{
// Guaranteed to be initialized once
static WindowBuilder instance;
return instance;
}
// Delete the copy constructor and assignment operator
WindowBuilder(const WindowBuilder &) = delete;
WindowBuilder &operator=(const WindowBuilder &) = delete;
void BuildWindow(Utils::Size size);
};
}
src/WindowBuilder/WindowBuilder.cpp
#include "WindowBuilder.h"
namespace WindowBuilder
{
void WindowBuilder::BuildWindow(Utils::Size size)
{
buildWindowWithSize(size);
}
}
src/Draw/WindowBuilder/macOS/WindowBuilderObjC.h
#import <Cocoa/Cocoa.h>
#import "Utils/Size.h"
@interface WindowBuilderObjC : NSObject
- (void)buildWindowWithSize:(Utils::Size)size;
@end
@interface WindowDelegate : NSObject <NSWindowDelegate>
@end
@implementation WindowDelegate
- (BOOL)windowShouldClose:(id)sender
{
[NSApp terminate:nil];
return YES;
}
@end
src/Draw/WindowBuilder/macOS/WindowBuilderObjC.mm
#import "WindowBuilderObjC.h"
@implementation WindowBuilderObjC
- (void)buildWindowWithSize:(Utils::Size)size
{
@autoreleasepool
{
[NSApplication sharedApplication];
NSRect frame = NSMakeRect(0, 0, 800, 600);
NSUInteger style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable;
NSWindow *window = [[NSWindow alloc] initWithContentRect:frame styleMask:style backing:NSBackingStoreBuffered defer:NO];
[window setTitle:@"My Window"];
[window center];
WindowDelegate *delegate = [[WindowDelegate alloc] init];
[window setDelegate:delegate];
[window makeKeyAndOrderFront:nil];
[NSApp run];
}
}
@end
src/main.cpp
#include <iostream>
#include <Draw/Window.h>
#include <Utils/Size.h>
int main()
{
std::cout << "Hello World" << std::endl;
Utils::Size size(600, 800);
auto &window = Draw::Window::Instance();
window.SetSize(size);
window.DrawWindow();
}
cmake_minimum_required(VERSION 3.16)
project(GameEngine LANGUAGES CXX OBJC OBJCXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# enable_language(OBJC)
# enable_language(OBJCXX)
add_executable(Engine src/main.cpp)
add_library(UtilsLib SHARED
src/Utils/Size.h
)
set_target_properties(UtilsLib PROPERTIES LINKER_LANGUAGE CXX)
add_library(WindowBuilderObjCLib SHARED
src/Draw/WindowBuilder/macOS/WindowBuilderObjC.h
src/Draw/WindowBuilder/macOS/WindowBuilderObjC.mm
)
target_include_directories(WindowBuilderObjCLib
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src/Draw/WindowBuilder/macOS
)
target_link_libraries(WindowBuilderObjCLib
PRIVATE "-framework Cocoa"
PRIVATE "-framework Foundation"
PRIVATE "-framework AppKit"
)
# Set the language for the library to Objective-C++
set_target_properties(WindowBuilderObjCLib PROPERTIES
LINKER_LANGUAGE "OBJCXX"
XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES YES
XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_WEAK YES
)
add_library(DrawLib STATIC
src/Draw/Window.h
src/Draw/Window.cpp
)
target_sources(DrawLib
PRIVATE
src/Draw/WindowBuilder/WindowBuilder.h
src/Draw/WindowBuilder/WindowBuilder.cpp
)
target_sources(DrawLib
PUBLIC
src/Draw/Window.h
src/Draw/Window.cpp
)
set_target_properties(DrawLib PROPERTIES LINKER_LANGUAGE CXX)
find_library(FOUNDATION_FRAMEWORK Foundation)
# Link against required frameworks and libraries
target_link_libraries(DrawLib
PRIVATE ${FOUNDATION_FRAMEWORK}
"-framework AppKit"
"-framework CoreGraphics"
"-lobjc"
)
find_package(OpenGL REQUIRED COMPONENTS OpenGL)
include_directories(${CMAKE_SOURCE_DIR}/src)
add_subdirectory(src/Draw/WindowBuilder/macOS)
# Link the DrawLib and UtilsLib targets to the Engine target
target_link_libraries(Engine PRIVATE
WindowBuilderObjCLib
DrawLib
UtilsLib
OpenGL::GL
${FOUNDATION_FRAMEWORK}
)
Seems like when I include Window.h into main.cpp, the compiler looks for symbols in Cocoa/Cocoa.h file and finds Objective-C symbols instead of C++.
The error is following:
[build] In file included from /Users/user/MySource/GameEngine_Vulkan/src/Draw/Window.cpp:1:
[build] In file included from /Users/user/MySource/GameEngine_Vulkan/src/Draw/Window.h:4:
[build] In file included from /Users/user/MySource/GameEngine_Vulkan/src/Draw/WindowBuilder/WindowBuilder.h:6:
[build] In file included from /Users/user/MySource/GameEngine_Vulkan/src/Draw/WindowBuilder/macOS/WindowBuilderObjC.h:1:
[build] In file included from /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.3.sdk/System/Library/Frameworks/Cocoa.framework/Headers/Cocoa.h:12:
[build] In file included from /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.3.sdk/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h:8:
[build] /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.3.sdk/System/Library/Frameworks/Foundation.framework/Headers/NSObjCRuntime.h:601:1: error: expected unqualified-id
[build] @class NSString, Protocol;
[build] In file included from /Users/user/MySource/GameEngine_Vulkan/src/Draw/WindowBuilder/WindowBuilder.cpp:1:
[build] In file included from /Users/user/MySource/GameEngine_Vulkan/src/Draw/WindowBuilder/WindowBuilder.h:6:
[build] ^
[build] In file included from /Users/user/MySource/GameEngine_Vulkan/src/Draw/WindowBuilder/macOS/WindowBuilderObjC.h:1:
[build] In file included from /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.3.sdk/System/Library/Frameworks/Cocoa.framework/Headers/Cocoa.h:12:
[build] In file included from /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.3.sdk/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h:8:
[build] /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.3.sdk/System/Library/Frameworks/Foundation.framework/Headers/NSObjCRuntime.h:601:1: error: expected unqualified-id
[build] @class NSString, Protocol;
[build] ^
[build] /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.3.sdk/System/Library/Frameworks/Foundation.framework/Headers/NSObjCRuntime.h:603:9: error: unknown type name 'NSString'
[build] typedef NSString * NSExceptionName NS_TYPED_EXTENSIBLE_ENUM;
[build] ^
[build] /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.3.sdk/System/Library/Frameworks/Foundation.framework/Headers/NSObjCRuntime.h:603:9: error: unknown type name 'NSString'
[build] typedef NSString * NSExceptionName NS_TYPED_EXTENSIBLE_ENUM;
[build] ^
[build] /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.3.sdk/System/Library/Frameworks/Foundation.framework/Headers/NSObjCRuntime.h:604:9: error: unknown type name 'NSString'
[build] typedef NSString * NSRunLoopMode NS_TYPED_EXTENSIBLE_ENUM;
[build] ^
[build] /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.3.sdk/System/Library/Frameworks/Foundation.framework/Headers/NSObjCRuntime.h:604:9: error: unknown type name 'NSString'
[build] typedef NSString * NSRunLoopMode NS_TYPED_EXTENSIBLE_ENUM;
[build] ^
[build] /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.3.sdk/System/Library/Frameworks/Foundation.framework/Headers/NSObjCRuntime.h:606:19: error: unknown type name 'NSString'
[build] FOUNDATION_EXPORT NSString *NSStringFromSelector(SEL aSelector);
[build] ^
[build] /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.3.sdk/System/Library/Frameworks/Foundation.framework/Headers/NSObjCRuntime.h:606:19: error: unknown type name 'NSString'
[build] FOUNDATION_EXPORT NSString *NSStringFromSelector(SEL aSelector);
[build] ^
...
Can I build a standalone library using Objective-C with Cocoa lib included (probably) and that it will have a public interface, so that I can use it in C++ project?
So for anyone who's looking for such kind of implementation to be used:
#ifndef WindowBuilderObjC_h
#define WindowBuilderObjC_h
#import <Cocoa/Cocoa.h>
@interface WindowBuilderObjC : NSObject
- (void)buildWindowWithWidth:(int)width height:(int)height;
@end
@interface WindowDelegate : NSObject <NSWindowDelegate>
@end
#endif /* WindowBuilderObjC_h */
#import "WindowBuilderObjC.h"
@implementation WindowBuilderObjC
- (void)buildWindowWithWidth:(int)width height:(int)height
{
@autoreleasepool
{
[NSApplication sharedApplication];
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
NSRect frame = NSMakeRect(0, 0, 800, 600);
NSUInteger style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable;
NSWindow *window = [[NSWindow alloc] initWithContentRect:frame styleMask:style backing:NSBackingStoreBuffered defer:NO];
[window setTitle:@"My Window"];
[window center];
WindowDelegate *delegate = [[WindowDelegate alloc] init];
[window setDelegate:delegate];
[window makeKeyWindow];
[window orderFrontRegardless];
[NSApp run];
}
}
@end
@implementation WindowDelegate
- (BOOL)windowShouldClose:(id)sender
{
[NSApp terminate:nil];
return YES;
}
@end
extern "C"
{
#import "WindowBuilderObjC.h"
}
extern "C" WindowBuilderObjC *createWindowBuilder()
{
WindowBuilderObjC* builder = [[WindowBuilderObjC alloc] init];
return builder;
}
extern "C" void deleteWindowBuilder(WindowBuilderObjC *builder)
{
[builder dealloc];
}
extern "C" void buildWindow(WindowBuilderObjC *builder, int* width, int* height)
{
[builder buildWindowWithWidth:*width height:*height];
}
add_library(WindowBuilderObjCLib MODULE
src/Utils/WindowBuilder/macOS/Objective-C/WindowBuilderObjCWrapper.mm
)
target_sources(WindowBuilderObjCLib
PRIVATE
src/Utils/WindowBuilder/macOS/Objective-C/WindowBuilderObjC.h
src/Utils/WindowBuilder/macOS/Objective-C/WindowBuilderObjC.mm
)
target_link_libraries(WindowBuilderObjCLib
PRIVATE "-framework Cocoa"
PRIVATE "-framework Foundation"
PRIVATE "-framework AppKit"
)
# Set the language for the library to Objective-C++
set_target_properties(WindowBuilderObjCLib PROPERTIES
LINKER_LANGUAGE "CXX"
XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES YES
XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_WEAK YES
)
// Open the library.
lib_handle = dlopen("./build/libWindowBuilderObjCLib.so", RTLD_LOCAL);
if (!lib_handle)
{
exit(EXIT_FAILURE);
}
// Boost for lib info
#include <boost/dll/library_info.hpp>
static void PrintSymbols(std::string libPath)
{
// Class `library_info` can extract information from a library
boost::dll::library_info inf(libPath);
// Getting exported symbols
std::vector<std::string> exports = inf.symbols();
// Printing symbols
for (std::size_t j = 0; j < exports.size(); ++j)
{
std::cout << exports[j] << std::endl;
}
}
// Objective-C wrapper signatures
typedef void (*buildWindowObjC)(void *builder, int *width, int *height);
typedef void *(*createWindowBuilderObjC)();
typedef void *(*deleteWindowBuilderObjC)(void *builder);
// Objective-C handlers
createWindowBuilderObjC createWindowBuilderObjCHandler;
buildWindowObjC buildWindowObjCHandler;
deleteWindowBuilderObjC deleteWindowBuilderObjCHandler;
// Objective-C Window Builder Instance
void *windowBuilderObjC;
// Load functions
createWindowBuilderObjCHandler = reinterpret_cast<createWindowBuilderObjC>(dlsym(lib_handle, "createWindowBuilder"));
if (!createWindowBuilderObjCHandler)
{
exit(EXIT_FAILURE);
}
buildWindowObjCHandler = reinterpret_cast<buildWindowObjC>(dlsym(lib_handle, "buildWindow"));
if (!buildWindowObjCHandler)
{
exit(EXIT_FAILURE);
}
deleteWindowBuilderObjCHandler = reinterpret_cast<deleteWindowBuilderObjC>(dlsym(lib_handle, "deleteWindowBuilder"));
if (!deleteWindowBuilderObjCHandler)
{
exit(EXIT_FAILURE);
}
windowBuilderObjC = createWindowBuilderObjCHandler();
int width = size.GetWidth();
int height = size.GetHeight();
buildWindowObjCHandler(windowBuilderObjC, &width, &height);
// ...
deleteWindowBuilderObjCHandler(windowBuilderObjC);
// ...
Thats it. Hope it can be useful for someone.