Om een meegeleverde dependency van een native Mac-plug-in privé te houden, compileer je de implementatie met verborgen zichtbaarheid en exporteer je bij de uiteindelijke link alleen de openbare entrypoints van de plug-in. Pas deze instellingen toe op het binaire bestand dat de dependency daadwerkelijk bevat.

Deze gids is bedoeld voor een Mac-desktopontwikkelaar die een tijdens runtime geladen plug-in bouwt met een C- of C++-dependency. Het concrete doel is de eigen implementatie van de plug-in te behouden en tegelijk een kleine interface voor de host beschikbaar te maken. Een synthetisch voorbeeld bouwt een C-dependency in een C++-plug-in en controleert zowel het resultaat als de geëxporteerde symbolen.

Van welk binair bestand moeten de exports worden beperkt?

Begin met vaststellen waar de machinecode van de dependency terechtkomt. Een statisch archief levert objectbestanden aan het binaire bestand dat wordt gelinkt; een dynamische bibliotheek blijft een afzonderlijke image. Daarom kan het verbergen van symbolen in een plug-in de exporttabel van een afzonderlijke dependency-dylib niet herschrijven. Apple beschrijft dit onderscheid in de richtlijnen voor het gebruik van dynamische bibliotheken.

Indeling van dependency Waar de instelling toepassen Wat controleren
Dependency-broncode gecompileerd in de plug-in Dependency-compilatie en uiteindelijke plug-inlink Privédefinities zijn aanwezig maar niet geëxporteerd
Statisch archief gelinkt in de plug-in Archiefcompilatie indien mogelijk, plus uiteindelijke plug-inlink Vereiste archiefleden zijn opgenomen; dependency-exports ontbreken
Afzonderlijke dependency-dylib Eigen build van de dependency-dylib De openbare interface en de vastgelegde imports van de plug-in
Dependency-code geïnstantieerd vanuit C++-headers Elk doel dat die headers compileert Gegenereerde inlinefuncties, templates en bijbehorende data

Deze tabel helpt bij het nemen van beslissingen op basis van de grenzen tussen compilatie en linken. Een exportlijst die bijvoorbeeld aan een wrapperdoel is gekoppeld, verbergt niet automatisch symbolen die in een ander doel worden gegenereerd.

macOS gebruikt standaard ook een namespace op twee niveaus: verwijzingen die tegen een dylib worden opgelost, registreren welke bibliotheek ze heeft geleverd. De flat namespace verandert dat gedrag. Weak external-definities vormen een extra aandachtspunt, omdat de loader ze kan samenvoegen. Apple documenteert beide mechanismen in de linkerhandleiding. Alleen overeenkomende namen bewijzen dus niet waarom een plug-in een onverwachte implementatie aanriep.

Combineer verborgen compilatie met een expliciete exportlijst

De compiler- en linkerinstellingen werken in verschillende fasen. Clangs -fvisibility=hidden stelt de standaardzichtbaarheid in van globale definities in de bronbestanden die worden gecompileerd. Bestaande archieven worden er niet opnieuw mee gecompileerd. Clang documenteert afzonderlijk -fvisibility-inlines-hidden voor inline-C++-memberfuncties; behandel dit als een gerichte optie waarvan de geschiktheid afhangt van de interface. Zie de Clang-referentie voor opdrachtregelopties.

Markeer de beoogde openbare entrypoints met __attribute__((visibility("default"))). Zichtbaarheidsattributen kunnen de standaardinstelling voor compilatie overschrijven, dus controleer dependency-headers op expliciete exportannotaties. Apple legt dit gedrag uit in symbolen zichtbaarheid geven.

Gebruik bij de uiteindelijke link -exported_symbols_list om de openbare interface op te sommen. Apples gedocumenteerde indeling voor exportlijsten gebruikt linker-symboolnamen, inclusief de voorloopunderscore voor C-symbolen. Kies een exportlijst of een lijst met niet-geëxporteerde symbolen; combineer de twee strategieën niet. Zie richtlijnen voor het ontwerpen van dynamische bibliotheken.

Voor een kleine plug-ininterface maakt een expliciete exportlijst de beoordeling eenvoudig: voor elke geëxporteerde naam moet een reden zijn om die openbaar te maken. Houd de broncodeannotatie en de lijst consistent; de annotatie identificeert de API in de code en de lijst beperkt het uiteindelijke binaire bestand.

Bouw een volledig lokaal voorbeeld

Het voorbeeld gebruikt bewust een C-dependency: zo wordt Mach-O-exportbeheer gedemonstreerd zonder incompatibele C++-definities voor te stellen als een draagbare taalkundige techniek. De dependency van de host retourneert 10; de privébuild van de plug-in retourneert 20. Deze waarden zijn illustratieve invoer.

Voer de volgende blokken in volgorde uit in dezelfde Terminal-shell op een Mac waarop de Xcode Command Line Tools beschikbaar zijn. Ze maken een nieuwe tijdelijke map aan en bouwen voor de standaardarchitectuur van de actieve toolchain. Het voorbeeld is gecontroleerd, maar tijdens dit onderzoek niet gecompileerd of uitgevoerd, omdat de beschikbare omgeving Linux was en de toolchain van Apple ontbrak.

