Как объединить код Swift и C в статической библиотеке

Я пытаюсь создать статическую библиотеку, содержащую код Swift и C и предоставляющую только интерфейс Swift. Таким образом, сторона кода C в идеале не должна подвергаться воздействию внешнего мира. Кажется, я не могу понять, как это сделать, не вызывая предупреждений или ошибок.

Для упрощения я попытался настроить минимальную статическую библиотеку Swift+C с этими файлами:

Test.swift

class Test {
    func test() -> CInt {
        return testFromC()
    }
}

Тест.с

#include "Test.h"

int testFromC(void) {
    return 0xdeadbeef;
}

Тест.ч

int testFromC(void);

Мне нужна статическая библиотека, которая предоставляет класс Swift Test, но не функцию C testFromC(). Я могу жить с тем, что это разоблачается, если нет альтернативы.

Что я пробовал:

  1. Добавьте соединительный заголовок. Это почти работает, но а) многие люди говорят, что это не поддерживается или не рекомендуется, б) он предоставляет testFromC() импортеру модуля и в) при некоторых обстоятельствах он выдает предупреждения о неявном импорте связующего заголовка (что верно, но я хочу специально НЕ делать этого).

  2. Добавьте карту модуля. Я не могу понять, как это сделать. Никакие онлайн-примеры, кажется, не работают для меня, и если кто-нибудь знает, как это сделать правильно, я хотел бы увидеть пример для этих трех файлов.

  3. Добавить приватную карту модулей - я понимаю, что это то, что мне нужно, если я хочу избежать раскрытия функций C, но у меня с этим было еще меньше успеха.

Кто-нибудь может собрать точные шаги, чтобы сделать этот тривиальный пример сборки?

Как настроить Tailwind CSS с React.js и Next.js?
Как настроить Tailwind CSS с React.js и Next.js?
Tailwind CSS - единственный фреймворк, который, как я убедился, масштабируется в больших командах. Он легко настраивается, адаптируется к любому...
LeetCode запись решения 2536. Увеличение подматриц на единицу
LeetCode запись решения 2536. Увеличение подматриц на единицу
Увеличение подматриц на единицу - LeetCode
Переключение светлых/темных тем
Переключение светлых/темных тем
В Microsoft Training - Guided Project - Build a simple website with web pages, CSS files and JavaScript files, мы объясняем, как CSS можно...
Отношения "многие ко многим" в Laravel с методами присоединения и отсоединения
Отношения "многие ко многим" в Laravel с методами присоединения и отсоединения
Отношения "многие ко многим" в Laravel могут быть немного сложными, но с помощью Eloquent ORM и его моделей мы можем сделать это с легкостью. В этой...
В PHP
В PHP
В большой кодовой базе с множеством различных компонентов классы, функции и константы могут иметь одинаковые имена. Это может привести к путанице и...
Карта дорог Беладжар PHP Laravel
Карта дорог Беладжар PHP Laravel
Laravel - это PHP-фреймворк, разработанный для облегчения разработки веб-приложений. Laravel предоставляет различные функции, упрощающие разработку...
7
0
274
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы действительно можете создать карту частного модуля, чтобы символы C не экспортировались, см. https://clang.llvm.org/docs/Modules.html#private-module-map-files.

Заголовок моста не будет работать, потому что вы хотите создать структуру, а не приложение.

Конечно, есть несколько способов настроить проект, одним из которых будет: создать проект с соответствующими настройками карты модулей в XCode.

.modulemap файл может выглядеть примерно так:

framework module SwiftOnlyAPI {
    umbrella header "SwiftOnlyAPI.h"

    export *
    module * { export * }
}

framework module SwiftOnlyAPI_Private {
    header "Test.h"
}

Ваш Test.swift скорее всего будет выглядеть примерно так:

import SwiftOnlyAPI_Private

public class Test {
    public init() {
        //perform some initializations
    }
    
    public func test() -> CInt {
        return testFromC()
    }
}

Обратите внимание на несколько небольших изменений: импорт SwiftOnlyAPI_Private, это необходимо для функций C, которые не экспортируются, но, конечно, вызываются локально внутри фреймворка. Классы и экспортированные методы должны быть общедоступными, чтобы быть доступными из-за пределов фреймворка.

