首页 > 解决方案 > 如何将 Swift 包中的 C++ 模块包含在另一个模块或 Xcode 项目中

问题描述

摘要:在创建包含 C++ 模块和 Obj-C 模块的 Swift 包时,C++ 标头导入出现问题。

这里的模块或“客户端”不涉及 Swift 代码 - 这只是关于使用 Swift 包管理器来创建和使用模块。过去,我经常使用框架使用来自 Objective-C 客户端(以及来自 Swift 代码使用的 Objective-C 包装器)的 C++ 代码。

我正在尝试使用 Swift 包管理器来创建一个包含 C++ 和 Objective-C API 的包。我有一个 Swift 包,带有工作单元测试,结构如下:MyPackageCPP是 C++ 实现;MyPackage是围绕它的objective-C 包装器。

Package.swift 文件是

import PackageDescription

let package = Package(
    name: "MyPackage",
    platforms: [.iOS(.v13)],
    products: [
        .library(name: "MyPackageCPP", targets: ["MyPackageCPP"]),
        .library(name: "MyPackage", targets: ["MyPackage"]),
    ],
    targets: [
        .target(
            name: "MyPackageCPP",
            path: "Sources/MyPackageCPP",
            publicHeadersPath: "include"
        ),
        .target(
            name: "MyPackage",
            dependencies: ["MyPackageCPP"],
            path: "Sources/MyPackage",
            exclude: [
                "Info.plist",
            ],
            publicHeadersPath: "include"
        ),
        .testTarget( // C++ tests
            name: "MyPackageCPPTests",
            dependencies: ["MyPackageCPP"]
        ),
        .testTarget( // Obj-C wrapper tests
            name: "MyPackageTests",
            dependencies: ["MyPackage"]
        )
    ],
    cxxLanguageStandard: .gnucxx1z
)

目录结构是

MyPackage
 |
 .- Sources
 |  |
 |  .- MyPackage
 |  |  |
 |  .  .- Classes (implementation files for Objective-C wrappers)
 |  |  |
 |  |  .- include
 |  |        MyClass.h (Obj-C header)
 |  |
 |  .- MyPackageCPP
 |     |
 |     .- Classes (C++ Implementation files)
 |     |
 |     .- include
 |           MyClassCPP.h (C++ header)
 |           MyPointClass.h (Simple C-header defining a custom struct)
 |
 .- Tests
    |
    .- MyPackageCPPTests
    |     MyPackageCPPTests.mm (XCTestCase-based unit tests for C++ API)
    |
    .- MyPackageTests
          MyPackageTests.mm (XCTestCase-based unit tests for pure Obj-C API)

关键头文件:

MyPointClass.h(旨在定义一个可由 C++ 类及其 Obj-C 包装器使用的简单 C 类型):

struct MyPoint {
    double x;
    double y;
};

MyClassCPP.h:

#pragma once

#include "MyPointClass.h"

#include <string>
#include <memory>

class MyClassCPP {

public:
    MyClassCPP() = delete;
    MyClassCPP(const std::string &info);
    ~MyClassCPP();

    bool initialized();
    MyPoint modifyPoint(MyPoint point);


private:
    struct Impl;
    std::unique_ptr <struct Impl> _pImpl;
};

MyClass.h(Obj-C 包装类)

#import <UIKit/UIKit.h>

#import "MyPointClass.h"

NS_ASSUME_NONNULL_BEGIN

// iOS Platform specific implementation

@interface MyClass : NSObject

-(instancetype)initWithInfoInAString:(NSString*)someInfoInAString;

-(MyPoint)modifyPoint:(MyPoint)point;

@property (atomic, readonly) BOOL initialized;

@end

NS_ASSUME_NONNULL_END

问题