Maak de dependency en plug-in aan

set -eu
example_dir=$(mktemp -d)
cd "$example_dir"

cat > dependency.h <<'EOF'
#ifndef DEMO_DEPENDENCY_H
#define DEMO_DEPENDENCY_H
#ifdef __cplusplus
extern "C" {
#endif
int demo_dependency_value(void);
#ifdef __cplusplus
}
#endif
#endif
EOF

cat > dependency.c <<'EOF'
#include "dependency.h"
#ifndef DEP_VALUE
#error DEP_VALUE must be supplied at compilation
#endif
int demo_dependency_value(void) {
    return DEP_VALUE;
}
EOF

cat > plugin.cpp <<'EOF'
#include "dependency.h"

extern "C" __attribute__((visibility("default")))
int demo_plugin_value(void) {
    return demo_dependency_value();
}
EOF

cat > exports.txt <<'EOF'
_demo_plugin_value
EOF

De dependency-header geeft de C++-wrapper de juiste declaratie voor C-taalkoppeling. De openbare wrapper gebruikt ook C-koppeling, zodat de linkernaam van dit entrypoint eenvoudig blijft. C++-namen vereisen doorgaans naamvervorming; Apple bespreekt dat onderscheid in symbolen zichtbaarheid geven.

Maak de host aan

De host laadt de plug-in en haalt de openbare functie op via de geretourneerde bibliotheekhandle. Apple documenteert dat dlopen zowel voor dynamische bibliotheken als voor Mach-O-bundles wordt ondersteund. De optie RTLD_LOCAL beperkt de beschikbaarheid van de geëxporteerde symbolen van de geladen image; deze vervangt de zichtbaarheidinstellingen tijdens het bouwen niet. Zie Apples dlopen-handleiding.

cat > host.cpp <<'EOF'
#include "dependency.h"
#include <cstdio>
#include <dlfcn.h>

int main() {
    void* handle = dlopen("./DemoPlugin.dylib", RTLD_NOW | RTLD_LOCAL);
    if (!handle) {
        std::fprintf(stderr, "dlopen: %s\n", dlerror());
        return 1;
    }

    dlerror();
    void* address = dlsym(handle, "demo_plugin_value");
    const char* error = dlerror();
    if (error || !address) {
        std::fprintf(stderr, "dlsym: %s\n",
                     error ? error : "null function address");
        dlclose(handle);
        return 1;
    }

    using PluginValue = int (*)(void);
    PluginValue plugin_value = reinterpret_cast<PluginValue>(address);
    const int host_result = demo_dependency_value();
    const int plugin_result = plugin_value();
    std::printf("host=%d plugin=%d\n", host_result, plugin_result);

    const bool correct = host_result == 10 && plugin_result == 20;
    if (dlclose(handle) != 0) {
        std::fprintf(stderr, "dlclose: %s\n", dlerror());
        return 1;
    }
    return correct ? 0 : 2;
}
EOF

Compileer en link

# Build the plug-in's dependency with hidden definitions.
xcrun clang -fvisibility=hidden -DDEP_VALUE=20 \
  -c dependency.c -o plugin_dependency.o
xcrun ar rcs libPluginDependency.a plugin_dependency.o

# Compile the public wrapper, then constrain the final exports.
xcrun clang++ -std=c++17 -fvisibility=hidden \
  -c plugin.cpp -o plugin.o
xcrun clang++ -dynamiclib plugin.o libPluginDependency.a \
  -Wl,-exported_symbols_list,exports.txt \
  -o DemoPlugin.dylib

# Give the host a separately compiled implementation.
xcrun clang -DDEP_VALUE=10 -c dependency.c -o host_dependency.o
xcrun clang++ -std=c++17 host.cpp host_dependency.o \
  -Wl,-exported_symbol,_demo_dependency_value -o host

./host

De verwachte uitvoer is host=10 plugin=20. De host exporteert zijn dependency-symbool expliciet, zodat het voorbeeld een extern zichtbare implementatie met dezelfde C-naam bevat. De plug-in moet nog steeds zijn privé-implementatie bevatten.

Dit is een verificatievoorbeeld, geen bewering dat het resultaat noodzakelijkerwijs faalt wanneer de instellingen worden verwijderd. De standaardbinding van Mach-O kan definities in veel indelingen al van elkaar onderscheiden. De nuttige controles zijn de geselecteerde implementatie en het uiteindelijke exportoppervlak.

Controleer exports en imports afzonderlijk

Voer deze opdrachten uit in de voorbeeldmap:

xcrun nm -g -U -j DemoPlugin.dylib
xcrun dyld_info -exports DemoPlugin.dylib
xcrun dyld_info -imports DemoPlugin.dylib
xcrun otool -L DemoPlugin.dylib

Bij de eerste opdracht selecteert -g externe symbolen, sluit -U ongedefinieerde symbolen uit en geeft -j namen weer. Voor deze minimale plug-in is de verwachte gedefinieerde externe naam _demo_plugin_value; _demo_dependency_value hoort afwezig te zijn. Apples nm-handleiding documenteert deze opties en legt uit dat informatie uit de symbolentabel kan verschillen van de informatie die de loader gebruikt.

