Per mantenere privata una dipendenza inclusa in un plug-in nativo per Mac, compila la sua implementazione con visibilità nascosta ed esporta solo i punti di ingresso pubblici del plug-in al collegamento finale. Applica questi controlli al binario che contiene effettivamente la dipendenza.
Questa guida è destinata a chi sviluppa applicazioni desktop per Mac e crea un plug-in caricato in fase di esecuzione che include una dipendenza C o C++. L’obiettivo concreto è mantenere l’implementazione del plug-in esponendo al contempo una piccola interfaccia rivolta all’applicazione host. Un esempio sintetico compila una dipendenza C in un plug-in C++ e ne verifica sia il risultato sia i simboli esportati.
Quale binario deve avere esportazioni limitate?
Per prima cosa, individua dove finisce il codice macchina della dipendenza. Un archivio statico fornisce file oggetto al binario in fase di collegamento; una libreria dinamica rimane un’immagine separata. Di conseguenza, nascondere i simboli in un plug-in non può riscrivere la tabella delle esportazioni di una dylib separata. Apple descrive questa distinzione nelle Linee guida per l’uso delle librerie dinamiche.
| Configurazione della dipendenza | Dove applicare il controllo | Cosa verificare |
|---|---|---|
| Sorgenti della dipendenza compilati nel plug-in | Compilazione della dipendenza e collegamento finale del plug-in | Le definizioni private sono presenti ma non esportate |
| Archivio statico collegato al plug-in | Compilazione dell’archivio, se possibile, e collegamento finale del plug-in | Sono stati inclusi i membri dell’archivio necessari; le esportazioni della dipendenza sono assenti |
| Dylib della dipendenza separata | Compilazione della dylib della dipendenza | La sua interfaccia pubblica e le importazioni registrate dal plug-in |
| Codice della dipendenza istanziato da intestazioni C++ | Ogni destinazione che compila tali intestazioni | Funzioni inline, template e dati associati generati |
Questa tabella è uno strumento decisionale ricavato dai confini di compilazione e collegamento. Per esempio, un elenco di esportazione associato a una destinazione wrapper non nasconde automaticamente i simboli generati in un’altra destinazione.
Per impostazione predefinita, macOS usa anche uno spazio dei nomi a due livelli: i riferimenti risolti rispetto a una dylib registrano quale libreria li ha forniti. Lo spazio dei nomi piatto modifica questo comportamento. Le definizioni esterne deboli introducono un’ulteriore considerazione, perché il loader può accorparle. Apple documenta entrambi i meccanismi nel manuale del linker. La sola corrispondenza dei nomi, quindi, non chiarisce perché un plug-in abbia chiamato un’implementazione inattesa.
Combina la compilazione con visibilità nascosta con un elenco esplicito delle esportazioni
I controlli del compilatore e del linker agiscono in fasi diverse. Clang usa -fvisibility=hidden per impostare la visibilità predefinita delle definizioni globali nei file sorgente compilati. Non ricompila un archivio esistente. Clang documenta separatamente -fvisibility-inlines-hidden per le funzioni membro inline in C++; considerala un’opzione mirata la cui adeguatezza dipende dall’interfaccia. Consulta la documentazione di riferimento della riga di comando di Clang.
Contrassegna i punti di ingresso pubblici previsti con __attribute__((visibility("default"))). Gli attributi di visibilità possono sovrascrivere l’impostazione predefinita della compilazione, quindi controlla nelle intestazioni della dipendenza la presenza di annotazioni di esportazione esplicite. Apple spiega questo comportamento in Controllo della visibilità dei simboli.
Al collegamento finale, usa -exported_symbols_list per elencare l’interfaccia pubblica. Il formato documentato da Apple per l’elenco delle esportazioni usa i nomi dei simboli del linker, incluso il trattino basso iniziale per i simboli C. Scegli un elenco delle esportazioni oppure un elenco dei simboli non esportati, anziché combinare entrambe le strategie. Consulta le Linee guida per la progettazione di librerie dinamiche.
Per una piccola interfaccia di plug-in, un elenco esplicito delle esportazioni rende la revisione semplice: ogni nome esportato deve avere una ragione per essere pubblico. Mantieni coerenti le annotazioni nel sorgente e l’elenco: l’annotazione identifica l’API nel codice e l’elenco vincola il binario finale.
Crea un esempio locale completo
L’esempio usa volutamente una dipendenza C: mostra il controllo delle esportazioni Mach-O senza presentare definizioni C++ incompatibili come tecnica di linguaggio portabile. La dipendenza dell’host restituisce 10; la compilazione privata del plug-in restituisce 20. Questi valori sono a scopo illustrativo.
Esegui i blocchi seguenti nell’ordine indicato, nella stessa shell di Terminale su un Mac con gli Xcode Command Line Tools disponibili. Creano una nuova directory temporanea e compilano per l’architettura predefinita della toolchain attiva. L’esempio è stato esaminato, ma non compilato né eseguito durante questa ricerca, perché l’ambiente disponibile era Linux e non includeva la toolchain Apple.
Crea la dipendenza e il plug-in
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
L’intestazione della dipendenza fornisce al wrapper C++ la dichiarazione corretta di collegamento al linguaggio C. Anche il wrapper pubblico usa il collegamento C, che mantiene semplice il nome del linker per questo punto di ingresso. In genere i nomi C++ richiedono il name mangling; Apple illustra questa distinzione in Controllo della visibilità dei simboli.
Crea l’host
L’host carica il plug-in e recupera la funzione pubblica tramite l’handle della libreria restituito. Apple documenta il supporto di dlopen sia per le librerie dinamiche sia per i bundle Mach-O. L’opzione RTLD_LOCAL limita la disponibilità dei simboli esportati dall’immagine caricata; non sostituisce i controlli di visibilità in fase di compilazione. Consulta il manuale dlopen di Apple.
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
Compila e collega
# 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
L’output previsto è host=10 plugin=20. L’host esporta esplicitamente il proprio simbolo di dipendenza affinché l’esempio includa un’implementazione visibile esternamente con lo stesso nome C. Il plug-in deve comunque contenere la propria implementazione privata.
Questo è un esempio di verifica, non l’affermazione che rimuovere i controlli comporti necessariamente un errore. Il binding Mach-O predefinito può già distinguere le definizioni in molte configurazioni. I controlli utili riguardano l’implementazione selezionata e la superficie delle esportazioni finali.
Verifica separatamente le esportazioni e le importazioni
Esegui questi comandi nella directory dell’esempio:
xcrun nm -g -U -j DemoPlugin.dylib
xcrun dyld_info -exports DemoPlugin.dylib
xcrun dyld_info -imports DemoPlugin.dylib
xcrun otool -L DemoPlugin.dylib
Nel primo comando, -g seleziona i simboli esterni, -U esclude quelli non definiti e -j stampa i nomi. Per questo plug-in minimale, il nome esterno definito previsto è _demo_plugin_value; _demo_dependency_value dovrebbe essere assente. Il manuale nm di Apple documenta queste opzioni e spiega che le informazioni nella tabella dei simboli possono differire da quelle usate dal loader.
Usa dyld_info -exports per esaminare le esportazioni visibili al loader e -imports per esaminare i simboli importati. La funzione della dipendenza non dovrebbe comparire in nessuno dei due elenchi: è implementata privatamente all’interno di questo plug-in. Queste opzioni di ispezione sono documentate nel manuale dyld_info di Apple.
Usa otool -L per controllare le dipendenze effettive dalle dylib. L’archivio statico non dovrebbe comparire come dipendenza separata in fase di esecuzione. Apple documenta questa ispezione delle dipendenze nelle Linee guida per l’uso delle librerie dinamiche.
Per un plug-in reale, tieni separate tre domande: l’implementazione necessaria è inclusa? È esportata? Ci sono chiamate che importano ancora un’implementazione concorrente? Il solo fatto che la compilazione riesca non risponde a nessuna di queste domande.
Applica gli stessi controlli in Xcode
Per la destinazione che produce il binario finale del plug-in, imposta Exported Symbols File (EXPORTED_SYMBOLS_FILE) sul percorso relativo al progetto dell’elenco delle esportazioni. Abilita Symbols Hidden by Default (GCC_SYMBOLS_PRIVATE_EXTERN) nelle destinazioni C e C++ che compilano l’implementazione privata. L’attuale documentazione di riferimento delle impostazioni di compilazione di Apple descrive entrambe le impostazioni.
Se la dipendenza è una destinazione di libreria statica separata, configura anche quella. Modificare solo le impostazioni del compilatore del plug-in lascia intatti gli oggetti della dipendenza compilati in precedenza. Ricompila le destinazioni interessate e ispeziona il prodotto risultante per ogni architettura che distribuisci.
Per un archivio precompilato, l’elenco finale delle esportazioni resta utile. Il manuale del linker pubblicato da Apple documenta anche -load_hidden, che tratta i simboli globali di un archivio come nascosti mantenendo la normale estrazione dell’archivio. Quel manuale è per ld-classic; controlla la documentazione del linker installato prima di scegliere questa opzione aggiuntiva. -force_load controlla l’inclusione dei membri dell’archivio, una questione distinta. Consulta il manuale del linker Apple.
Mantieni i requisiti delle interfacce C++ e Swift
La visibilità C++ non risolve definizioni incompatibili
La regola della definizione unica in C++ impone requisiti alle definizioni presenti in più unità di traduzione, inclusi requisiti sulla corrispondenza dei token e sulla ricerca dei nomi nei casi in cui sono ammesse più definizioni. Nascondere un simbolo non autorizza a combinare definizioni C++ in conflitto. Per implementazioni con versioni indipendenti, quando possibile usa namespace o nomi di simboli distinti. Consulta la regola della definizione unica nella bozza di lavoro C++.
Mantieni inoltre la visibilità necessaria per un’interfaccia C++ intenzionale. Apple segnala che l’RTTI tra librerie, le eccezioni, i cast dinamici, l’identità degli indirizzi delle funzioni inline e gli oggetti statici condivisi sono casi che richiedono attenzione. Una soluzione pratica consiste nel mantenere i tipi e la gestione della dipendenza all’interno del plug-in ed esporre una piccola interfaccia che usi valori scalari concordati o handle opachi. Consulta Controllo della visibilità dei simboli.
L’accesso all’importazione Swift controlla un confine diverso
L’internal import di Swift limita i contesti in cui è possibile fare riferimento alle dichiarazioni importate. SE-0409, implementato in Swift 6.0, consiglia di sostituire gli usi di @_implementationOnly import con un’importazione internal o di livello inferiore. Non definisce un meccanismo per rinominare le dipendenze Mach-O o filtrare le esportazioni. Continua a configurare e ispezionare separatamente i binari nativi. Consulta Modificatori del livello di accesso nelle dichiarazioni di importazione.
Se usi l’interoperabilità diretta tra Swift e C++, mantieni i relativi vincoli della toolchain: il supporto è disponibile a partire da Swift 5.9, il codice misto deve usare la stessa libreria standard C++ e le piattaforme Apple usano libc++. I tipi di riferimento C++ importati impongono inoltre una versione minima di distribuzione pari a macOS 13.3. Il filtraggio delle esportazioni non modifica nessuno di questi requisiti. Consulta Funzionalità supportate e vincoli dell’interoperabilità C++.
Un plug-in è pronto per i test d’integrazione quando i punti di ingresso previsti sono ancora disponibili, le definizioni private della dipendenza sono assenti dalle esportazioni del loader e le importazioni corrispondono alle librerie che dovrebbe usare. Affianca questi controlli a un test comportamentale come l’esempio sintetico, così gli aggiornamenti successivi della dipendenza rivelano sia le modifiche all’interfaccia sia quelle nella selezione dell’implementazione.
Fonti
- Documentazione di riferimento delle impostazioni di compilazione — Apple.
- Linee guida per la progettazione di librerie dinamiche — Apple.
- Linee guida per l’uso delle librerie dinamiche — Apple.
- Controllo della visibilità dei simboli — Apple.
- Documentazione di riferimento degli argomenti della riga di comando di Clang — LLVM Project.
- Manuale ld-classic(1) — Apple.
- Manuale dlopen(3) — Apple.
- Manuale nm(1) — Apple.
- Manuale dyld_info(1) — Apple.
- Regola della definizione unica — Bozza di lavoro C++, resa da Tim Song.
- Modificatori del livello di accesso nelle dichiarazioni di importazione — Swift Project.
- Funzionalità supportate e vincoli dell’interoperabilità C++ — Swift.org.