diff --git a/.github/workflows/gnustep.yml b/.github/workflows/gnustep.yml
index 68f8aabde..6c40f3f14 100644
--- a/.github/workflows/gnustep.yml
+++ b/.github/workflows/gnustep.yml
@@ -101,18 +101,17 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: check
- args: --verbose --no-default-features
+ args: --verbose --no-default-features --features gnustep-1-9
- - name: Test GNUStep
+ - name: Test without features
uses: actions-rs/cargo@v1
with:
command: test
- # Temporary fix
- args: --verbose --no-fail-fast --no-default-features --package objc2_sys --package objc2 --package objc2_encode --package objc2_exception --package objc2_foundation
+ args: --verbose --no-fail-fast --no-default-features --features gnustep-1-9
- - name: Test GNUStep with features
+ - name: Test with features
uses: actions-rs/cargo@v1
with:
command: test
- # Temporary fix
- args: --verbose --no-fail-fast --no-default-features --features exception,verify_message --package objc2_sys --package objc2 --package objc2_encode --package objc2_exception --package objc2_foundation
+ # Not using --all-features because some features are nightly-only
+ args: --verbose --no-fail-fast --features gnustep-1-9,block,exception,verify_message
diff --git a/Cargo.toml b/Cargo.toml
index cdd936995..e5e47d14f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,6 +2,7 @@
members = [
"objc2",
"objc2_block",
+ "objc2_block_sys",
"objc2_encode",
"objc2_exception",
"objc2_foundation",
diff --git a/objc2/src/lib.rs b/objc2/src/lib.rs
index e97e6b207..50352b4aa 100644
--- a/objc2/src/lib.rs
+++ b/objc2/src/lib.rs
@@ -75,10 +75,6 @@ extern crate std;
#[doc = include_str!("../README.md")]
extern "C" {}
-#[cfg(doctest)]
-#[doc = include_str!("../../README.md")]
-extern "C" {}
-
pub use objc2_encode::{Encode, EncodeArguments, Encoding, RefEncode};
pub use crate::message::{Message, MessageArguments, MessageError, MessageReceiver};
diff --git a/objc2_block/Cargo.toml b/objc2_block/Cargo.toml
index 046b02fac..9f4ad1cb3 100644
--- a/objc2_block/Cargo.toml
+++ b/objc2_block/Cargo.toml
@@ -23,6 +23,7 @@ exclude = [
[dependencies]
objc2_encode = { path = "../objc2_encode" }
+objc2_block_sys = { path = "../objc2_block_sys" }
[dev-dependencies]
objc2_test_utils = { path = "../objc2_test_utils" }
diff --git a/objc2_block/src/lib.rs b/objc2_block/src/lib.rs
index e8178cef4..1c6ea8c97 100644
--- a/objc2_block/src/lib.rs
+++ b/objc2_block/src/lib.rs
@@ -55,7 +55,6 @@ extern crate std;
#[cfg(test)]
mod test_utils;
-use alloc::boxed::Box;
use core::ffi::c_void;
use core::marker::PhantomData;
use core::mem;
@@ -63,26 +62,9 @@ use core::ops::{Deref, DerefMut};
use core::ptr;
use std::os::raw::{c_int, c_ulong};
+pub use objc2_block_sys as ffi;
use objc2_encode::{Encode, EncodeArguments, Encoding, RefEncode};
-// TODO: Replace with `objc2::Class`
-#[repr(C)]
-struct ClassInternal {
- _priv: [u8; 0],
-}
-
-#[cfg_attr(target_vendor = "apple", link(name = "System", kind = "dylib"))]
-#[cfg_attr(
- not(target_vendor = "apple"),
- link(name = "BlocksRuntime", kind = "dylib")
-)]
-extern "C" {
- static _NSConcreteStackBlock: ClassInternal;
-
- fn _Block_copy(block: *const c_void) -> *mut c_void;
- fn _Block_release(block: *const c_void);
-}
-
/// Types that may be used as the arguments to an Objective-C block.
pub trait BlockArguments: Sized {
/// Calls the given `Block` with self as the arguments.
@@ -151,7 +133,7 @@ block_args_impl!(
#[repr(C)]
struct BlockBase {
- isa: *const ClassInternal,
+ isa: *const ffi::Class,
flags: c_int,
_reserved: c_int,
invoke: unsafe extern "C" fn(*mut Block, ...) -> R,
@@ -207,7 +189,7 @@ impl RcBlock {
///
/// The given pointer must point to a valid `Block`.
pub unsafe fn copy(ptr: *mut Block) -> Self {
- let ptr = _Block_copy(ptr as *const c_void) as *mut Block;
+ let ptr = ffi::_Block_copy(ptr as *const c_void) as *mut Block;
RcBlock { ptr }
}
}
@@ -229,7 +211,7 @@ impl Deref for RcBlock {
impl Drop for RcBlock {
fn drop(&mut self) {
unsafe {
- _Block_release(self.ptr as *const c_void);
+ ffi::_Block_release(self.ptr as *const c_void);
}
}
}
@@ -366,7 +348,7 @@ concrete_block_impl!(
#[repr(C)]
pub struct ConcreteBlock {
base: BlockBase,
- descriptor: Box>>,
+ descriptor: *const BlockDescriptor>,
closure: F,
}
@@ -391,19 +373,25 @@ where
}
impl ConcreteBlock {
+ const DESCRIPTOR: BlockDescriptor = BlockDescriptor {
+ _reserved: 0,
+ block_size: mem::size_of::() as c_ulong,
+ copy_helper: block_context_copy::,
+ dispose_helper: block_context_dispose::,
+ };
+
/// Constructs a `ConcreteBlock` with the given invoke function and closure.
/// Unsafe because the caller must ensure the invoke function takes the
/// correct arguments.
unsafe fn with_invoke(invoke: unsafe extern "C" fn(*mut Self, ...) -> R, closure: F) -> Self {
ConcreteBlock {
base: BlockBase {
- isa: &_NSConcreteStackBlock,
- // 1 << 25 = BLOCK_HAS_COPY_DISPOSE
- flags: 1 << 25,
+ isa: &ffi::_NSConcreteStackBlock,
+ flags: ffi::BLOCK_HAS_COPY_DISPOSE,
_reserved: 0,
invoke: mem::transmute(invoke),
},
- descriptor: Box::new(BlockDescriptor::new()),
+ descriptor: &Self::DESCRIPTOR,
closure,
}
}
@@ -469,17 +457,6 @@ struct BlockDescriptor {
dispose_helper: unsafe extern "C" fn(&mut B),
}
-impl BlockDescriptor {
- fn new() -> BlockDescriptor {
- BlockDescriptor {
- _reserved: 0,
- block_size: mem::size_of::() as c_ulong,
- copy_helper: block_context_copy::,
- dispose_helper: block_context_dispose::,
- }
- }
-}
-
#[cfg(test)]
mod tests {
use super::{ConcreteBlock, RcBlock};
diff --git a/objc2_block_sys/Cargo.toml b/objc2_block_sys/Cargo.toml
new file mode 100644
index 000000000..691e5b8ec
--- /dev/null
+++ b/objc2_block_sys/Cargo.toml
@@ -0,0 +1,50 @@
+[package]
+name = "objc2_block_sys"
+version = "0.0.0" # Remember to update html_root_url in lib.rs
+authors = ["Mads Marquart "]
+edition = "2018"
+
+description = "Raw bindings to Apple's C language extension of blocks"
+keywords = ["objective-c", "macos", "ios", "blocks", "sys"]
+categories = [
+ "external-ffi-bindings",
+ # "no_std", # TODO
+ "os::macos-apis",
+]
+repository = "https://github.com/madsmtm/objc2"
+documentation = "https://docs.rs/objc2_block_sys/"
+license = "MIT"
+
+readme = "README.md"
+
+# Downstream users can customize the linking!
+# See https://doc.rust-lang.org/cargo/reference/build-scripts.html#overriding-build-scripts
+links = "block"
+build = "build.rs"
+
+[features]
+# Link to Apple's libclosure (exists in libSystem)
+#
+# This is the default on Apple platforms
+apple = []
+
+# Link to libBlocksRuntime from compiler-rt
+#
+# This is the default on non-Apple platforms
+compiler-rt = []
+
+# Link to GNUStep's libobjc2 (which contains the block implementation)
+gnustep-1-7 = ["objc2_sys"]
+gnustep-1-8 = ["gnustep-1-7"]
+gnustep-1-9 = ["gnustep-1-8"]
+gnustep-2-0 = ["gnustep-1-9"]
+gnustep-2-1 = ["gnustep-2-0"]
+
+# Link to Microsoft's libobjc2
+winobjc = ["gnustep-1-8"]
+
+# TODO
+objfw = []
+
+[dependencies]
+objc2_sys = { path = "../objc2_sys", optional = true }
diff --git a/objc2_block_sys/README.md b/objc2_block_sys/README.md
new file mode 100644
index 000000000..3bddf8487
--- /dev/null
+++ b/objc2_block_sys/README.md
@@ -0,0 +1,111 @@
+# `objc2_block_sys`
+
+[](https://crates.io/crates/objc2_block_sys)
+[](../LICENSE.txt)
+[](https://docs.rs/objc2_block_sys/)
+[](https://github.com/madsmtm/objc2/actions/workflows/apple.yml)
+[](https://github.com/madsmtm/objc2/actions/workflows/gnustep.yml)
+
+Raw Rust bindings to Apple's C language extension of blocks
+
+## Runtime Support
+
+This library is basically just a raw interface to the aptly specified [Blocks
+ABI](https://clang.llvm.org/docs/Block-ABI-Apple.html). However, different
+runtime implementations exist and act in slightly different ways (and have
+several different helper functions), the most important aspect being that the
+libraries are named differently, so the linking must take that into account.
+
+The user can choose the desired runtime by using the relevant cargo feature
+flags, see the following sections:
+
+
+### Apple's [`libclosure`](https://opensource.apple.com/source/libclosure/)
+
+- Feature flag: `apple`.
+
+This is naturally the most sophisticated runtime, and it has quite a lot more
+features than the specification mandates. This is used by default on Apple
+platforms when no feature flags are specified.
+
+The minimum required operating system versions are as follows:
+- macOS: `10.6`
+- iOS: `3.2`
+- tvOS: Unknown
+- watchOS: Unknown
+
+Though in practice Rust itself requires higher versions than this.
+
+A git mirror of the sources is available [here](https://github.com/madsmtm/libclosure).
+
+
+### LLVM `compiler-rt`'s [`libBlocksRuntime`](https://github.com/llvm/llvm-project/tree/release/13.x/compiler-rt/lib/BlocksRuntime)
+
+- Feature flag: `compiler-rt`.
+
+This is the default runtime on all non-Apple platforms when no feature flags
+are specified.
+
+This is effectively just a copy of Apple's older (around macOS 10.6) runtime,
+and is now used in [Swift's `libdispatch`] and [Swift's Foundation] as well.
+
+This can be easily used on many Linux systems with the `libblocksruntime-dev`
+package.
+
+[Swift's `libdispatch`]: https://github.com/apple/swift-corelibs-libdispatch/tree/swift-5.5.1-RELEASE/src/BlocksRuntime
+[Swift's Foundation]: https://github.com/apple/swift-corelibs-foundation/tree/swift-5.5.1-RELEASE/Sources/BlocksRuntime
+
+
+### GNUStep's [`libobjc2`](https://github.com/gnustep/libobjc2)
+
+- Feature flag: `gnustep-1-7`, `gnustep-1-8`, `gnustep-1-9`, `gnustep-2-0` and
+ `gnustep-2-1` depending on the version you're using.
+
+GNUStep is a bit odd, because it bundles blocks support into its Objective-C
+runtime. This means we have to link to `libobjc`, and this is done by
+depending on the `objc2_sys` crate. A bit unorthodox, yes, but it works.
+
+Sources:
+- [`Block.h`](https://github.com/gnustep/libobjc2/blob/v2.1/objc/blocks_runtime.h)
+- [`Block_private.h`](https://github.com/gnustep/libobjc2/blob/v2.1/objc/blocks_private.h)
+
+
+### Microsoft's [`WinObjC`](https://github.com/microsoft/WinObjC)
+
+- Feature flag: `winobjc`.
+
+Essentially just [a fork](https://github.com/microsoft/libobjc2) based on
+GNUStep's `libobjc2` version 1.8.
+
+
+### [`ObjFW`](https://github.com/ObjFW/ObjFW) (WIP)
+
+- Feature flag: `objfw`.
+
+TODO.
+
+
+## C Compiler configuration
+
+To our knowledge, currently only `clang` supports the [Language Specification
+for Blocks][block-lang]. To assist in compiling C (or Objective-C) sources
+using these features, this crate's build script expose the `DEP_BLOCK_CC_ARGS`
+environment variable to downstream build scripts.
+
+Example usage in your `build.rs` (using the `cc` crate) would be as follows:
+
+```rust , ignore
+fn main() {
+ let mut builder = cc::Build::new();
+ builder.compiler("clang");
+ builder.file("my_script_using_blocks.c");
+
+ for flag in std::env::var("DEP_BLOCK_CC_ARGS").unwrap().split(' ') {
+ builder.flag(flag);
+ }
+
+ builder.compile("libmy_script_using_blocks.a");
+}
+```
+
+[block-lang]: https://clang.llvm.org/docs/BlockLanguageSpec.html
diff --git a/objc2_block_sys/build.rs b/objc2_block_sys/build.rs
new file mode 100644
index 000000000..50d3cae76
--- /dev/null
+++ b/objc2_block_sys/build.rs
@@ -0,0 +1,63 @@
+use std::env;
+use std::path::Path;
+
+fn main() {
+ // Only rerun if this file changes; the script doesn't depend on our code
+ println!("cargo:rerun-if-changed=build.rs");
+
+ let mut apple = env::var_os("CARGO_FEATURE_APPLE").is_some();
+ let mut compiler_rt = env::var_os("CARGO_FEATURE_COMPILER_RT").is_some();
+ let gnustep = env::var_os("CARGO_FEATURE_GNUSTEP_1_7").is_some();
+ let objfw = env::var_os("CARGO_FEATURE_OBJFW").is_some();
+
+ if let (false, false, false, false) = (apple, compiler_rt, gnustep, objfw) {
+ let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
+ if let "macos" | "ios" | "tvos" | "watchos" = &*target_os {
+ apple = true;
+ // Add cheaty #[cfg(feature = "apple")] directive
+ println!("cargo:rustc-cfg=feature=\"apple\"");
+ } else {
+ compiler_rt = true;
+ // Add cheaty #[cfg(feature = "compiler-rt")] directive
+ println!("cargo:rustc-cfg=feature=\"compiler-rt\"");
+ }
+ }
+
+ let mut cc_args = "-fblocks".to_owned();
+
+ match (apple, compiler_rt, gnustep, objfw) {
+ (true, false, false, false) => {
+ // Link to libclosure (internally called libsystem_blocks), which is
+ // exported by libSystem.dylib.
+ //
+ // Note that System.framework is just a deprecated wrapper over the
+ // dynamic library.
+ println!("cargo:rustc-link-lib=dylib=System");
+ // Alternative: Only link to libsystem_blocks.dylib
+ // println!("cargo:rustc-link-search=native=/usr/lib/system");
+ // println!("cargo:rustc-link-lib=dylib=system_blocks");
+ }
+ (false, true, false, false) => {
+ println!("cargo:rustc-link-lib=dylib=BlocksRuntime");
+ }
+ (false, false, true, false) => {
+ // Don't link to anything; objc2_sys already does that for us!
+
+ // Add GNUStep compability headers to make `#include `
+ // work (on newer GNUStep versions these headers are present)
+ if !env::var_os("CARGO_FEATURE_GNUSTEP_2_0").is_some() {
+ let compat_headers =
+ Path::new(env!("CARGO_MANIFEST_DIR")).join("gnustep-compat-headers");
+ cc_args.push_str(" -I");
+ cc_args.push_str(compat_headers.to_str().unwrap());
+ }
+ }
+ (false, false, false, true) => unimplemented!(),
+ // Checked in if-let above
+ (false, false, false, false) => unreachable!(),
+ (_, _, _, _) => panic!("Invalid feature combination; only one runtime may be selected!"),
+ }
+
+ // Add DEP_BLOCK_CC_ARGS
+ println!("cargo:cc_args={}", cc_args);
+}
diff --git a/objc2_block_sys/gnustep-compat-headers/Block.h b/objc2_block_sys/gnustep-compat-headers/Block.h
new file mode 100644
index 000000000..253e36595
--- /dev/null
+++ b/objc2_block_sys/gnustep-compat-headers/Block.h
@@ -0,0 +1 @@
+#include
diff --git a/objc2_block_sys/gnustep-compat-headers/Block_private.h b/objc2_block_sys/gnustep-compat-headers/Block_private.h
new file mode 100644
index 000000000..c565ce718
--- /dev/null
+++ b/objc2_block_sys/gnustep-compat-headers/Block_private.h
@@ -0,0 +1 @@
+#include
diff --git a/objc2_block_sys/src/lib.rs b/objc2_block_sys/src/lib.rs
new file mode 100644
index 000000000..3281071e0
--- /dev/null
+++ b/objc2_block_sys/src/lib.rs
@@ -0,0 +1,389 @@
+//! # Raw bindings to Apple's C language extension of blocks
+//!
+//! The documentation for these bindings is a mix from GNUStep's and Apple's
+//! sources, but the [ABI specification][ABI] is really the place you should
+//! be looking!
+//!
+//! See also the `README.md` for more info.
+//!
+//! [ABI]: https://clang.llvm.org/docs/Block-ABI-Apple.html
+
+// Update in Cargo.toml as well.
+#![doc(html_root_url = "https://docs.rs/objc2_block_sys/0.0.0")]
+
+// Ensure linkage actually happens
+#[cfg(feature = "gnustep-1-7")]
+extern crate objc2_sys;
+
+#[cfg(doctest)]
+#[doc = include_str!("../README.md")]
+extern "C" {}
+
+use core::cell::UnsafeCell;
+use core::ffi::c_void;
+use core::marker::{PhantomData, PhantomPinned};
+use std::os::raw::{c_char, c_ulong};
+
+#[repr(C)]
+pub struct Class {
+ #[cfg(any(feature = "apple", feature = "compiler-rt"))]
+ _priv: [*mut c_void; 32],
+
+ #[cfg(any(feature = "gnustep-1-7", feature = "objfw"))]
+ // The size of this is unknown
+ _priv: [u8; 0],
+
+ /// See objc2_sys::OpaqueData
+ _opaque: PhantomData<(UnsafeCell<*const ()>, PhantomPinned)>,
+}
+
+/// Block descriptor flags.
+/// Values for Block_layout->flags to describe block objects
+#[allow(non_camel_case_types)]
+pub type block_flags = i32;
+
+#[cfg(feature = "apple")]
+pub const BLOCK_DEALLOCATING: block_flags = 0x0001;
+
+/// Mask for the reference count in byref structure's flags field. The low
+/// 3 bytes are reserved for the reference count, the top byte for the flags.
+#[cfg(feature = "gnustep-1-7")]
+pub const BLOCK_REFCOUNT_MASK: block_flags = 0x00ffffff;
+#[cfg(any(feature = "compiler-rt", feature = "objfw"))]
+pub const BLOCK_REFCOUNT_MASK: block_flags = 0xffff;
+#[cfg(feature = "apple")]
+/// runtime
+pub const BLOCK_REFCOUNT_MASK: block_flags = 0xfffe;
+
+#[cfg(feature = "apple")]
+/// compiler
+pub const BLOCK_INLINE_LAYOUT_STRING: block_flags = 1 << 21;
+
+#[cfg(feature = "apple")]
+/// compiler
+pub const BLOCK_SMALL_DESCRIPTOR: block_flags = 1 << 22;
+
+#[cfg(feature = "apple")] // Part of ABI?
+/// compiler
+pub const BLOCK_IS_NOESCAPE: block_flags = 1 << 23;
+
+#[cfg(feature = "apple")]
+/// runtime
+pub const BLOCK_NEEDS_FREE: block_flags = 1 << 24;
+
+/// The block descriptor contains copy and dispose helpers.
+/// compiler
+pub const BLOCK_HAS_COPY_DISPOSE: block_flags = 1 << 25;
+
+/// The helpers have C++ code.
+/// compiler: helpers have C++ code
+pub const BLOCK_HAS_CTOR: block_flags = 1 << 26;
+
+#[cfg(feature = "apple")]
+/// compiler
+pub const BLOCK_IS_GC: block_flags = 1 << 27;
+
+/// Block is stored in global memory and does not need to be copied.
+/// compiler
+pub const BLOCK_IS_GLOBAL: block_flags = 1 << 28;
+
+/// Block function uses a calling convention that returns a structure via a
+/// pointer passed in by the caller.
+///
+/// match (BLOCK_USE_STRET, BLOCK_HAS_SIGNATURE) {
+/// (false, false) => 10.6.ABI, no signature field available
+/// (true, false) => 10.6.ABI, no signature field available
+/// (false, true) => ABI.2010.3.16, regular calling convention, presence of signature field
+/// (true, true) => ABI.2010.3.16, stret calling convention, presence of signature field,
+/// }
+///
+/// See
+#[doc(alias = "BLOCK_USE_SRET")]
+#[doc(alias = "BLOCK_HAS_DESCRIPTOR")] // compiler-rt || macOS 10.6
+pub const BLOCK_USE_STRET: block_flags = 1 << 29;
+
+/// Block has an Objective-C type encoding.
+/// compiler
+pub const BLOCK_HAS_SIGNATURE: block_flags = 1 << 30;
+
+#[cfg(feature = "apple")]
+/// compiler
+pub const BLOCK_HAS_EXTENDED_LAYOUT: block_flags = 1 << 31;
+
+/// Flags used in the final argument to _Block_object_assign() and
+/// _Block_object_dispose(). These indicate the type of copy or dispose to
+/// perform.
+/// Values for _Block_object_assign() and _Block_object_dispose() parameters
+///
+/// This is a helper type, in the sources this type does not have a name!
+#[allow(non_camel_case_types)]
+pub type block_assign_dispose_flags = i32;
+
+/// The value is of some id-like type, and should be copied as an Objective-C
+/// object: i.e. by sending -retain or via the GC assign functions in GC mode
+/// (not yet supported).
+///
+/// id, NSObject, __attribute__((NSObject)), block, ...
+pub const BLOCK_FIELD_IS_OBJECT: block_assign_dispose_flags = 3;
+
+/// The field is a block. This must be copied by the block copy functions.
+///
+/// a block variable
+pub const BLOCK_FIELD_IS_BLOCK: block_assign_dispose_flags = 7;
+
+/// The field is an indirect reference to a variable declared with the __block
+/// storage qualifier.
+///
+/// the on stack structure holding the __block variable
+pub const BLOCK_FIELD_IS_BYREF: block_assign_dispose_flags = 8;
+
+/// The field is an indirect reference to a variable declared with the __block
+/// storage qualifier.
+///
+/// declared __weak, only used in byref copy helpers
+pub const BLOCK_FIELD_IS_WEAK: block_assign_dispose_flags = 16;
+
+/// The field is an indirect reference to a variable declared with the __block
+/// storage qualifier.
+///
+/// called from __block (byref) copy/dispose support routines.
+pub const BLOCK_BYREF_CALLER: block_assign_dispose_flags = 128;
+
+#[cfg(feature = "apple")]
+pub const BLOCK_ALL_COPY_DISPOSE_FLAGS: block_assign_dispose_flags = BLOCK_FIELD_IS_OBJECT
+ | BLOCK_FIELD_IS_BLOCK
+ | BLOCK_FIELD_IS_BYREF
+ | BLOCK_FIELD_IS_WEAK
+ | BLOCK_BYREF_CALLER;
+
+// TODO: BLOCK_LAYOUT_X
+
+extern "C" {
+ // the raw data space for runtime classes for blocks
+ // class+meta used for stack, malloc, and collectable based blocks
+
+ pub static _NSConcreteGlobalBlock: Class;
+ pub static _NSConcreteStackBlock: Class;
+ pub static _NSConcreteMallocBlock: Class;
+
+ pub fn _Block_copy(block: *const c_void) -> *mut c_void;
+ pub fn _Block_release(block: *const c_void);
+
+ /// Runtime entry point called by compiler when assigning objects inside
+ /// copy helper routines
+ pub fn _Block_object_assign(
+ dest_addr: *mut c_void,
+ object: *const c_void,
+ flags: block_assign_dispose_flags,
+ );
+
+ /// runtime entry point called by the compiler when disposing of objects
+ /// inside dispose helper routine
+ pub fn _Block_object_dispose(object: *const c_void, flags: block_assign_dispose_flags);
+}
+
+#[cfg(feature = "apple")]
+extern "C" {
+ // Whether the return value of the block is on the stack.
+ // macOS 10.7
+ // pub fn _Block_use_stret(block: *mut c_void) -> bool;
+
+ // Returns a string describing the block's GC layout.
+ // This uses the GC skip/scan encoding.
+ // May return NULL.
+ // macOS 10.7
+ // pub fn _Block_layout(block: *mut c_void) -> *const c_char;
+
+ // Returns a string describing the block's layout.
+ // This uses the "extended layout" form described above.
+ // May return NULL.
+ // macOS 10.8
+ // pub fn _Block_extended_layout(block: *mut c_void) -> *const c_char;
+
+ // Callable only from the ARR weak subsystem while in exclusion zone
+ // macOS 10.7
+ // pub fn _Block_tryRetain(block: *const c_void) -> bool;
+
+ // Callable only from the ARR weak subsystem while in exclusion zone
+ // macOS 10.7
+ // pub fn _Block_isDeallocating(block: *const c_void) -> bool;
+}
+
+#[cfg(any(feature = "apple", feature = "compiler-rt"))]
+extern "C" {
+ // the raw data space for runtime classes for blocks
+ // class+meta used for stack, malloc, and collectable based blocks
+
+ pub static _NSConcreteAutoBlock: Class;
+ pub static _NSConcreteFinalizingBlock: Class;
+ pub static _NSConcreteWeakBlockVariable: Class;
+
+ pub fn Block_size(block: *mut c_void) -> c_ulong; // usize
+}
+
+#[cfg(any(feature = "apple", feature = "gnustep-1-7"))]
+extern "C" {
+ // indicates whether block was compiled with compiler that sets the ABI
+ // related metadata bits
+ // macOS 10.7
+ // pub fn _Block_has_signature(block: *mut c_void) -> bool;
+
+ // Returns a string describing the block's parameter and return types.
+ // The encoding scheme is the same as Objective-C @encode.
+ // Returns NULL for blocks compiled with some compilers.
+ // macOS 10.7
+ // pub fn _Block_signature(block: *mut c_void) -> *const c_char;
+}
+
+#[repr(C)]
+pub struct Block_layout {
+ /// Class pointer. Always initialised to &_NSConcreteStackBlock for blocks
+ /// that are created on the stack or &_NSConcreteGlobalBlock for blocks
+ /// that are created in global storage.
+ pub isa: *mut Class,
+ /// Flags.
+ /// See the `block_flags` enumerated type for possible values.
+ /// Contains ref count in Apple and ObjFW.
+ pub flags: block_flags,
+ /// Reserved - always initialised to 0 by the compiler (but this is not
+ /// said in the specification).
+ ///
+ /// Used for the reference count in GNUStep and WinObjC.
+ pub reserved: i32,
+ /// The function that implements the block. The first argument is this
+ /// structure, the subsequent arguments are the block's explicit
+ /// parameters. If the BLOCK_USE_SRET & BLOCK_HAS_SIGNATURE flag is set,
+ /// there is an additional hidden argument, which is a pointer to the
+ /// space on the stack allocated to hold the return value.
+ pub invoke: Option,
+ /// The block's descriptor. The actual type of this is:
+ /// ```ignore
+ /// match (BLOCK_HAS_COPY_DISPOSE, BLOCK_HAS_SIGNATURE) {
+ /// (false, false) => Block_descriptor_header,
+ /// (true, false) => Block_descriptor,
+ /// (false, true) => Block_descriptor_basic,
+ /// (true, true) => Block_descriptor_with_signature,
+ /// }
+ /// ```
+ ///
+ /// But it is safe to access this through just `Block_descriptor_header`.
+ pub descriptor: *mut Block_descriptor_header,
+}
+
+#[repr(C)]
+pub struct Block_descriptor_header {
+ /// Reserved for future use. Currently always 0.
+ pub reserved: c_ulong, // usize
+ /// Size of the block.
+ pub size: c_ulong, // usize
+}
+
+/// Block descriptor that contains copy and dispose operations.
+///
+/// Requires BLOCK_HAS_COPY_DISPOSE
+#[repr(C)]
+pub struct Block_descriptor {
+ pub header: Block_descriptor_header,
+ /// Copy function, generated by the compiler to help copy the block if it
+ /// contains nontrivial copy operations.
+ pub copy: Option,
+ /// Dispose function, generated by the compiler to help copy the block if
+ /// it contains nontrivial destructors.
+ pub dispose: Option,
+}
+
+/// Extended block descriptor that does not contain copy and dispose helper
+/// functions.
+///
+/// Requires BLOCK_HAS_SIGNATURE
+#[repr(C)]
+#[cfg(not(feature = "objfw"))]
+pub struct Block_descriptor_basic {
+ pub header: Block_descriptor_header,
+
+ /// Objective-C type encoding of the block.
+ #[doc(alias = "signature")]
+ pub encoding: *const c_char,
+}
+
+/// Requires BLOCK_HAS_COPY_DISPOSE and BLOCK_HAS_SIGNATURE
+#[repr(C)]
+pub struct Block_descriptor_with_signature {
+ pub inner: Block_descriptor,
+
+ /// Objective-C type encoding of the block.
+ #[doc(alias = "signature")]
+ pub encoding: *const c_char,
+}
+
+// #[cfg(feature = "apple")]
+// pub layout: *const c_char,
+
+// #[repr(C)]
+// pub struct Block_descriptor_small {
+// pub size: u32,
+// pub signature: i32,
+// pub layout: i32,
+// pub copy: i32,
+// pub dispose: i32,
+// }
+
+// #[repr(C)]
+// pub struct Block_basic {
+// pub isa: *mut c_void,
+// pub Block_flags: i32,
+// pub Block_size: i32,
+// pub Block_invoke: Option,
+// pub Block_copy: Option,
+// pub Block_dispose: Option,
+// }
+// Example usage: https://opensource.apple.com/source/libdispatch/libdispatch-84.5/src/once.c.auto.html
+
+/// Structure used for on-stack variables that are referenced by blocks.
+#[repr(C)]
+#[doc(alias = "Block_byref_1")]
+pub struct Block_byref_header {
+ /// Class pointer. Currently unused on GNUStep and always NULL. Could be
+ /// used in the future to support introspection.
+ pub isa: *mut c_void,
+ /// The pointer to the structure that contains the real version of the
+ /// data. All accesses go via this pointer. If an on-stack byref structure
+ /// is copied to the heap, then its forwarding pointer should point to the
+ /// heap version. Otherwise it should point to itself.
+ pub forwarding: *mut Block_byref_header,
+ /// Flags and reference count.
+ ///
+ /// TODO: Volatile!
+ pub flags: block_flags,
+ #[cfg(feature = "apple")]
+ /// Size of this structure.
+ pub size: u32,
+ #[cfg(not(feature = "apple"))]
+ /// Size of this structure.
+ pub size: i32,
+}
+
+/// Structure used for on-stack variables that are referenced by blocks.
+///
+/// requires BLOCK_BYREF_HAS_COPY_DISPOSE
+#[repr(C)]
+#[doc(alias = "Block_byref_2")]
+pub struct Block_byref {
+ pub header: Block_byref_header,
+ /// Copy function.
+ pub keep:
+ Option,
+ /// Dispose function.
+ pub destroy: Option,
+}
+
+#[cfg(feature = "apple")]
+/// Structure used for on-stack variables that are referenced by blocks.
+///
+/// requires BLOCK_BYREF_LAYOUT_EXTENDED
+#[repr(C)]
+#[doc(alias = "Block_byref_3")]
+pub struct Block_byref_extended {
+ pub inner: Block_byref,
+ pub layout: *const c_char,
+}
diff --git a/objc2_foundation/Cargo.toml b/objc2_foundation/Cargo.toml
index 746a45933..8be17605f 100644
--- a/objc2_foundation/Cargo.toml
+++ b/objc2_foundation/Cargo.toml
@@ -16,6 +16,8 @@ repository = "https://github.com/madsmtm/objc2"
documentation = "https://docs.rs/objc2_foundation/"
license = "MIT"
+build = "build.rs"
+
[features]
default = ["block"]
# Provided as a way to cut down on dependencies
@@ -24,3 +26,4 @@ block = ["objc2_block"]
[dependencies]
objc2_block = { path = "../objc2_block", optional = true }
objc2 = { path = "../objc2" }
+objc2_sys = { path = "../objc2_sys" }
diff --git a/objc2_foundation/build.rs b/objc2_foundation/build.rs
new file mode 100644
index 000000000..70fdc688a
--- /dev/null
+++ b/objc2_foundation/build.rs
@@ -0,0 +1,9 @@
+use std::env;
+
+fn main() {
+ // The script doesn't depend on our code
+ println!("cargo:rerun-if-changed=build.rs");
+
+ let runtime = env::var("DEP_OBJC_RUNTIME").unwrap();
+ println!("cargo:rustc-cfg={}", runtime);
+}
diff --git a/objc2_foundation/src/data.rs b/objc2_foundation/src/data.rs
index 33cb925e6..e9d417b3b 100644
--- a/objc2_foundation/src/data.rs
+++ b/objc2_foundation/src/data.rs
@@ -7,8 +7,6 @@ use core::{ffi::c_void, ptr::NonNull};
use super::{INSCopying, INSMutableCopying, INSObject, NSRange};
use objc2::msg_send;
use objc2::rc::{Id, Owned};
-#[cfg(feature = "block")]
-use objc2_block::{Block, ConcreteBlock};
pub trait INSData: INSObject {
fn len(&self) -> usize {
@@ -22,12 +20,11 @@ pub trait INSData: INSObject {
fn bytes(&self) -> &[u8] {
let ptr: *const c_void = unsafe { msg_send![self, bytes] };
// The bytes pointer may be null for length zero
- let (ptr, len) = if ptr.is_null() {
- (0x1 as *const u8, 0)
+ if ptr.is_null() {
+ &[]
} else {
- (ptr as *const u8, self.len())
- };
- unsafe { slice::from_raw_parts(ptr, len) }
+ unsafe { slice::from_raw_parts(ptr as *const u8, self.len()) }
+ }
}
fn with_bytes(bytes: &[u8]) -> Id {
@@ -46,6 +43,8 @@ pub trait INSData: INSObject {
#[cfg(feature = "block")]
fn from_vec(bytes: Vec) -> Id {
+ use objc2_block::{Block, ConcreteBlock};
+
let capacity = bytes.capacity();
let dealloc = ConcreteBlock::new(move |bytes: *mut c_void, len: usize| unsafe {
// Recreate the Vec and let it drop
@@ -56,6 +55,22 @@ pub trait INSData: INSObject {
let mut bytes = bytes;
let bytes_ptr = bytes.as_mut_ptr() as *mut c_void;
+
+ // GNUStep's NSData `initWithBytesNoCopy:length:deallocator:` has a
+ // bug; it forgets to assign the input buffer and length to the
+ // instance before it swizzles to NSDataWithDeallocatorBlock.
+ // See https://github.com/gnustep/libs-base/pull/213
+ // So we just use NSDataWithDeallocatorBlock directly.
+ #[cfg(gnustep)]
+ let cls = {
+ let cls = Self::class();
+ if cls == objc2::class!(NSData) {
+ objc2::class!(NSDataWithDeallocatorBlock)
+ } else {
+ cls
+ }
+ };
+ #[cfg(not(gnustep))]
let cls = Self::class();
unsafe {
let obj: *mut Self = msg_send![cls, alloc];
@@ -216,6 +231,6 @@ mod tests {
let bytes_ptr = bytes.as_ptr();
let data = NSData::from_vec(bytes);
- assert!(data.bytes().as_ptr() == bytes_ptr);
+ assert_eq!(data.bytes().as_ptr(), bytes_ptr);
}
}
diff --git a/objc2_test_utils/Cargo.toml b/objc2_test_utils/Cargo.toml
index aea262997..77869e511 100644
--- a/objc2_test_utils/Cargo.toml
+++ b/objc2_test_utils/Cargo.toml
@@ -18,5 +18,8 @@ license = "MIT"
build = "build.rs"
+[dependencies]
+objc2_block_sys = { path = "../objc2_block_sys" }
+
[build-dependencies]
cc = "1.0"
diff --git a/objc2_test_utils/build.rs b/objc2_test_utils/build.rs
index 1da9e08c1..b5711d7c0 100644
--- a/objc2_test_utils/build.rs
+++ b/objc2_test_utils/build.rs
@@ -1,9 +1,15 @@
-use cc;
+use std::env;
fn main() {
- cc::Build::new()
- .compiler("clang")
- .file("extern/block_utils.c")
- .flag("-fblocks")
- .compile("libblock_utils.a");
+ println!("cargo:rerun-if-changed=extern/block_utils.c");
+
+ let mut builder = cc::Build::new();
+ builder.compiler("clang");
+ builder.file("extern/block_utils.c");
+
+ for flag in env::var("DEP_BLOCK_CC_ARGS").unwrap().split(' ') {
+ builder.flag(flag);
+ }
+
+ builder.compile("libblock_utils.a");
}
diff --git a/objc2_test_utils/src/lib.rs b/objc2_test_utils/src/lib.rs
index d1b9efa8a..2d00e65cb 100644
--- a/objc2_test_utils/src/lib.rs
+++ b/objc2_test_utils/src/lib.rs
@@ -1,5 +1,8 @@
#![no_std]
+// Ensure linkage actually happens
+extern crate objc2_block_sys;
+
/// A block that takes no arguments and returns an integer: `int32_t (^)()`.
#[repr(C)]
pub struct IntBlock {