Gebruik dyld_info -exports om exports te controleren die zichtbaar zijn voor de loader en -imports om geïmporteerde symbolen te controleren. De dependencyfunctie hoort in geen van beide lijsten te staan: deze wordt privé in de plug-in geïmplementeerd. Deze inspectieopties staan beschreven in Apples dyld_info-handleiding.

Gebruik otool -L om de daadwerkelijke dylib-afhankelijkheden te controleren. Het statische archief hoort niet als afzonderlijke runtime-afhankelijkheid te verschijnen. Apple documenteert deze inspectie van afhankelijkheden in de richtlijnen voor het gebruik van dynamische bibliotheken.

Houd voor een echte plug-in drie vragen uit elkaar: Is de vereiste implementatie opgenomen? Wordt deze geëxporteerd? Importeert een aanroep nog steeds een concurrerende implementatie? Een geslaagde build beantwoordt op zichzelf geen van deze vragen.

Pas dezelfde instellingen toe in Xcode

Stel voor het doel dat het uiteindelijke plug-inbinaire bestand produceert Exported Symbols File (EXPORTED_SYMBOLS_FILE) in op het projectspecifieke pad van de exportlijst. Schakel Symbols Hidden by Default (GCC_SYMBOLS_PRIVATE_EXTERN) in voor de C- en C++-doelen die de privé-implementatie compileren. Apples actuele referentie voor buildinstellingen documenteert beide instellingen.

Als de dependency een afzonderlijk doel voor een statische bibliotheek is, configureer dan ook dat doel. Alleen de compilerinstellingen van de plug-in wijzigen laat eerder gecompileerde dependency-objecten ongemoeid. Bouw de betreffende doelen opnieuw en inspecteer het resulterende product voor elke architectuur die je distribueert.

Voor een vooraf gebouwd archief blijft de uiteindelijke exportlijst nuttig. Apples gepubliceerde linkerhandleiding documenteert ook -load_hidden, waarmee de globale symbolen van een archief als verborgen worden behandeld terwijl de normale extractie van archiefleden wordt gebruikt. Die handleiding is voor ld-classic; controleer de documentatie van de geïnstalleerde linker voordat je deze extra optie kiest. -force_load regelt welke archiefleden worden opgenomen; dat is een afzonderlijke kwestie. Zie de linkerhandleiding van Apple.

Behoud de interfacevereisten van C++ en Swift

C++-zichtbaarheid herstelt geen incompatibele definities

De one-definition rule van C++ stelt eisen aan definities in verschillende vertaaleenheden, waaronder vereisten voor overeenkomende tokens en naamopzoeking wanneer meerdere definities zijn toegestaan. Het verbergen van een symbool geeft geen toestemming om conflicterende C++-definities te combineren. Gebruik waar mogelijk afzonderlijke namespaces of symboolnamen voor onafhankelijk geversioneerde implementaties. Zie de one-definition rule in het C++-werkconcept.

Behoud ook de zichtbaarheid die nodig is voor een bewuste C++-interface. Apple noemt RTTI tussen bibliotheken, uitzonderingen, dynamische casts, adresidentiteit van inlinefuncties en gedeelde statische objecten als gevallen die aandacht vereisen. Een praktisch ontwerp houdt dependencytypen en eigenaarschap binnen de plug-in en biedt een kleine interface met afgesproken scalaire waarden of ondoorzichtige handles. Zie symbolen zichtbaarheid geven.

Swift-importtoegang bepaalt een andere grens

Swift's internal import beperkt waar geïmporteerde declaraties mogen worden gebruikt. SE-0409, geïmplementeerd in Swift 6.0, raadt aan het gebruik van @_implementationOnly import te vervangen door een interne import of lager. Het definieert geen mechanisme voor het hernoemen van Mach-O-afhankelijkheden of het filteren van exports. Blijf native binaire bestanden afzonderlijk configureren en inspecteren. Zie toegangsmodifiers voor importdeclaraties.

Behoud bij directe interoperabiliteit tussen Swift en C++ de beperkingen van de toolchain: ondersteuning begint bij Swift 5.9, gemengde code moet dezelfde C++-standaardbibliotheek gebruiken en Apple-platforms gebruiken libc++. Voor geïmporteerde C++-referentietypen geldt ook macOS 13.3 als minimale implementatieversie. Exportfiltering verandert geen van deze vereisten. Zie ondersteunde functies en beperkingen van C++-interoperabiliteit.

Een plug-in is klaar voor integratietests wanneer de beoogde entrypoints beschikbaar blijven, de privédependencydefinities ontbreken in de loader-exports en de imports overeenkomen met de bibliotheken die de plug-in hoort te gebruiken. Houd deze controles naast een gedragstest zoals het synthetische voorbeeld, zodat latere dependency-updates zowel interfacewijzigingen als wijzigingen in de geselecteerde implementatie aan het licht brengen.

Bronnen