我一直遇到包含问题。单元测试像现在一样工作,但是如果我将 Obj-C 测试文件从 更改MyPackageTests.mmMyPackageTests.m,我会遇到构建失败'string' not found. 我知道需要 Objective-C++ (*.mm) 才能正确构建使用 C++ 的 Objective-C 文件,但 MyPackageTests 的直接包含链不包含任何 C++。相反,从错误中可以看出,在构建模块 Obj-CMyPackage时,包含 C++ 包中的所有头文件MyPackageCPP,即使MyPointClass.hObj-C MyPackage 仅显式包含 C 头文件(通过#import "MyClass.h")(线索是“ In file included from <module-includes>”)

当我尝试将 Obj-C 模块包含到MyApp导入 Obj-C 包装器的简单 Objective-C 测试应用程序中时,我得到了相同的构建错误MyPackage

While building module 'MyPackage' imported from /Users/{user}/TestCode/MyPackage/Tests/MyPackageTests/MyPackageTests.m:3:
While building module 'MyPackageCPP' imported from /Users/{user}/TestCode/MyPackage/Sources/MyPackage/include/MyClass.h:3:
In file included from <module-includes>:1: /Users/{user}/TestCode/MyPackage/Sources/MyPackageCPP/include/MyClassCPP.h:5:10: fatal error: 'string' file not found
#include <string>

问题

C++ 包中的所有头文件是否都必须包含在 Obj-C 包装器构建中?(我尝试添加MyClassCPP.h到目标 MyPackage 的排除项,但无济于事。)或者有没有办法解决这个问题,允许我在构建 Obj-C 包装器时只包含 C++ 包中的纯 C 头文件MyPackage(同时保持 C++ 头文件可用于 C++ 客户端)?

标签: c++objective-cmoduleswift-package-manager

解决方案


在构建 Obj-C 包装器 MyPackage 时(同时保持 C++ 头文件可用于 C++ 客户端),有没有办法解决这个问题?

在尝试了多种方法后,我找到了一种可行的方法。这些变化涉及:

  1. 修改结构文件 MyPointClass.h 如下(我在这里重命名为 MyPackageCommonHeaders.h):

    typedef struct MyPoint {
        double x;
        double y;
    } MyPoint;
    
  2. 添加文件 MyPackageCommonHeaders.c 仅包含以下内容:

    #include "../include/MyPackageCommonHeaders.h"
    
  3. 仅为共享标头创建一个新模块

    MyPackage
     |
     .- Sources
     |  |
     |  .- MyPackage
     |  |  |
     |  |  .- Classes (implementation files for Objective-C wrappers)
     |  |  |
     |  |  .- include
     |  |        MyClass.h (Obj-C header)
     |  |
     |  .- MyPackageCommonHeaders  // NEW MODULE
     |  |  |
     |  |  .- Classes (C++ Implementation files)
     |  |        MyPackageCommonHeaders.c // NEW FILE
     |  |  |
     |  |  .- include
     |  |        MyPackageCommonHeaders.h (Simple C-header)
     |  |
     |  .- MyPackageCPP
     |     |
     |     .- Classes (C++ Implementation files)
     |     |
     |     .- include
     |               MyClassCPP.h (C++ header)
     |
     .- Tests
        |
        .- MyPackageCPPTests
        |     MyPackageCPPTests.mm (XCTestCase-based unit tests for C++ API)
        |
        .- MyPackageTests
              MyPackageTests.m (XCTestCase-based unit tests for pure Obj-C API)
    

(注意 MyPackageTests.m 不再是 Obj-C++,这是我最初的目标)

  1. 修改 Package.swift 以包含新模块,将其列为 C++ 和 Obj-C API mmodules 的依赖项:

    import PackageDescription
    
    let package = Package(
        name: "MyPackage",
        platforms: [.iOS(.v13)],
        products: [
            .library(name: "MyPackageCommonHeaders", targets: ["MyPackageCommonHeaders"]),
            .library(name: "MyPackageCPP", targets: ["MyPackageCPP"]),
            .library(name: "MyPackage", targets: ["MyPackage"]),
        ],
        targets: [
            .target(
                name: "MyPackageCPP",
                dependencies: ["MyPackageCommonHeaders"],
                path: "Sources/MyPackageCPP",
                publicHeadersPath: "include"
            ),
            .target(
                name: "MyPackage",
                dependencies: ["MyPackageCPP", "MyPackageCommonHeaders"],
                path: "Sources/MyPackage",
                exclude: [
                    "Info.plist",
                ],
                publicHeadersPath: "include"
            ),
            .testTarget( // C++ tests
                name: "MyPackageCPPTests",
                dependencies: ["MyPackageCPP"]
            ),
            .testTarget( // Obj-C wrapper tests
                name: "MyPackageTests",
                dependencies: ["MyPackage"]
            )
        ],
        cxxLanguageStandard: .gnucxx1z
    )
    

有了这个,我可以创建一个支持 C++ API 的封装,以及一个使用 C++ 代码的纯 Obj-C 包装 API。请注意,Obj-C 包装器不会强制将 Obj-C 调用者提升为 Obj-C++。其中一个关键部分是两个API 都可以使用一个简单的 C-struct (MyPoint),因此 Obj-C 包装器不必翻译;即,Obj-C 包装器可以返回从其包装的 C++ 方法获得的相同对象。


推荐阅读