Инструкции

  • <Файл/Новый проект...> выберите Framework, выберите язык Swift, назовите его SwiftOnlyAPI
  • добавить файлы (.c,.h,.swift) в папку SwiftOnlyAPI
  • выберите Test.h в навигаторе проекта и переключите целевое членство в инспекторе с Project на Private
  • выберите SwiftOnlyAPI в навигаторе проекта, выберите цель SwiftOnlyAPI
  • теперь нажмите на Build Settings и для Mach-O Type выберите: Static Library
  • все еще в Build Settings: для Module Map File введите: SwiftOnlyAPI/SwiftOnlyAPI.modulemap
  • выберите папку SwiftOnlyAPI в проекте SwiftOnlyAPI, выберите New File... в контекстном меню и выберите шаблон Empty в разделе Other и назовите его SwiftOnlyAPI.modulemap (снимите флажок целевого членства)
  • добавьте содержимое modulemap, показанное выше

Тест дает желаемый результат, как показано ниже для альтернативного решения для нескольких целей.

Статическая библиотека

Фреймворки на самом деле представляют собой либо динамические, либо статические библиотеки (в зависимости от настройки) с дополнительными связанными файлами. Удобно для пользователей фреймворка.

Вы можете проверить это самостоятельно с помощью команды file:

Альтернатива

Если вы хотите, чтобы часть C была отдельной (например, для других потребителей), вы также можете разделить ее на два подпроекта в рабочей области Xcode, один для статической библиотеки C и один для статической платформы на основе Swift:

  • создайте рабочее пространство XCode <File/New Workspace...>, например. назови SwiftCWorkspace
  • <Файл/Новый проект...> выберите Library, выберите в разделе Framework None (Plain C/C++ Library), назовите его MyCLib и выберите тип Static
  • в диалоговом окне выберите для Add to и Group: SwiftCWorkspace
  • выберите узел MyCLib в навигаторе проекта, выберите New File... в контекстном меню и выберите шаблон файла C, назовите его Test
  • замените содержимое своим содержимым Test.c и Test.h
  • <Файл/Новый проект...> выберите Framework, выберите язык Swift, назовите его SwiftOnlyAPI
  • в диалоговом окне выберите для Add to и Group: SwiftCWorkspace
  • выберите папку SwiftOnlyAPI в проекте SwiftOnlyAPI, выберите New File... в контекстном меню и выберите шаблон Swift и замените его своим контентом
  • выберите SwiftOnlyAPI в навигаторе проекта, выберите цель SwiftOnlyAPI
  • теперь нажмите на Build Settings и для Mach-O Type выберите: Static Library
  • все еще в Build Settings: для Module Map File введите: SwiftOnlyAPI/SwiftOnlyAPI.modulemap
  • теперь нажмите на General и в разделе Frameworks and Library добавьте libMyCLib.a
  • выберите папку SwiftOnlyAPI в проекте SwiftOnlyAPI, выберите New Group... в контекстном меню и назовите ее MyCLib
  • перетащите Test.h из MyCLib проекта в папку MyCLib в SwiftOnlyAPI, снимите флажок Copy items if needed
  • с выбранным недавно связанным файлом Test.h переключите целевое членство в инспекторе с Project на Private
  • выберите папку SwiftOnlyAPI в проекте SwiftOnlyAPI, выберите New File... в контекстном меню и выберите шаблон Empty в разделе Other и назовите его SwiftOnlyAPI.modulemap (снимите флажок целевого членства)
  • добавьте содержимое modulemap, показанное выше

Ноты

Если Xcode отображает сообщения об ошибках, даже если вы все настроили правильно, иногда необходимо выйти из Xcode после задач настройки проекта, удалить папку ~/Library/Developer/Xcode/DerivedData, а затем перезапустить сборку.

Контрольная работа

import SwiftOnlyAPI

    ...

    let result = SwiftOnlyAPI.Test()
    print(result.test())

Вы можете протестировать это в отдельном приложении с помощью фреймворка SwiftOnlyAPI: будет выведен правильный результат.

Также функция C testFromC является частной и недоступной извне, как хотелось бы, см. снимок экрана:

