使用CMake混合C++和Objective-C代码并获得C++可执行文件

ax6ht2ek  于 2023-08-05  发布在  其他
关注(0)|答案(1)|浏览(146)

理念

我正在尝试用C实现一个游戏引擎。
我正在使用macOS进行开发(arm-64架构)。我想创建一个使用Foundation框架的窗口。
CMakeLists.txt使用C
和Objective-C语言,我想构建一个只包含Objective-C代码的库,然后在我的C代码中使用这个库(可能包括头文件)。最终结果应该是一个C可执行文件。

项目结构

[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();
}

CMakeLists.txt

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}
)

错误

似乎当我将Window.h包含到main.cpp中时,编译器会在可可/Cocoa.h文件中查找符号,并找到 Objective-C 符号而不是 C++
错误如下:

[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]                   ^

...

问题

我可以使用Objective-C构建一个包含可可lib的独立库(可能),并且它将具有公共接口,以便我可以在C++项目中使用它吗?

xfb7svmp

xfb7svmp1#

因此,对于任何正在寻找这种实现的人来说:

1.用Objective-C编写代码,提供你想要的功能(在我的例子中,在macOS中创建一个窗口):

WindowBuilderObjC.h

#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 */

字符串

WindowBuilderObjC.mm
#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

2.使用关键字 extern“C” 创建一个带有C头的文件(仍然是Objective-C)(因此它们可以在C代码中使用):

WindowBuilderObjCWrapper.mm

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];
}

3.修改CMakeLists.txt,从Objective-C文件创建模块库:

CMakeLists.txt

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
)

4.在C++中按照以下步骤操作:

4.1.使用 dlfcn.h lib打开库:

// Open the library.
lib_handle = dlopen("./build/libWindowBuilderObjCLib.so", RTLD_LOCAL);
if (!lib_handle)
{
    exit(EXIT_FAILURE);
}

4.2.(可选)使用 boost lib检查打开的库是否包含所需的函数签名:

// 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;
    }
}

4.3.(可选)在将要使用库函数的C++类中创建方法签名(您也可以基于签名构建自己的实现):

// 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;

4.4.从库中获取函数,并将其转换为合适的签名:

// 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);
}

4.5.使用导入库中的Objective-C函数(通过C++头文件):

windowBuilderObjC = createWindowBuilderObjCHandler();

int width = size.GetWidth();
int height = size.GetHeight();
buildWindowObjCHandler(windowBuilderObjC, &width, &height);

// ...

deleteWindowBuilderObjCHandler(windowBuilderObjC);

// ...


就是这样,希望对大家有用。

相关问题