Untuk memastikan kebergantungan terbina dalam pemalam Mac asli kekal peribadi, kompilkan pelaksanaannya dengan keterlihatan tersembunyi dan eksport hanya titik masuk awam pemalam pada pautan akhir. Gunakan kawalan ini pada binari yang benar-benar mengandungi kebergantungan tersebut.
Panduan ini ditujukan kepada pembangun desktop Mac yang membina pemalam yang dimuatkan semasa masa jalan dan merangkumi kebergantungan C atau C++. Tugas khususnya ialah mengekalkan pelaksanaan pemalam itu sendiri sambil mendedahkan antara muka kecil untuk hos. Contoh sintetik membina kebergantungan C ke dalam pemalam C++ dan menyemak hasil serta simbol yang dieksport.
Binari manakah yang perlu eksportnya dihadkan?
Mulakan dengan mengenal pasti tempat kod mesin kebergantungan itu berakhir. Arkib statik membekalkan fail objek kepada binari yang sedang dipautkan; pustaka dinamik kekal sebagai imej yang berasingan. Oleh itu, menyembunyikan simbol dalam pemalam tidak dapat mengubah jadual eksport dylib kebergantungan yang berasingan. Apple menerangkan perbezaan ini dalam Dynamic Library Usage Guidelines.
| Susunan kebergantungan | Tempat kawalan digunakan | Perkara yang perlu disahkan |
|---|---|---|
| Sumber kebergantungan dikompilkan ke dalam pemalam | Kompilasi kebergantungan dan pautan akhir pemalam | Takrif peribadi wujud tetapi tidak dieksport |
| Arkib statik dipautkan ke dalam pemalam | Kompilasi arkib jika boleh, serta pautan akhir pemalam | Ahli arkib yang diperlukan disertakan; eksport kebergantungan tiada |
| Dylib kebergantungan yang berasingan | Binaan dylib kebergantungan itu sendiri | Antara muka awamnya dan import yang direkodkan oleh pemalam |
| Kod kebergantungan diterbitkan daripada pengepala C++ | Setiap sasaran yang mengkompilkan pengepala tersebut | Fungsi sebaris, templat dan data berkaitan yang dijana |
Jadual ini ialah panduan keputusan yang dirumus daripada sempadan kompilasi dan pemautan tersebut. Contohnya, senarai eksport yang dilampirkan pada sasaran pembalut tidak secara automatik menyembunyikan simbol yang dijana ke dalam sasaran lain.
macOS juga menggunakan ruang nama dua aras secara lalai: rujukan yang diselesaikan terhadap dylib merekodkan pustaka yang membekalkannya. Ruang nama rata mengubah tingkah laku itu. Takrif luaran lemah menimbulkan pertimbangan lain kerana pemuat boleh menggabungkannya. Apple mendokumenkan kedua-dua mekanisme dalam manual pemautnya. Oleh itu, nama yang sepadan sahaja tidak membuktikan sebab pemalam memanggil pelaksanaan yang tidak dijangka.
Gabungkan kompilasi tersembunyi dengan senarai eksport yang jelas
Kawalan pengkompil dan pemaut berfungsi pada peringkat yang berbeza. -fvisibility=hidden Clang menetapkan keterlihatan lalai bagi takrif global dalam fail sumber yang sedang dikompilkan. Ia tidak mengkompilkan semula arkib sedia ada. Clang mendokumenkan secara berasingan -fvisibility-inlines-hidden untuk fungsi ahli C++ sebaris; anggap ini sebagai pilihan khusus yang kesesuaiannya bergantung pada antara muka. Lihat rujukan baris perintah Clang.
Tandakan titik masuk awam yang dimaksudkan dengan __attribute__((visibility("default"))). Atribut keterlihatan boleh mengatasi tetapan lalai kompilasi, jadi periksa pengepala kebergantungan untuk anotasi eksport yang jelas. Apple menerangkan tingkah laku ini dalam Controlling Symbol Visibility.
Pada pautan akhir, gunakan -exported_symbols_list untuk menyenaraikan antara muka awam. Format senarai eksport yang didokumenkan oleh Apple menggunakan nama simbol pemaut, termasuk garis bawah di hadapan bagi simbol C. Pilih senarai eksport atau senarai simbol yang tidak dieksport; jangan gabungkan kedua-dua strategi. Lihat Dynamic Library Design Guidelines.
Bagi antara muka pemalam yang kecil, senarai eksport yang jelas memudahkan semakan: setiap nama yang dieksport mesti mempunyai alasan untuk dijadikan awam. Pastikan anotasi sumber dan senarai tersebut selaras; anotasi mengenal pasti API dalam kod, manakala senarai mengehadkan binari akhir.
Bina contoh setempat yang lengkap
Contoh ini sengaja menggunakan kebergantungan C: ia menunjukkan kawalan eksport Mach-O tanpa mengemukakan takrif C++ yang tidak serasi sebagai teknik bahasa mudah alih. Kebergantungan hos memulangkan 10; binaan peribadi pemalam memulangkan 20. Nilai ini hanyalah input ilustrasi.
Jalankan blok berikut mengikut turutan dalam shell Terminal yang sama pada Mac dengan Xcode Command Line Tools tersedia. Blok ini mencipta direktori sementara yang baharu dan membina untuk seni bina lalai toolchain yang aktif. Contoh ini telah diperiksa tetapi tidak dikompilkan atau dijalankan semasa penyelidikan ini kerana persekitaran yang tersedia ialah Linux tanpa toolchain Apple.
Cipta kebergantungan dan pemalam
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
Pengepala kebergantungan memberikan pengisytiharan pautan bahasa C yang betul kepada pembalut C++. Pembalut awam juga menggunakan pautan C supaya nama pemaut titik masuk ini kekal ringkas. Nama C++ biasanya memerlukan pemangling; Apple membincangkan perbezaan itu dalam Controlling Symbol Visibility.
Cipta hos
Hos memuatkan pemalam dan mendapatkan fungsi awamnya melalui pemegang pustaka yang dipulangkan. Apple mendokumenkan sokongan dlopen untuk pustaka dinamik dan bundle Mach-O. Pilihannya RTLD_LOCAL mengehadkan ketersediaan simbol yang dieksport oleh imej yang dimuatkan; pilihan itu tidak menggantikan kawalan keterlihatan semasa binaan. Lihat manual dlopen 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
Kompil dan pautkan
# 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
Output yang dijangka ialah host=10 plugin=20. Hos mengeksport simbol kebergantungannya secara jelas supaya contoh ini merangkumi pelaksanaan yang boleh dilihat secara luaran dengan nama C yang sama. Pemalam itu tetap perlu mengandungi pelaksanaan peribadinya sendiri.
Ini ialah contoh pengesahan, bukannya dakwaan bahawa membuang kawalan semestinya menyebabkan hasilnya gagal. Pengikatan Mach-O lalai sudah boleh membezakan takrif dalam banyak susunan. Semakan yang berguna ialah pelaksanaan yang dipilih dan permukaan eksport akhir.
Sahkan eksport dan import secara berasingan
Jalankan arahan ini dalam direktori contoh:
xcrun nm -g -U -j DemoPlugin.dylib
xcrun dyld_info -exports DemoPlugin.dylib
xcrun dyld_info -imports DemoPlugin.dylib
xcrun otool -L DemoPlugin.dylib
Dalam arahan pertama, -g memilih simbol luaran, -U mengecualikan simbol tidak ditakrifkan dan -j mencetak nama. Bagi pemalam minimum ini, nama luaran ditakrifkan yang dijangka ialah _demo_plugin_value; _demo_dependency_value sepatutnya tiada. Manual nm Apple mendokumenkan pilihan ini dan menjelaskan bahawa maklumat jadual simbol mungkin berbeza daripada maklumat yang digunakan oleh pemuat.
Gunakan dyld_info -exports untuk memeriksa eksport yang dilihat pemuat dan -imports untuk memeriksa simbol yang diimport. Fungsi kebergantungan sepatutnya tidak muncul dalam mana-mana senarai: fungsinya dilaksanakan secara peribadi di dalam pemalam ini. Pilihan pemeriksaan ini didokumenkan dalam manual dyld_info Apple.
Gunakan otool -L untuk menyemak kebergantungan dylib sebenar. Arkib statik tidak sepatutnya muncul sebagai kebergantungan masa jalan yang berasingan. Apple mendokumenkan pemeriksaan kebergantungan ini dalam Dynamic Library Usage Guidelines.
Bagi pemalam sebenar, asingkan tiga soalan: Adakah pelaksanaan yang diperlukan disertakan? Adakah pelaksanaan itu dieksport? Adakah sebarang panggilan masih mengimport pelaksanaan lain yang bersaing? Binaan yang berjaya sahaja tidak menjawab mana-mana soalan ini.
Gunakan kawalan yang sama dalam Xcode
Bagi sasaran yang menghasilkan binari akhir pemalam, tetapkan Exported Symbols File (EXPORTED_SYMBOLS_FILE) kepada laluan relatif projek bagi senarai eksport. Dayakan Symbols Hidden by Default (GCC_SYMBOLS_PRIVATE_EXTERN) pada sasaran C dan C++ yang mengkompilkan pelaksanaan peribadi. Rujukan tetapan binaan Apple yang terkini mendokumenkan kedua-dua tetapan ini.
Jika kebergantungan ialah sasaran pustaka statik yang berasingan, konfigurasikan sasaran itu juga. Mengubah tetapan pengkompil pemalam sahaja tidak mengubah objek kebergantungan yang telah dikompilkan. Bina semula sasaran yang terjejas dan periksa produk yang terhasil bagi setiap seni bina yang anda edarkan.
Bagi arkib prabina, senarai eksport akhir tetap berguna. Manual pemaut terbitan Apple juga mendokumenkan -load_hidden untuk menganggap global arkib sebagai tersembunyi sambil menggunakan pengekstrakan arkib biasa. Manual itu adalah untuk ld-classic; semak dokumentasi pemaut yang dipasang sebelum memilih pilihan tambahan ini. -force_load mengawal penyertaan ahli arkib, iaitu isu yang berasingan. Lihat manual pemaut Apple.
Kekalkan keperluan antara muka C++ dan Swift
Keterlihatan C++ tidak membetulkan takrif yang tidak serasi
Peraturan satu takrif C++ menetapkan keperluan bagi takrif merentas unit terjemahan, termasuk keperluan padanan token dan pencarian nama apabila berbilang takrif dibenarkan. Menyembunyikan simbol bukan kebenaran untuk menggabungkan takrif C++ yang bercanggah. Bagi pelaksanaan yang mempunyai versi berasingan, gunakan ruang nama atau nama simbol yang berbeza jika boleh. Lihat peraturan satu takrif dalam draf kerja C++.
Kekalkan juga keterlihatan yang diperlukan oleh antara muka C++ yang disengajakan. Apple mengenal pasti RTTI merentas pustaka, pengecualian, pelakon dinamik, identiti alamat fungsi sebaris dan objek statik yang dikongsi sebagai perkara yang perlu diberi perhatian. Reka bentuk praktikal ialah mengekalkan jenis kebergantungan dan pemilikannya di dalam pemalam, lalu mendedahkan antara muka kecil menggunakan nilai skalar yang dipersetujui atau pemegang legap. Lihat Controlling Symbol Visibility.
Akses import Swift mengawal sempadan yang berbeza
internal import Swift mengehadkan tempat pengisytiharan yang diimport boleh dirujuk. SE-0409, yang dilaksanakan dalam Swift 6.0, mengesyorkan agar penggunaan @_implementationOnly import digantikan dengan import dalaman atau yang lebih rendah. Ia tidak mentakrifkan mekanisme penamaan semula kebergantungan Mach-O atau penapisan eksport. Teruskan mengkonfigurasi dan memeriksa binari asli secara berasingan. Lihat Pengubah tahap akses pada pengisytiharan import.
Jika menggunakan saling kendali Swift/C++ secara langsung, kekalkan kekangan toolchainnya: sokongan bermula dengan Swift 5.9, kod campuran mesti menggunakan pustaka standard C++ yang sama, dan platform Apple menggunakan libc++. Jenis rujukan C++ yang diimport juga mengenakan versi penggunaan minimum macOS 13.3. Penapisan eksport tidak mengubah mana-mana keperluan ini. Lihat Ciri dan Kekangan yang Disokong bagi Saling Kendali C++.
Pemalam sedia untuk ujian integrasi apabila titik masuk yang dimaksudkan masih tersedia, takrif kebergantungan peribadinya tiada daripada eksport pemuat, dan importnya sepadan dengan pustaka yang sepatutnya digunakan. Kekalkan semakan tersebut bersama ujian tingkah laku seperti contoh sintetik supaya kemas kini kebergantungan kemudian dapat mendedahkan perubahan antara muka dan perubahan pemilihan pelaksanaan.
Sumber
- Rujukan tetapan binaan — Apple.
- Dynamic Library Design Guidelines — Apple.
- Dynamic Library Usage Guidelines — Apple.
- Controlling Symbol Visibility — Apple.
- Rujukan argumen baris perintah Clang — LLVM Project.
- Manual ld-classic(1) — Apple.
- Manual dlopen(3) — Apple.
- Manual nm(1) — Apple.
- Manual dyld_info(1) — Apple.
- Peraturan satu takrif — draf kerja C++, dipaparkan oleh Tim Song.
- Pengubah tahap akses pada pengisytiharan import — Swift Project.
- Ciri dan Kekangan yang Disokong bagi Saling Kendali C++ — Swift.org.