Как скомпилировать код C в Swift Framework

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

Все, что я могу найти в Интернете о том, как это сделать, является разновидностью этого метода: https://spin.atomicobject.com/2015/02/23/c-libraries-swift/

Но предостережение этого метода: «Обратите внимание, что потребители любых фреймворков, которые вы создаете с использованием этой техники, также должны будут добавить модуль в свои пути поиска Swift». И мы попробовали это, и, конечно же, наши сторонние разработчики получили ошибку Missing required module 'CGeodesic', которая является модулем, который мы определили, указывающим на заголовок C.

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

Редактировать


Проект представляет собой код C бок о бок с кодом Swift. У меня есть подкаталог в моем проекте, который выглядит так:

  • Geodesic/
    • Geodesic.h
    • Geodesic.c
    • Geodesic.swift

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


person user1084447    schedule 08.07.2016    source источник
comment
Какова ваша текущая установка? Код C рядом с кодом Swift?   -  person zneak    schedule 08.07.2016
comment
developer.apple.com/library/ios/documentation/ Свифт/Концептуальный/. Это помогает? Код C — это Objective-C, и похоже, что можно написать фреймворк на смеси C и Swift, и он будет казаться пользователям, как если бы он был написан на одном языке. Я еще не пробовал это сам.   -  person Anatoli P    schedule 13.07.2016
comment
@OmniProg, вот чем мы сейчас занимаемся. Проблема с этим методом заключается в том, что он обязательно раскрывает все эти заголовки публично, чего мы пытаемся избежать.   -  person user1084447    schedule 13.07.2016


Ответы (2)


Просто перефразируя вашу проблему: вы в основном хотите обернуть код C в Swift и предотвратить прямой доступ к инкапсулированному коду C. Это означает запретить доступ как из Swift, так и из Objective-C.

Это возможно, но требует некоторых корректировок процесса сборки и постобработки.

Взаимодействие C и Swift в платформах

Любой код C, который вы хотите обернуть в Swift, должен быть общедоступным во время компиляции либо через заголовок зонтика, либо с помощью карт модулей. Важная часть здесь: во время компиляции. Фреймворки не имеют проверки целостности, что означает, что вы можете применить к ним постобработку (например, создать толстый бинарный фреймворк с lipo). И это то, что нам нужно сделать здесь.

Подготовка сборки

Вместо того, чтобы помещать все зависимости заголовка C в заголовок зонтика, вы также можете поместить их в файл module.modulemap. У этого есть одно преимущество: вы можете сделать свои заголовки приватными на Headers этапе сборки.

Поэтому создайте в своем проекте файл module.modulemap и укажите путь к нему в настройке Module Map File Build в секции Packaging (MODULEMAP_FILE в файлах xcconfig), например, $(SRCROOT)/MyFramework/module.modulemap.

Ваш файл module.modulemap должен иметь следующее содержимое:

# module.modulemap

framework module MyFramework {

    umbrella header "MyFramework.h"  

    export *
    module * {export *}

    # All C Files you want to wrap in your Swift code

    header "A.h"
    header "B.h"
}

Все идет нормально. Теперь вы сможете создавать свой код и получать к нему доступ как из Swift, так и из Objective-C. Единственная проблема: вы все еще можете получить доступ к своим заголовочным файлам C в Swift и Objective-C.

Постобработка

Если вы посмотрите на окончательный пакет Framework, вы заметите, что все ваши личные заголовочные файлы были скопированы в папку MyFramework.framework/PrivateHeaders. Это означает, что вы все еще можете получить к ним доступ через #import <MyFramework/A.h> в Objective-C.

В Swift вы по-прежнему можете получить доступ к коду C, потому что мы поместили файлы заголовков в файл module.modulemap, который был скопирован в MyFramework.framework/Modules/module.modulemap.

К счастью, мы можем просто избавиться от этих двух проблем с помощью постобработки:

  1. Удалите папку PrivateHeaders из пакета фреймворка.
  2. Создайте отдельную карту модуля и удалите все операторы header "XYZ.h", например, назовите ее public.modulemap.
  3. Поместите все это в Run Script этап сборки

Вот общедоступная карта модулей:

# public.modulemap

framework module MyFramework {

    umbrella header "MyFramework.h"  

    export *
    module * {export *}

    # No more C Headers here
}

И скрипт запуска, который вы должны добавить в конце фаз сборки вашего фреймворка:

# Delete PrivateHeaders folder
rm -rf ${TARGET_BUILD_DIR}/${PRODUCT_NAME}${WRAPPER_SUFFIX}/PrivateHeaders

# Remove module.modulemap file
rm ${TARGET_BUILD_DIR}/${PRODUCT_NAME}${WRAPPER_SUFFIX}/Modules/module.modulemap

# Copy public.modulemap file and rename it to module.modulemap
cp ${SRCROOT}/test/public.modulemap ${TARGET_BUILD_DIR}/${PRODUCT_NAME}${WRAPPER_SUFFIX}/Modules/module.modulemap

# Append the Swift module so you can access you Swift code in Objective-C via @import MyFramework.Swift
echo "module ${PRODUCT_NAME}.Swift { header \"${PRODUCT_NAME}-Swift.h\" }" >> ${TARGET_BUILD_DIR}/${PRODUCT_NAME}${WRAPPER_SUFFIX}/Modules/module.modulemap

Надеюсь, это поможет!

person Jens Meder    schedule 14.07.2016
comment
Работает отлично! Благодарю вас! - person user1084447; 14.07.2016
comment
этот ответ, конечно, невероятен, @JensMeder, спасибо. кое-что - возможно, вы могли бы добавить небольшое дополнение для более простого случая, когда совсем нет необходимости предотвращать прямой доступ. Это может прояснить ответ. Держу пари, это было бы полезно для многих. (Все старые статьи по этому поводу на www со временем исчезли! :) ) - person Fattie; 29.05.2017
comment
В Xcode 11 у меня была ошибка сборки Переопределение модуля. Именование файла модуля карты MyFramework.modulemap, а не module.modulemap помогло решить эту проблему. Я также должен был убедиться, что мои заголовки c были указаны как частные, а мой заголовок зонтика был указан как общедоступный на этапе сборки заголовков моей структуры, чтобы избежать ошибки сборки Заголовок зонтика не найден и включить автозаполнение. - person followben; 11.10.2019
comment
Спасибо за ответ. Одна проблема (по крайней мере, для меня), которую я обнаружил, заключается в том, что любая функция, определенная в заголовках C, появится, когда вы нажмете cmd + на имени модуля при использовании фреймворка. Любая идея, как предотвратить это? - person Rui Rodrigues; 09.02.2020

Решение есть, хотя оно и не рекомендуется. Вы можете использовать @_silgen_name внутри файла .Swift вместо использования заголовка C.

person bzz    schedule 14.07.2016