Похоже, что это может сработать, но кажется слишком сложным, поскольку включает в себя несколько целей. Кроме того, в идеале я хочу, чтобы в качестве продукта была одна статическая библиотека, а не фреймворк.

Dag Ågren 01.01.2023 15:26

Важным моментом является структура файла .modulemap и настройка приватного заголовочного файла. Я добавил к ответу вариант, в котором все происходит в одном проекте Xcode. Фреймворки на самом деле представляют собой либо динамические, либо статические библиотеки с дополнительными связанными файлами. Удобно для потребителей фреймворка, см. обновленный ответ.

Stephan Schlecht 01.01.2023 17:39

Вы уверены, что? Я вижу много ссылок, в которых говорится, что не существует такого понятия, как «статическая структура». Фреймворки являются динамическими, а статические библиотеки — это просто файлы .a. Помещение статического .a внутри фреймворка на самом деле не должно поддерживаться? Например, если вы поместите ресурс в эту «статическую структуру», будет ли он действительно включен в приложение или просто будет проигнорирован?

Dag Ågren 01.01.2023 18:41

Да, я уверен, что есть динамические и статические фреймворки. Соответствующая настройка — Mach-O Type: либо Static Library, либо Dynamic Library. Статическая библиотека — это ar архив. Вы можете проверить это сами. Смотрите скриншот команды file в ответе, вместо Mach-O 64-bit dynamically linked shared library печатает current ar archive random library. В терминале вы также можете запустить команду ar -t SwiftOnlyAPI для статического фреймворка, чтобы вывести содержимое этого ar архива.

Stephan Schlecht 01.01.2023 19:10

Я не уверен, что правильно понял вопрос о ресурсах. При статической компоновке код помещается в окончательный исполняемый файл. Фактическая структура не будет встроена в виде папки в пакет приложений. Вы можете включать ресурсы отдельно, например, в само приложение или в отдельный пакет. Но возможно я не правильно понял вопрос. Хотя в любом случае звучит как отдельная тема.

Stephan Schlecht 01.01.2023 19:55

Вы сказали: «Фреймворки на самом деле представляют собой либо динамические, либо статические библиотеки с дополнительными связанными файлами», а я говорю, что не думаю, что у вас действительно могут быть статические фреймворки со связанными файлами. Таким образом, в целом кажется неправильным подход, чтобы попытаться создать "статическую структуру" здесь, это должна быть обычная статическая библиотека, а не какая-либо структура.

Dag Ågren 01.01.2023 21:23

Ах хорошо. Например, статические фреймворки также имеют в комплекте info.plist, который содержит, например, информацию об используемом компиляторе. Произвольные отдельные файлы ресурсов (например, изображения) действительно нельзя использовать, как указано выше (ни при использовании статической структуры, ни при использовании статической библиотеки).

Stephan Schlecht 01.01.2023 21:43

Я не думаю, что это «статическая структура». Это просто статическая библиотека, которая находится внутри папки, которая выглядит как фреймворк, но на самом деле таковой не является. Здесь правильно было бы собрать статическую библиотеку, а не фреймворк. Есть ли способ сделать это?

Dag Ågren 02.01.2023 21:52

Хм, я бы даже сказал, что это было определение «статической структуры» в течение многих лет. Следует признать, что этот термин, вероятно, был придуман за пределами Apple, и первоначальное значение фреймворка относилось к объединению динамической библиотеки с информацией и ресурсами. Но он широко используется, например. если вы загрузите версии Firebase за последние годы, такие как github.com/firebase/firebase-ios-sdk/releases/download/6.19.‌​0/… и примените file FirebaseCrashlytics.framework/FirebaseCrashlytics, вы получите current ar archive. Другим очень ярким примером является вариант статической привязки CocoaPods.

Stephan Schlecht 03.01.2023 21:08

Ну, вот в чем дело: это слово для чего-то, что Apple не создавала и не поддерживала, и оно не имеет особого смысла. Пытаясь найти это, Apple кажется довольно непреклонной в том, что не существует такого понятия, как «статическая структура», и поэтому я бы предпочел не пытаться ее создать, а использовать то, что на самом деле поддерживается.

Dag Ågren 03.01.2023 22:58

Другие вопросы по теме