├── .gitignore ├── .idea └── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── composer.lock ├── examples ├── 00-basic │ ├── example.php │ ├── test.h │ └── test.php ├── 01-stdio-builder │ └── example.php ├── 02-stdio-codegen │ ├── example.php │ └── stdio.php └── 03-multiple-headers │ └── example.php ├── lib ├── BuiltinFunction.php ├── CompiledExpr.php ├── CompiledFunctionType.php ├── CompiledType.php ├── Compiler.php ├── FFIMe.php ├── NotExportedFunctionException.php └── UnsupportedFeatureException.php ├── phpunit.xml.dist └── test ├── InlineFunctions ├── InlineTestcase.php ├── basic_strings.php ├── basic_structs.php ├── duffs_device.php ├── function_pointers.php ├── generated │ └── .gitignore └── passing_string_array.php ├── cases ├── basic_enums.phpt ├── basic_strings.phpt ├── basic_typedefs.phpt ├── char_returns.phpt └── enum_self_references.phpt ├── generated ├── basic_enumsTest.h ├── basic_enumsTest.php ├── basic_stringsTest.h ├── basic_stringsTest.php ├── basic_typedefsTest.h ├── basic_typedefsTest.php ├── char_returnsTest.h ├── char_returnsTest.php ├── enum_self_referencesTest.h └── enum_self_referencesTest.php └── rebuild.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /workspace.xml -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Anthony Ferrara 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FFIMe 2 | 3 | This is a wrapper library for PHP's FFI extension. 4 | 5 | You provide it with a shared object, and one or more header files, and it automatically generates the C structures and function signatures for you (just like doing it in C would). 6 | It then provides wrapper classes for all C structures and functions to provide a fully typed experience. 7 | 8 | ## Usage: 9 | 10 | Currently there are two modes of operation, an "inline" mode: 11 | 12 | ```php 13 | $libc = (new FFIMe\FFIMe(FFIMe\FFIMe::LIBC)) 14 | ->include("stdio.h") 15 | ->build(); 16 | 17 | $libc->printf("test\n"); 18 | ``` 19 | 20 | And a code generating mode: 21 | 22 | ```php 23 | (new FFIMe\FFIMe(FFIMe\FFIMe::LIBC)) 24 | ->include("stdio.h") 25 | ->include("other.h") 26 | ->codeGen('full\\classname', 'path/to/file.php'); 27 | 28 | require 'path/to/file.php'; 29 | $libc = full\classname::ffi(); 30 | $libc->printf("test\n"); 31 | ``` 32 | 33 | The code generating mode is designed to be used in production, where you'd code generate during a build step and ship with your library. 34 | 35 | This should now work for the majority of header files. Looking at some of the code, specifically the compiler, there is a bit of hard coding necessary. So I don't expect every file to work out of the box. If you find a header file that doesn't work, just open a bug and we'll take a look. 36 | 37 | Check out the [examples](examples/); 38 | 39 | ## Slimming it down 40 | 41 | Generating FFI for large projects may result in codefiles counting tens or hundreds of thousands of lines, though only a few hundred or thousand are actually needed. This may result in a non-trivial overhead, which can be removed after development. 42 | 43 | ```php 44 | $ffi = (new FFIMe\FFIMe(FFIMe\FFIMe::LIBC)) 45 | ->include("stdio.h"); 46 | 47 | $ffi->codeGenWithInstrumentation('full\\classname', 'path/to/file.php'); 48 | 49 | // Run some code providing 100% coverage of all code using FFI 50 | (new PHPUnit\TextUI\Command)->run([$argv[0], "test"], false); 51 | 52 | $ffi->codeGen('full\\classname', 'path/to/file.php'); 53 | ``` 54 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ircmaxell/ffime", 3 | "description": "Make life easy when working with FFI", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Anthony Ferrara", 8 | "email": "ircmaxell@gmail.com" 9 | }, 10 | { 11 | "name": "Bob Weinand", 12 | "email": "bobwei9@hotmail.com" 13 | } 14 | ], 15 | "require": { 16 | "php": ">=8.0", 17 | "ircmaxell/php-c-parser": "dev-master", 18 | "ircmaxell/php-object-symbolresolver": "dev-master" 19 | }, 20 | "require-dev": { 21 | "phpunit/phpunit": "^8.0" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "FFIMe\\": "lib/" 26 | } 27 | }, 28 | "autoload-dev": { 29 | "psr-4": { 30 | "FFIMe\\Test\\": "test/" 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/00-basic/example.php: -------------------------------------------------------------------------------- 1 | include(__DIR__ . "/test.h") 6 | ->codeGen('test\\test', __DIR__ . '/test.php'); 7 | 8 | 9 | require __DIR__ . '/test.php'; 10 | $test = Test\Test::ffi(); 11 | 12 | var_dump($test->add2(1, 2)); 13 | echo "Done\n"; -------------------------------------------------------------------------------- /examples/00-basic/test.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | long add(long a, long b); 4 | 5 | long add2(long a, long b) { 6 | return a + b; 7 | } -------------------------------------------------------------------------------- /examples/00-basic/test.php: -------------------------------------------------------------------------------- 1 | ffi = FFI::cdef(self::HEADER_DEF, $pathToSoFile); 26 | } 27 | 28 | public static function cast(itest $from, string $to): itest { 29 | if (!is_a($to, itest::class, true)) { 30 | throw new \LogicException("Cannot cast to a non-wrapper type"); 31 | } 32 | return new $to(self::$staticFFI->cast($to::getType(), $from->getData())); 33 | } 34 | 35 | public static function makeArray(string $class, int|array $elements): itest { 36 | $type = $class::getType(); 37 | if (substr($type, -1) !== "*") { 38 | throw new \LogicException("Attempting to make a non-pointer element into an array"); 39 | } 40 | if (is_int($elements)) { 41 | $cdata = self::$staticFFI->new(substr($type, 0, -1) . "[$elements]"); 42 | } else { 43 | $cdata = self::$staticFFI->new(substr($type, 0, -1) . "[" . count($elements) . "]"); 44 | foreach ($elements as $key => $raw) { 45 | $cdata[$key] = \is_scalar($raw) ? \is_int($raw) && $type === "char*" ? \chr($raw) : $raw : $raw->getData(); 46 | } 47 | } 48 | $object = new $class(self::$staticFFI->cast($type, \FFI::addr($cdata))); 49 | self::$__arrayWeakMap[$object] = $cdata; 50 | return $object; 51 | } 52 | 53 | public static function sizeof($classOrObject): int { 54 | if (is_object($classOrObject) && $classOrObject instanceof itest) { 55 | return FFI::sizeof($classOrObject->getData()); 56 | } elseif (is_a($classOrObject, itest::class, true)) { 57 | return FFI::sizeof(self::$staticFFI->type($classOrObject::getType())); 58 | } else { 59 | throw new \LogicException("Unknown class/object passed to sizeof()"); 60 | } 61 | } 62 | 63 | public function getFFI(): FFI { 64 | return $this->ffi; 65 | } 66 | 67 | 68 | public function __get(string $name) { 69 | switch($name) { 70 | default: return $this->ffi->$name; 71 | } 72 | } 73 | public function __set(string $name, $value) { 74 | switch($name) { 75 | default: return $this->ffi->$name; 76 | } 77 | } 78 | public function __allocCachedString(string $str): FFI\CData { 79 | return $this->__literalStrings[$str] ??= string_::ownedZero($str)->getData(); 80 | } 81 | public function add2(int $a, int $b): int { 82 | $result = $this->_ffi_internal_add2($a, $b); 83 | return $result; 84 | } 85 | private function _ffi_internal_add2(int $a, int $b): int { 86 | $a = (function ($cdata, $val) { $cdata->cdata = $val; return $cdata; })($this->ffi->new("int"), $a); 87 | $b = (function ($cdata, $val) { $cdata->cdata = $val; return $cdata; })($this->ffi->new("int"), $b); 88 | return (($a)->cdata + ($b)->cdata); 89 | } 90 | } 91 | 92 | class string_ implements itest, itest_ptr, \ArrayAccess { 93 | private FFI\CData $data; 94 | public function __construct(FFI\CData $data) { $this->data = $data; } 95 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 96 | public function getData(): FFI\CData { return $this->data; } 97 | public function equals(string_ $other): bool { return $this->data == $other->data; } 98 | public function addr(): string_ptr { return new string_ptr(FFI::addr($this->data)); } 99 | public function deref(int $n = 0): int { return \ord($this->data[$n]); } 100 | #[\ReturnTypeWillChange] public function offsetGet($offset): int { return $this->deref($offset); } 101 | #[\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 102 | #[\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \Error("Cannot unset C structures"); } 103 | #[\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = \chr($value); } 104 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 105 | /** @return int[] */ public function toArray(?int $length = null): array { $ret = []; if ($length === null) { $i = 0; while ("\0" !== $cur = $this->data[$i++]) { $ret[] = \ord($cur); } } else { for ($i = 0; $i < $length; ++$i) { $ret[] = \ord($this->data[$i]); } } return $ret; } 106 | public function toString(?int $length = null): string { return $length === null ? FFI::string($this->data) : FFI::string($this->data, $length); } 107 | public static function persistent(string $string): self { $str = new self(FFI::cdef()->new("char[" . \strlen($string) . "]", false)); FFI::memcpy($str->data, $string, \strlen($string)); return $str; } 108 | public static function owned(string $string): self { $str = new self(FFI::cdef()->new("char[" . \strlen($string) . "]", true)); FFI::memcpy($str->data, $string, \strlen($string)); return $str; } 109 | public static function persistentZero(string $string): self { return self::persistent("$string\0"); } 110 | public static function ownedZero(string $string): self { return self::owned("$string\0"); } 111 | public function set(int | void_ptr | string_ $value): void { 112 | if (\is_scalar($value)) { 113 | $this->data[0] = \chr($value); 114 | } else { 115 | FFI::addr($this->data)[0] = $value->getData(); 116 | } 117 | } 118 | public static function getType(): string { return 'char*'; } 119 | public static function size(): int { return testFFI::sizeof(self::class); } 120 | public function getDefinition(): string { return static::getType(); } 121 | } 122 | class string_ptr implements itest, itest_ptr, \ArrayAccess { 123 | private FFI\CData $data; 124 | public function __construct(FFI\CData $data) { $this->data = $data; } 125 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 126 | public function getData(): FFI\CData { return $this->data; } 127 | public function equals(string_ptr $other): bool { return $this->data == $other->data; } 128 | public function addr(): string_ptr_ptr { return new string_ptr_ptr(FFI::addr($this->data)); } 129 | public function deref(int $n = 0): string_ { return new string_($this->data[$n]); } 130 | #[\ReturnTypeWillChange] public function offsetGet($offset): string_ { return $this->deref($offset); } 131 | #[\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 132 | #[\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \Error("Cannot unset C structures"); } 133 | #[\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = $value->getData(); } 134 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 135 | /** @return string_[] */ public function toArray(?int $length = null): array { $ret = []; if ($length === null) { $i = 0; while (null !== $cur = $this->data[$i++]) { $ret[] = new string_($cur); } } else { for ($i = 0; $i < $length; ++$i) { $ret[] = new string_($this->data[$i]); } } return $ret; } 136 | public function set(void_ptr | string_ptr $value): void { 137 | FFI::addr($this->data)[0] = $value->getData(); 138 | } 139 | public static function getType(): string { return 'char**'; } 140 | public static function size(): int { return testFFI::sizeof(self::class); } 141 | public function getDefinition(): string { return static::getType(); } 142 | } 143 | class string_ptr_ptr implements itest, itest_ptr, \ArrayAccess { 144 | private FFI\CData $data; 145 | public function __construct(FFI\CData $data) { $this->data = $data; } 146 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 147 | public function getData(): FFI\CData { return $this->data; } 148 | public function equals(string_ptr_ptr $other): bool { return $this->data == $other->data; } 149 | public function addr(): string_ptr_ptr_ptr { return new string_ptr_ptr_ptr(FFI::addr($this->data)); } 150 | public function deref(int $n = 0): string_ptr { return new string_ptr($this->data[$n]); } 151 | #[\ReturnTypeWillChange] public function offsetGet($offset): string_ptr { return $this->deref($offset); } 152 | #[\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 153 | #[\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \Error("Cannot unset C structures"); } 154 | #[\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = $value->getData(); } 155 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 156 | /** @return string_ptr[] */ public function toArray(?int $length = null): array { $ret = []; if ($length === null) { $i = 0; while (null !== $cur = $this->data[$i++]) { $ret[] = new string_ptr($cur); } } else { for ($i = 0; $i < $length; ++$i) { $ret[] = new string_ptr($this->data[$i]); } } return $ret; } 157 | public function set(void_ptr | string_ptr_ptr $value): void { 158 | FFI::addr($this->data)[0] = $value->getData(); 159 | } 160 | public static function getType(): string { return 'char***'; } 161 | public static function size(): int { return testFFI::sizeof(self::class); } 162 | public function getDefinition(): string { return static::getType(); } 163 | } 164 | class string_ptr_ptr_ptr implements itest, itest_ptr, \ArrayAccess { 165 | private FFI\CData $data; 166 | public function __construct(FFI\CData $data) { $this->data = $data; } 167 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 168 | public function getData(): FFI\CData { return $this->data; } 169 | public function equals(string_ptr_ptr_ptr $other): bool { return $this->data == $other->data; } 170 | public function addr(): string_ptr_ptr_ptr_ptr { return new string_ptr_ptr_ptr_ptr(FFI::addr($this->data)); } 171 | public function deref(int $n = 0): string_ptr_ptr { return new string_ptr_ptr($this->data[$n]); } 172 | #[\ReturnTypeWillChange] public function offsetGet($offset): string_ptr_ptr { return $this->deref($offset); } 173 | #[\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 174 | #[\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \Error("Cannot unset C structures"); } 175 | #[\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = $value->getData(); } 176 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 177 | /** @return string_ptr_ptr[] */ public function toArray(?int $length = null): array { $ret = []; if ($length === null) { $i = 0; while (null !== $cur = $this->data[$i++]) { $ret[] = new string_ptr_ptr($cur); } } else { for ($i = 0; $i < $length; ++$i) { $ret[] = new string_ptr_ptr($this->data[$i]); } } return $ret; } 178 | public function set(void_ptr | string_ptr_ptr_ptr $value): void { 179 | FFI::addr($this->data)[0] = $value->getData(); 180 | } 181 | public static function getType(): string { return 'char***'; } 182 | public static function size(): int { return testFFI::sizeof(self::class); } 183 | public function getDefinition(): string { return static::getType(); } 184 | } 185 | class void_ptr implements itest, itest_ptr { 186 | private FFI\CData $data; 187 | public function __construct(FFI\CData $data) { $this->data = $data; } 188 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 189 | public function getData(): FFI\CData { return $this->data; } 190 | public function equals(void_ptr $other): bool { return $this->data == $other->data; } 191 | public function addr(): void_ptr_ptr { return new void_ptr_ptr(FFI::addr($this->data)); } 192 | public function set(itest_ptr $value): void { 193 | FFI::addr($this->data)[0] = $value->getData(); 194 | } 195 | public static function getType(): string { return 'void*'; } 196 | public static function size(): int { return testFFI::sizeof(self::class); } 197 | public function getDefinition(): string { return static::getType(); } 198 | } 199 | class void_ptr_ptr implements itest, itest_ptr, \ArrayAccess { 200 | private FFI\CData $data; 201 | public function __construct(FFI\CData $data) { $this->data = $data; } 202 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 203 | public function getData(): FFI\CData { return $this->data; } 204 | public function equals(void_ptr_ptr $other): bool { return $this->data == $other->data; } 205 | public function addr(): void_ptr_ptr_ptr { return new void_ptr_ptr_ptr(FFI::addr($this->data)); } 206 | public function deref(int $n = 0): void_ptr { return new void_ptr($this->data[$n]); } 207 | #[\ReturnTypeWillChange] public function offsetGet($offset): void_ptr { return $this->deref($offset); } 208 | #[\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 209 | #[\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \Error("Cannot unset C structures"); } 210 | #[\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = $value->getData(); } 211 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 212 | /** @return void_ptr[] */ public function toArray(?int $length = null): array { $ret = []; if ($length === null) { $i = 0; while (null !== $cur = $this->data[$i++]) { $ret[] = new void_ptr($cur); } } else { for ($i = 0; $i < $length; ++$i) { $ret[] = new void_ptr($this->data[$i]); } } return $ret; } 213 | public function set(void_ptr | void_ptr_ptr $value): void { 214 | FFI::addr($this->data)[0] = $value->getData(); 215 | } 216 | public static function getType(): string { return 'void**'; } 217 | public static function size(): int { return testFFI::sizeof(self::class); } 218 | public function getDefinition(): string { return static::getType(); } 219 | } 220 | class void_ptr_ptr_ptr implements itest, itest_ptr, \ArrayAccess { 221 | private FFI\CData $data; 222 | public function __construct(FFI\CData $data) { $this->data = $data; } 223 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 224 | public function getData(): FFI\CData { return $this->data; } 225 | public function equals(void_ptr_ptr_ptr $other): bool { return $this->data == $other->data; } 226 | public function addr(): void_ptr_ptr_ptr_ptr { return new void_ptr_ptr_ptr_ptr(FFI::addr($this->data)); } 227 | public function deref(int $n = 0): void_ptr_ptr { return new void_ptr_ptr($this->data[$n]); } 228 | #[\ReturnTypeWillChange] public function offsetGet($offset): void_ptr_ptr { return $this->deref($offset); } 229 | #[\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 230 | #[\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \Error("Cannot unset C structures"); } 231 | #[\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = $value->getData(); } 232 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 233 | /** @return void_ptr_ptr[] */ public function toArray(?int $length = null): array { $ret = []; if ($length === null) { $i = 0; while (null !== $cur = $this->data[$i++]) { $ret[] = new void_ptr_ptr($cur); } } else { for ($i = 0; $i < $length; ++$i) { $ret[] = new void_ptr_ptr($this->data[$i]); } } return $ret; } 234 | public function set(void_ptr | void_ptr_ptr_ptr $value): void { 235 | FFI::addr($this->data)[0] = $value->getData(); 236 | } 237 | public static function getType(): string { return 'void***'; } 238 | public static function size(): int { return testFFI::sizeof(self::class); } 239 | public function getDefinition(): string { return static::getType(); } 240 | } 241 | class void_ptr_ptr_ptr_ptr implements itest, itest_ptr, \ArrayAccess { 242 | private FFI\CData $data; 243 | public function __construct(FFI\CData $data) { $this->data = $data; } 244 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 245 | public function getData(): FFI\CData { return $this->data; } 246 | public function equals(void_ptr_ptr_ptr_ptr $other): bool { return $this->data == $other->data; } 247 | public function addr(): void_ptr_ptr_ptr_ptr_ptr { return new void_ptr_ptr_ptr_ptr_ptr(FFI::addr($this->data)); } 248 | public function deref(int $n = 0): void_ptr_ptr_ptr { return new void_ptr_ptr_ptr($this->data[$n]); } 249 | #[\ReturnTypeWillChange] public function offsetGet($offset): void_ptr_ptr_ptr { return $this->deref($offset); } 250 | #[\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 251 | #[\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \Error("Cannot unset C structures"); } 252 | #[\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = $value->getData(); } 253 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 254 | /** @return void_ptr_ptr_ptr[] */ public function toArray(?int $length = null): array { $ret = []; if ($length === null) { $i = 0; while (null !== $cur = $this->data[$i++]) { $ret[] = new void_ptr_ptr_ptr($cur); } } else { for ($i = 0; $i < $length; ++$i) { $ret[] = new void_ptr_ptr_ptr($this->data[$i]); } } return $ret; } 255 | public function set(void_ptr | void_ptr_ptr_ptr_ptr $value): void { 256 | FFI::addr($this->data)[0] = $value->getData(); 257 | } 258 | public static function getType(): string { return 'void****'; } 259 | public static function size(): int { return testFFI::sizeof(self::class); } 260 | public function getDefinition(): string { return static::getType(); } 261 | } 262 | class long_ptr implements itest, itest_ptr, \ArrayAccess { 263 | private FFI\CData $data; 264 | public function __construct(FFI\CData $data) { $this->data = $data; } 265 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 266 | public function getData(): FFI\CData { return $this->data; } 267 | public function equals(long_ptr $other): bool { return $this->data == $other->data; } 268 | public function addr(): long_ptr_ptr { return new long_ptr_ptr(FFI::addr($this->data)); } 269 | public function deref(int $n = 0): int { return $this->data[$n]; } 270 | #[\ReturnTypeWillChange] public function offsetGet($offset): int { return $this->deref($offset); } 271 | #[\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 272 | #[\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \Error("Cannot unset C structures"); } 273 | #[\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = $value; } 274 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 275 | /** @return int[] */ public function toArray(int $length): array { $ret = []; for ($i = 0; $i < $length; ++$i) { $ret[] = ($this->data[$i]); } return $ret; } 276 | public function set(int | void_ptr | long_ptr $value): void { 277 | if (\is_scalar($value)) { 278 | $this->data[0] = $value; 279 | } else { 280 | FFI::addr($this->data)[0] = $value->getData(); 281 | } 282 | } 283 | public static function getType(): string { return 'long*'; } 284 | public static function size(): int { return testFFI::sizeof(self::class); } 285 | public function getDefinition(): string { return static::getType(); } 286 | } 287 | class long_ptr_ptr implements itest, itest_ptr, \ArrayAccess { 288 | private FFI\CData $data; 289 | public function __construct(FFI\CData $data) { $this->data = $data; } 290 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 291 | public function getData(): FFI\CData { return $this->data; } 292 | public function equals(long_ptr_ptr $other): bool { return $this->data == $other->data; } 293 | public function addr(): long_ptr_ptr_ptr { return new long_ptr_ptr_ptr(FFI::addr($this->data)); } 294 | public function deref(int $n = 0): long_ptr { return new long_ptr($this->data[$n]); } 295 | #[\ReturnTypeWillChange] public function offsetGet($offset): long_ptr { return $this->deref($offset); } 296 | #[\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 297 | #[\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \Error("Cannot unset C structures"); } 298 | #[\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = $value->getData(); } 299 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 300 | /** @return long_ptr[] */ public function toArray(?int $length = null): array { $ret = []; if ($length === null) { $i = 0; while (null !== $cur = $this->data[$i++]) { $ret[] = new long_ptr($cur); } } else { for ($i = 0; $i < $length; ++$i) { $ret[] = new long_ptr($this->data[$i]); } } return $ret; } 301 | public function set(void_ptr | long_ptr_ptr $value): void { 302 | FFI::addr($this->data)[0] = $value->getData(); 303 | } 304 | public static function getType(): string { return 'long**'; } 305 | public static function size(): int { return testFFI::sizeof(self::class); } 306 | public function getDefinition(): string { return static::getType(); } 307 | } 308 | class long_ptr_ptr_ptr implements itest, itest_ptr, \ArrayAccess { 309 | private FFI\CData $data; 310 | public function __construct(FFI\CData $data) { $this->data = $data; } 311 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 312 | public function getData(): FFI\CData { return $this->data; } 313 | public function equals(long_ptr_ptr_ptr $other): bool { return $this->data == $other->data; } 314 | public function addr(): long_ptr_ptr_ptr_ptr { return new long_ptr_ptr_ptr_ptr(FFI::addr($this->data)); } 315 | public function deref(int $n = 0): long_ptr_ptr { return new long_ptr_ptr($this->data[$n]); } 316 | #[\ReturnTypeWillChange] public function offsetGet($offset): long_ptr_ptr { return $this->deref($offset); } 317 | #[\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 318 | #[\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \Error("Cannot unset C structures"); } 319 | #[\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = $value->getData(); } 320 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 321 | /** @return long_ptr_ptr[] */ public function toArray(?int $length = null): array { $ret = []; if ($length === null) { $i = 0; while (null !== $cur = $this->data[$i++]) { $ret[] = new long_ptr_ptr($cur); } } else { for ($i = 0; $i < $length; ++$i) { $ret[] = new long_ptr_ptr($this->data[$i]); } } return $ret; } 322 | public function set(void_ptr | long_ptr_ptr_ptr $value): void { 323 | FFI::addr($this->data)[0] = $value->getData(); 324 | } 325 | public static function getType(): string { return 'long***'; } 326 | public static function size(): int { return testFFI::sizeof(self::class); } 327 | public function getDefinition(): string { return static::getType(); } 328 | } 329 | class long_ptr_ptr_ptr_ptr implements itest, itest_ptr, \ArrayAccess { 330 | private FFI\CData $data; 331 | public function __construct(FFI\CData $data) { $this->data = $data; } 332 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 333 | public function getData(): FFI\CData { return $this->data; } 334 | public function equals(long_ptr_ptr_ptr_ptr $other): bool { return $this->data == $other->data; } 335 | public function addr(): long_ptr_ptr_ptr_ptr_ptr { return new long_ptr_ptr_ptr_ptr_ptr(FFI::addr($this->data)); } 336 | public function deref(int $n = 0): long_ptr_ptr_ptr { return new long_ptr_ptr_ptr($this->data[$n]); } 337 | #[\ReturnTypeWillChange] public function offsetGet($offset): long_ptr_ptr_ptr { return $this->deref($offset); } 338 | #[\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 339 | #[\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \Error("Cannot unset C structures"); } 340 | #[\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = $value->getData(); } 341 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 342 | /** @return long_ptr_ptr_ptr[] */ public function toArray(?int $length = null): array { $ret = []; if ($length === null) { $i = 0; while (null !== $cur = $this->data[$i++]) { $ret[] = new long_ptr_ptr_ptr($cur); } } else { for ($i = 0; $i < $length; ++$i) { $ret[] = new long_ptr_ptr_ptr($this->data[$i]); } } return $ret; } 343 | public function set(void_ptr | long_ptr_ptr_ptr_ptr $value): void { 344 | FFI::addr($this->data)[0] = $value->getData(); 345 | } 346 | public static function getType(): string { return 'long****'; } 347 | public static function size(): int { return testFFI::sizeof(self::class); } 348 | public function getDefinition(): string { return static::getType(); } 349 | } 350 | (function() { self::$staticFFI = \FFI::cdef(testFFI::TYPES_DEF); self::$__arrayWeakMap = new \WeakMap; })->bindTo(null, testFFI::class)(); -------------------------------------------------------------------------------- /examples/01-stdio-builder/example.php: -------------------------------------------------------------------------------- 1 | include("stdio.h") 6 | ->build(); 7 | 8 | $libc->printf("test\n"); -------------------------------------------------------------------------------- /examples/02-stdio-codegen/example.php: -------------------------------------------------------------------------------- 1 | include("stdio.h") 6 | ->codeGen('stdio\\stdio', __DIR__ . '/stdio.php'); 7 | 8 | require __DIR__ . '/stdio.php'; 9 | 10 | $libc = stdio\stdio::ffi(); 11 | 12 | $libc->printf("test\n"); -------------------------------------------------------------------------------- /examples/03-multiple-headers/example.php: -------------------------------------------------------------------------------- 1 | include("stdio.h") 6 | ->include("ctype.h") 7 | ->build(); 8 | 9 | // from stdio.h 10 | $libc->printf("test\n"); 11 | 12 | // from ctype.h 13 | if ($libc->isalpha(ord("h"))) { 14 | echo "It's alpha\n"; 15 | } else { 16 | echo "It's not alpha\n"; 17 | } -------------------------------------------------------------------------------- /lib/BuiltinFunction.php: -------------------------------------------------------------------------------- 1 | name = $name; 15 | $this->type = $type; 16 | $this->body = $body; 17 | 18 | self::$registry[$name] = $this; 19 | } 20 | 21 | public function print(): array { 22 | $return[] = " private function " . Compiler::COMPILED_PREFIX . $this->name . $this->body; 23 | return $return; 24 | } 25 | } 26 | 27 | new BuiltinFunction("__builtin_expect", new CompiledType("int"), '(int $val, int $expected) { return $val; }'); 28 | new BuiltinFunction("__builtin_constant_p", new CompiledType("int"), '(int $val) { return 0; }'); 29 | 30 | new BuiltinFunction("__builtin_bswap16", new CompiledType("int", rawValue: "uint16_t"), '(int $val) { return ($val >> 8) | (($val << 8) & 0xff00); }'); 31 | new BuiltinFunction("__builtin_bswap32", new CompiledType("int", rawValue: "uint32_t"), '(int $val) { return (($val & 0xff000000) >> 24) | (($val & 0xff0000) >> 8) | (($val & 0xff00) << 8) | (($val & 0xff) << 24); }'); 32 | new BuiltinFunction("__builtin_bswap64", new CompiledType("int", rawValue: "uint64_t"), '(int $val) { return (($val & 0xff00000000000000) >> 56) | (($val & 0xff000000000000) >> 40) | (($val & 0xff0000000000) >> 24) | (($val & 0xff00000000) >> 8) | (($val & 0xff000000) << 8) | (($val & 0xff0000) << 24) | (($val & 0xff00) << 40) | (($val & 0xff) << 56); }'); 33 | new BuiltinFunction("__builtin_object_size", new CompiledType("int", rawValue: "size_t"), '(FFI\CData $ptr, int $type) { return $type <= 1 ? -1 : 0; }'); // Defaults when "conditions not met" 34 | 35 | new BuiltinFunction("__builtin_huge_val", new CompiledType("float", rawValue: "double"), '() { return INF; }'); 36 | new BuiltinFunction("__builtin_inf", new CompiledType("float", rawValue: "double"), '() { return INF; }'); 37 | new BuiltinFunction("__builtin_inff", new CompiledType("float"), '() { return INF; }'); 38 | new BuiltinFunction("__builtin_infl", new CompiledType("float", rawValue: "long double"), '() { return INF; }'); 39 | 40 | // __builtin___*_chk functions 41 | new BuiltinFunction("__builtin___memcpy_chk", new CompiledType("void", [null]), '(FFI\CData $dest, FFI\CData $src, int $len, int $destlen) { return $this->ffi->memcpy($dest, $src, $len); }'); 42 | new BuiltinFunction("__builtin___mempcpy_chk", new CompiledType("void", [null]), '(FFI\CData $dest, FFI\CData $src, int $len, int $destlen) { return $this->ffi->mempcpy($dest, $src, $len); }'); 43 | new BuiltinFunction("__builtin___memmove_chk", new CompiledType("void", [null]), '(FFI\CData $dest, FFI\CData $src, int $len, int $destlen) { return $this->ffi->memmove($dest, $src, $len); }'); 44 | new BuiltinFunction("__builtin___memset_chk", new CompiledType("void", [null]), '(FFI\CData $dest, int $c, int $len, int $destlen) { return $this->ffi->memset($dest, $c, $len); }'); 45 | new BuiltinFunction("__builtin___strcpy_chk", new CompiledType("int", [null], rawValue: "char"), '(FFI\CData $dest, FFI\CData $src, int $destlen) { return $this->ffi->strcpy($dest, $src); }'); 46 | new BuiltinFunction("__builtin___strncpy_chk", new CompiledType("int", [null], rawValue: "char"), '(FFI\CData $dest, FFI\CData $src, int $len, int $destlen) { return $this->ffi->strncpy($dest, $src, $len); }'); 47 | new BuiltinFunction("__builtin___stpcpy_chk", new CompiledType("int", [null], rawValue: "char"), '(FFI\CData $dest, FFI\CData $src, int $destlen) { return $this->ffi->stpcpy($dest, $src); }'); 48 | new BuiltinFunction("__builtin___strcat_chk", new CompiledType("int", [null], rawValue: "char"), '(FFI\CData $dest, FFI\CData $src, int $destlen) { return $this->ffi->strcat($dest, $src); }'); 49 | new BuiltinFunction("__builtin___strncat_chk", new CompiledType("int", [null], rawValue: "char"), '(FFI\CData $dest, FFI\CData $src, int $len, int $destlen) { return $this->ffi->strncat($dest, $src, $len); }'); 50 | new BuiltinFunction("__builtin___sprintf_chk", new CompiledType("int"), '(FFI\CData $buf, int $flag, int $destlen, FFI\CData $fmt, ...$args) { return $this->ffi->sprintf($buf, $fmt, ...$args); }'); 51 | new BuiltinFunction("__builtin___snprintf_chk", new CompiledType("int"), '(FFI\CData $buf, int $maxlen, int $flag, int $destlen, FFI\CData $fmt, ...$args) { return $this->ffi->snprintf($buf, $maxlen, $fmt, ...$args); }'); 52 | new BuiltinFunction("__builtin___vsprintf_chk", new CompiledType("int"), '(FFI\CData $buf, int $flag, int $destlen, FFI\CData $fmt, FFI\CData $va_list) { return $this->ffi->vsprintf($buf, $fmt, $va_list); }'); 53 | new BuiltinFunction("__builtin___vsnprintf_chk", new CompiledType("int"), '(FFI\CData $buf, int $maxlen, int $flag, int $destlen, FFI\CData $fmt, FFI\CData $va_list) { return $this->ffi->vsnprintf($buf, $maxlen, $fmt, $va_list); }'); 54 | new BuiltinFunction("__builtin___printf_chk", new CompiledType("int"), '(int $flag, FFI\CData $fmt, ...$args) { return $this->ffi->printf($fmt, ...$args); }'); 55 | new BuiltinFunction("__builtin___vprintf_chk", new CompiledType("int"), '(int $flag, FFI\CData $fmt, FFI\CData $va_list) { return $this->ffi->vprintf($fmt, $va_list); }'); 56 | new BuiltinFunction("__builtin___fprintf_chk", new CompiledType("int"), '(FFI\CData $stream, int $flag, FFI\CData $fmt, ...$args) { return $this->ffi->fprintf($fmt, ...$args); }'); 57 | new BuiltinFunction("__builtin___vfprintf_chk", new CompiledType("int"), '(FFI\CData $stream, int $flag, FFI\CData $fmt, FFI\CData $va_list) { return $this->ffi->vfprintf($fmt, $va_list); }'); 58 | -------------------------------------------------------------------------------- /lib/CompiledExpr.php: -------------------------------------------------------------------------------- 1 | value = $value; 13 | $this->type = $type ?? new CompiledType('int'); 14 | $this->cdata = $cdata; 15 | } 16 | 17 | public function toValue(?CompiledType $type = null, $charConvert = true): string { 18 | $val = $this->value; 19 | if ($type && $this->type != $type) { 20 | if ($this->type->indirections) { 21 | $val = '$this->ffi->cast("' . $type->toValue() . '", ' . $val . ')'; 22 | } elseif (!$type->indirections && ($this->type->rawValue === 'char') !== ($type->rawValue === 'char')) { 23 | if ($type->rawValue === 'char') { 24 | $val = '\chr(' . $val . ')'; 25 | } else { 26 | $val = '\ord(' . $val . ')'; 27 | } 28 | } 29 | return $this->cdata && $type->isNative ? '(' . $val . ')->cdata' : $val; 30 | } else { 31 | if ($this->cdata && $this->type->isNative) { 32 | $val = '(' . $val . ')->cdata'; 33 | } 34 | if ($charConvert && !$this->type->indirections && $this->type->rawValue === 'char') { 35 | $val = '\ord(' . $val . ')'; 36 | } 37 | return $val; 38 | } 39 | } 40 | 41 | public function toBool(): string { 42 | $value = $this->value; 43 | if ($this->cdata) { 44 | if ($this->type->isNative) { 45 | $value = '(' . $this->value . ')->cdata'; 46 | } else { 47 | return '!FFI::isNull(' . $this->value . ')'; 48 | } 49 | } 50 | if ($this->type->rawValue === 'char') { 51 | $value = '(' . $value . ' !== "\0")'; 52 | } 53 | return $value; 54 | } 55 | 56 | public function withCurrent(string $newExpr, int $modifyPointer = 0): self { 57 | $type = $this->type; 58 | $cdata = $this->cdata; 59 | if ($modifyPointer) { 60 | if ($modifyPointer < 0) { 61 | $indirections = array_slice($type->indirections, 1); 62 | } else { 63 | $indirections = []; 64 | } 65 | $type = new CompiledType($type->value, $indirections, $type->rawValue); 66 | $cdata = !$type->isNative; 67 | } 68 | return new self($newExpr, $type, $cdata); 69 | } 70 | } -------------------------------------------------------------------------------- /lib/CompiledFunctionType.php: -------------------------------------------------------------------------------- 1 | return = $return; 17 | if ($args && $args[0]->value === 'void' && $args[0]->indirections() === 0) { 18 | $args = $argNames = []; 19 | } 20 | $this->args = $args; 21 | $this->argNames = $argNames; 22 | $this->isVariadic = $isVariadic; 23 | } 24 | 25 | public function toValue(?string $compiledBaseType = null): string { 26 | return "{$this->return->toValue()}(" . parent::toValue('') . ")(" . implode(', ', array_map(static function(CompiledType $type) { return $type->toValue(); }, $this->args)) . ($this->isVariadic ? ", ..." : "") . ")"; 27 | } 28 | } -------------------------------------------------------------------------------- /lib/CompiledType.php: -------------------------------------------------------------------------------- 1 | value = $value; 16 | $this->indirections = $indirections; 17 | $this->rawValue = $rawValue ?? $value; 18 | $this->isNative = !$indirections && $this->baseTypeIsNative(); 19 | } 20 | 21 | public function indirections(): int { 22 | return \count($this->indirections); 23 | } 24 | 25 | public function toValue(?string $compiledBaseType = null): string { 26 | $value = $compiledBaseType ?? $this->rawValue; 27 | foreach ($this->indirections as $indirection) { 28 | if ($indirection === null || $indirection === false) { 29 | $value .= '*'; 30 | } else { 31 | $value .= "[$indirection]"; 32 | } 33 | } 34 | return $value; 35 | } 36 | 37 | public function baseTypeIsNative(): bool { 38 | return $this->value === 'int' || $this->value === 'float'; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/FFIMe.php: -------------------------------------------------------------------------------- 1 | sofile = $this->findSOFile($sharedObjectFile, $soSearchPaths); 88 | $this->context = new Context($headerSearchPaths); 89 | $this->cparser = new CParser; 90 | $this->symbols = array_flip(ObjectParser::parseFor($this->sofile)->getAllSymbolsRecursively()); 91 | } 92 | 93 | public function defineInt(string $identifier, int $value): void { 94 | $this->context->define($identifier, new Token(Token::NUMBER, (string) $value, 'php')); 95 | } 96 | 97 | public function defineIdentifier(string $identifier, string $value): void { 98 | $this->context->define($identifier, new Token(Token::IDENTIFIER, $value, 'php')); 99 | } 100 | 101 | public function defineString(string $identifier, string $value): void { 102 | $this->context->define($identifier, new Token(Token::LITERAL, $value, 'php')); 103 | } 104 | 105 | public function include(string $header): self { 106 | if ($this->built) { 107 | throw new \RuntimeException("Already built, cannot include twice"); 108 | } 109 | $this->filterDeclarations($this->cparser->parse($header, $this->context)->declarations); 110 | return $this; 111 | } 112 | 113 | public function showWarnings(bool $show) { 114 | $this->showWarnings = $show; 115 | return $this; 116 | } 117 | 118 | public function warning(string $message) { 119 | if ($this->showWarnings) { 120 | echo "[Warning] $message\n"; 121 | } 122 | } 123 | 124 | /** @param string class to code map */ 125 | public function getCode(): array { 126 | return $this->code; 127 | } 128 | 129 | public function codeGen(string $className, string $filename): void { 130 | $code = $this->compile($className); 131 | file_put_contents($filename, 'compile($className, true); 136 | file_put_contents($filename, 'getDynamicClassName(); 141 | eval($this->compile($className)); 142 | return $className::ffi(); 143 | } 144 | 145 | public function getDynamicClassName(): string { 146 | $class = 'ffime\ffime_'; 147 | do { 148 | $class .= mt_rand(0, 99); 149 | } while (class_exists($class)); 150 | return $class; 151 | } 152 | 153 | public function restrictCompiledClasses(?array $classes) { 154 | if ($classes && \is_string(current($classes))) { 155 | $classes = array_fill_keys($classes, 1); 156 | } 157 | $this->restrictedCompiledClasses = $classes; 158 | } 159 | 160 | public function restrictCompiledConstants(?array $constants) { 161 | if ($constants && \is_string(current($constants))) { 162 | $constants = array_fill_keys($constants, 1); 163 | } 164 | $this->restrictedCompiledConstants = $constants; 165 | } 166 | 167 | public function restrictCompiledGlobals(?array $globals) { 168 | if ($globals && \is_string(current($globals))) { 169 | $globals = array_fill_keys($globals, 1); 170 | } 171 | $this->restrictedCompiledGlobals = $globals; 172 | } 173 | 174 | public function restrictCompiledFunctions(?array $functions) { 175 | if ($functions && \is_string(current($functions))) { 176 | $functions = array_fill_keys($functions, false); 177 | } 178 | $this->restrictedCompiledFunctions = $functions; 179 | } 180 | 181 | private function checkTypeForClasses(string $namespace, \ReflectionType $type) { 182 | if ($type instanceof \ReflectionNamedType && str_starts_with($type->getName(), $namespace)) { 183 | $this->restrictedCompiledClasses[substr($type->getName(), \strlen($namespace))] = 1; 184 | } else { 185 | foreach ($type->getTypes() as $subtype) { 186 | $this->checkTypeForClasses($namespace, $subtype); 187 | } 188 | } 189 | } 190 | 191 | private function checkSignaturesForClasses(string $namespace, \ReflectionFunctionAbstract $func) { 192 | if ($ret = $func->getReturnType()) { 193 | $this->checkTypeForClasses($namespace, $ret); 194 | } 195 | foreach ($func->getParameters() as $parameter) { 196 | if ($type = $parameter->getType()) { 197 | $this->checkTypeForClasses($namespace, $type); 198 | } 199 | } 200 | } 201 | 202 | public function compile($className, $instrument = false): string { 203 | if (isset($this->code[$className]) && !$instrument) { 204 | return $this->code[$className]; 205 | } 206 | 207 | $this->filterSymbolDeclarations(); 208 | 209 | $ffiClass = "{$className}FFI"; 210 | if (class_exists($ffiClass, false) && isset($ffiClass::$__visitedClasses)) { 211 | $this->restrictCompiledClasses($ffiClass::$__visitedClasses); 212 | $this->restrictCompiledConstants($ffiClass::$__visitedConstants); 213 | $this->restrictCompiledFunctions($ffiClass::$__visitedFunctions); 214 | $this->restrictCompiledGlobals($ffiClass::$__visitedGlobals); 215 | } 216 | 217 | $compiler = new Compiler($instrument, $this->restrictedCompiledFunctions, $this->restrictedCompiledClasses, $this->restrictedCompiledConstants, $this->restrictedCompiledGlobals); 218 | 219 | $this->numericDefines = $this->context->getNumericDefines(); 220 | $code = $compiler->compile($this->sofile, $this->declarationAst, $this->definitionAst, $this->skippedDeclarationAst, $this->numericDefines, $className); 221 | 222 | foreach ($compiler->warnings as $warning) { 223 | $this->warning($warning); 224 | } 225 | 226 | if (!$instrument) { 227 | $this->code[$className] = $code; 228 | } 229 | 230 | return $code; 231 | } 232 | 233 | /** @param string[] $searchPaths */ 234 | private function findSOFile(string $filename, array $searchPaths): string { 235 | if (is_file($filename)) { 236 | // no searching needed 237 | return $filename; 238 | } 239 | // Under MacOS dyld (runtime linker) has a cache of specific objects tied to paths, which actually do not exist on disk. They have an equivalent path in the SDK, containing their exported symbol definitions 240 | if (PHP_OS_FAMILY === 'Darwin' && str_starts_with($filename, '/usr/lib/')) { 241 | if (is_file(str_replace(".dylib", ".tbd", "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk$filename"))) { 242 | return $filename; 243 | } 244 | } 245 | foreach ($searchPaths as $path) { 246 | $test = $path . '/' . $filename; 247 | if (file_exists($test)) { 248 | return $test; 249 | } 250 | } 251 | throw new \LogicException('Could not find shared object file ' . $filename); 252 | } 253 | 254 | protected function isExternType(Type $type) { 255 | while ($type instanceof Type\AttributedType) { 256 | if ($type instanceof Type\ExplicitAttributedType && $type->kind === Type\ExplicitAttributedType::KIND_EXTERN) { 257 | return true; 258 | } 259 | $type = $type->parent; 260 | } 261 | return false; 262 | } 263 | 264 | protected function filterSymbolDeclarations(): void { 265 | $result = $skipped = []; 266 | foreach ($this->declarationAst as $declaration) { 267 | if ($declaration instanceof Decl\NamedDecl\ValueDecl\DeclaratorDecl\FunctionDecl) { 268 | if (isset($this->symbols[$declaration->name]) || isset($this->symbols["_{$declaration->name}"])) { 269 | $result[] = $declaration; 270 | } else { 271 | $skipped[] = $declaration; 272 | $this->warning("Skipping {$declaration->name}, not found in object file"); 273 | } 274 | } elseif ($declaration instanceof Decl\NamedDecl\ValueDecl\DeclaratorDecl\VarDecl) { 275 | if ($this->isExternType($declaration->type)) { 276 | if (isset($this->symbols[$declaration->name]) || isset($this->symbols["_{$declaration->name}"])) { 277 | $result[] = $declaration; 278 | } else { 279 | $skipped[] = $declaration; 280 | $this->warning("Skipping {$declaration->name}, not found in object file"); 281 | } 282 | } else { 283 | array_unshift($this->definitionAst, $declaration); 284 | } 285 | } else { 286 | $result[] = $declaration; 287 | } 288 | } 289 | $this->declarationAst = $result; 290 | $this->skippedDeclarationAst = $skipped; 291 | } 292 | 293 | /** @param Decl[] $declarations */ 294 | protected function filterDeclarations(array $declarations) { 295 | $result = []; 296 | foreach ($declarations as $declaration) { 297 | if ($declaration instanceof Decl\NamedDecl\TypeDecl\TypedefNameDecl\TypedefDecl) { 298 | if (!in_array($declaration->name, self::TYPES_TO_REMOVE)) { 299 | $result[] = $declaration; 300 | } 301 | } elseif ($declaration instanceof Decl\NamedDecl\TypeDecl\TagDecl\RecordDecl) { 302 | if (!in_array($declaration->name, self::RECORDS_TO_REMOVE)) { 303 | $result[] = $declaration; 304 | } 305 | } elseif ($declaration instanceof Decl\NamedDecl\ValueDecl\DeclaratorDecl\FunctionDecl) { 306 | if ($declaration->stmts === null) { 307 | // only declarations, not definitions 308 | if (!in_array($declaration->name, self::FUNCTIONS_TO_REMOVE)) { 309 | // Skip __ functions 310 | $result[] = $declaration; 311 | } 312 | } else { 313 | if (!in_array($declaration->name, self::FUNCTIONS_TO_REMOVE)) { 314 | // Skip __ functions 315 | $this->definitionAst[] = $declaration; 316 | } 317 | } 318 | } elseif ($declaration instanceof Decl\NamedDecl\ValueDecl\DeclaratorDecl\VarDecl) { 319 | if (!in_array($declaration->name, self::VARS_TO_REMOVE)) { 320 | $result[] = $declaration; 321 | } 322 | } elseif ($declaration instanceof Decl\NamedDecl\TypeDecl\TagDecl\EnumDecl) { 323 | $result[] = $declaration; 324 | } else { 325 | throw new \LogicException('Unknown declaration type to skip/ignore: ' . get_class($declaration)); 326 | } 327 | } 328 | $this->declarationAst = array_merge($this->declarationAst, $result); 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /lib/NotExportedFunctionException.php: -------------------------------------------------------------------------------- 1 | feature = $feature; 12 | parent::__construct($message); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 14 | ./test/generated 15 | 16 | 17 | ./test/InlineFunctions 18 | 19 | 20 | 21 | 22 | 23 | ./lib/ 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /test/InlineFunctions/InlineTestcase.php: -------------------------------------------------------------------------------- 1 | include($headerfile); 21 | 22 | $phpfile = __DIR__ . '/generated/' . $classname . '/defs.php'; 23 | $ffi->codeGen(__NAMESPACE__ . '\\generated\\'. $classname . '\\Defs', $phpfile); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/InlineFunctions/basic_strings.php: -------------------------------------------------------------------------------- 1 | compile(<<
9 | #include 10 | #include 11 | #include 12 | 13 | static inline char *getUppercaseString(char *str) { 14 | int len = strlen(str); 15 | char *upper = malloc(len + 1), *ret; 16 | for (int i = 0; i < len; ++i) { 17 | upper[i] = toupper(*(str++)); 18 | } 19 | upper[len] = 0; 20 | ret = malloc(50); 21 | sprintf(ret, "len: %d str: %s", strlen(upper), upper); 22 | free(upper); 23 | return ret; 24 | } 25 | HEADER); 26 | 27 | $testCase = generated\BasicStrings\Defs::ffi(); 28 | $str = $testCase->getUppercaseString("lower case string"); 29 | $this->assertSame('len: 17 str: LOWER CASE STRING', $str->toString()); 30 | $testCase->free($str); 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /test/InlineFunctions/basic_structs.php: -------------------------------------------------------------------------------- 1 | compile(<<
bar = (bar_t){ .val = foo->value[1] }; 40 | } 41 | HEADER); 42 | 43 | $testCase = generated\BasicStructs\Defs::ffi(); 44 | $str = string_::persistentZero("some str"); 45 | $foo = $testCase->init_foo($str); 46 | 47 | $this->assertSame(0, $foo->value[0]); 48 | $this->assertSame(1, $foo->value[1]); 49 | $this->assertSame(2, $foo->u->second_value); 50 | $this->assertSame("some str", $foo->str->toString()); 51 | $this->assertSame(3, $foo->bar->val); 52 | $this->assertSame(3, $testCase->global_bar->val); 53 | 54 | $testCase->update_foo_bar($foo->addr()); 55 | 56 | $this->assertSame(1, $foo->bar->val); 57 | 58 | \FFI::free($str->getData()); 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /test/InlineFunctions/duffs_device.php: -------------------------------------------------------------------------------- 1 | compile(<<
assertSame(32 * (9 ** 3), $testCase->calcHash(1, \chr(32))); 31 | $this->assertSame((32 * (9 ** 3)) ^ ((9 ** 4) * 64) ^ ((9 ** 5) * 16) ^ ((9 ** 6) * 48) ^ ((9 ** 7) * 80), $testCase->calcHash(5, \chr(32) . \chr(64) . \chr(16) . \chr(48) . \chr(80))); 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /test/InlineFunctions/function_pointers.php: -------------------------------------------------------------------------------- 1 | compile(<<
global_bar = \Closure::fromCallable([$testCase, 'init_other_bar']); 36 | self::assertSame("oo", $testCase->call_bar(1)->toString()); 37 | 38 | $testCase->set_bar_ptr(); 39 | self::assertSame("foo", $testCase->call_bar(1)->toString()); 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /test/InlineFunctions/generated/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /test/InlineFunctions/passing_string_array.php: -------------------------------------------------------------------------------- 1 | compile(<<
collect_strs(["foo", "bar", null]); 31 | 32 | $this->assertSame("foo", $foo->str1->toString()); 33 | $this->assertSame("bar", $foo->str2->toString()); 34 | $this->assertSame(1, $foo->isNull); 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /test/cases/basic_enums.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test basic parsing of enums 3 | --FILE-- 4 | enum A { 5 | A1, 6 | A2, 7 | }; 8 | typedef enum { 9 | B1, 10 | B2, 11 | } B; 12 | extern void something(B b); 13 | 14 | --EXPECTF-- 15 | ffi = FFI::cdef(self::HEADER_DEF, $pathToSoFile); 53 | } 54 | 55 | public static function cast(itest $from, string $to): itest { 56 | if (!is_a($to, itest::class, true)) { 57 | throw new \LogicException("Cannot cast to a non-wrapper type"); 58 | } 59 | return new $to(self::$staticFFI->cast($to::getType(), $from->getData())); 60 | } 61 | 62 | public static function makeArray(string $class, int|array $elements): itest { 63 | $type = $class::getType(); 64 | if (substr($type, -1) !== "*") { 65 | throw new \LogicException("Attempting to make a non-pointer element into an array"); 66 | } 67 | if (is_int($elements)) { 68 | $cdata = self::$staticFFI->new(substr($type, 0, -1) . "[$elements]"); 69 | } else { 70 | $cdata = self::$staticFFI->new(substr($type, 0, -1) . "[" . count($elements) . "]"); 71 | foreach ($elements as $key => $raw) { 72 | $cdata[$key] = \is_scalar($raw) ? \is_int($raw) && $type === "char*" ? \chr($raw) : $raw : $raw->getData(); 73 | } 74 | } 75 | $object = new $class(self::$staticFFI->cast($type, \FFI::addr($cdata))); 76 | self::$__arrayWeakMap[$object] = $cdata; 77 | return $object; 78 | } 79 | 80 | public static function sizeof($classOrObject): int { 81 | if (is_object($classOrObject) && $classOrObject instanceof itest) { 82 | return FFI::sizeof($classOrObject->getData()); 83 | } elseif (is_a($classOrObject, itest::class, true)) { 84 | return FFI::sizeof(self::$staticFFI->type($classOrObject::getType())); 85 | } else { 86 | throw new \LogicException("Unknown class/object passed to sizeof()"); 87 | } 88 | } 89 | 90 | public function getFFI(): FFI { 91 | return $this->ffi; 92 | } 93 | 94 | 95 | public function __get(string $name) { 96 | switch($name) { 97 | default: return $this->ffi->$name; 98 | } 99 | } 100 | public function __set(string $name, $value) { 101 | switch($name) { 102 | default: return $this->ffi->$name; 103 | } 104 | } 105 | public function __allocCachedString(string $str): FFI\CData { 106 | return $this->__literalStrings[$str] ??= string_::ownedZero($str)->getData(); 107 | } 108 | public function something(int $b): void { 109 | $this->ffi->something($b); 110 | } 111 | } 112 | 113 | class string_ implements itest, itest_ptr, \ArrayAccess { 114 | private FFI\CData $data; 115 | public function __construct(FFI\CData $data) { $this->data = $data; } 116 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 117 | public function getData(): FFI\CData { return $this->data; } 118 | public function equals(string_ $other): bool { return $this->data == $other->data; } 119 | public function addr(): string_ptr { return new string_ptr(FFI::addr($this->data)); } 120 | public function deref(int $n = 0): int { return \ord($this->data[$n]); } 121 | #[\ReturnTypeWillChange] public function offsetGet($offset): int { return $this->deref($offset); } 122 | #[\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 123 | #[\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \Error("Cannot unset C structures"); } 124 | #[\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = \chr($value); } 125 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 126 | /** @return int[] */ public function toArray(?int $length = null): array { $ret = []; if ($length === null) { $i = 0; while ("\0" !== $cur = $this->data[$i++]) { $ret[] = \ord($cur); } } else { for ($i = 0; $i < $length; ++$i) { $ret[] = \ord($this->data[$i]); } } return $ret; } 127 | public function toString(?int $length = null): string { return $length === null ? FFI::string($this->data) : FFI::string($this->data, $length); } 128 | public static function persistent(string $string): self { $str = new self(FFI::cdef()->new("char[" . \strlen($string) . "]", false)); FFI::memcpy($str->data, $string, \strlen($string)); return $str; } 129 | public static function owned(string $string): self { $str = new self(FFI::cdef()->new("char[" . \strlen($string) . "]", true)); FFI::memcpy($str->data, $string, \strlen($string)); return $str; } 130 | public static function persistentZero(string $string): self { return self::persistent("$string\0"); } 131 | public static function ownedZero(string $string): self { return self::owned("$string\0"); } 132 | public function set(int | void_ptr | string_ $value): void { 133 | if (\is_scalar($value)) { 134 | $this->data[0] = \chr($value); 135 | } else { 136 | FFI::addr($this->data)[0] = $value->getData(); 137 | } 138 | } 139 | public static function getType(): string { return 'char*'; } 140 | public static function size(): int { return testFFI::sizeof(self::class); } 141 | public function getDefinition(): string { return static::getType(); } 142 | } 143 | class string_ptr implements itest, itest_ptr, \ArrayAccess { 144 | private FFI\CData $data; 145 | public function __construct(FFI\CData $data) { $this->data = $data; } 146 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 147 | public function getData(): FFI\CData { return $this->data; } 148 | public function equals(string_ptr $other): bool { return $this->data == $other->data; } 149 | public function addr(): string_ptr_ptr { return new string_ptr_ptr(FFI::addr($this->data)); } 150 | public function deref(int $n = 0): string_ { return new string_($this->data[$n]); } 151 | #[\ReturnTypeWillChange] public function offsetGet($offset): string_ { return $this->deref($offset); } 152 | #[\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 153 | #[\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \Error("Cannot unset C structures"); } 154 | #[\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = $value->getData(); } 155 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 156 | /** @return string_[] */ public function toArray(?int $length = null): array { $ret = []; if ($length === null) { $i = 0; while (null !== $cur = $this->data[$i++]) { $ret[] = new string_($cur); } } else { for ($i = 0; $i < $length; ++$i) { $ret[] = new string_($this->data[$i]); } } return $ret; } 157 | public function set(void_ptr | string_ptr $value): void { 158 | FFI::addr($this->data)[0] = $value->getData(); 159 | } 160 | public static function getType(): string { return 'char**'; } 161 | public static function size(): int { return testFFI::sizeof(self::class); } 162 | public function getDefinition(): string { return static::getType(); } 163 | } 164 | class string_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 165 | class string_ptr_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 166 | class void_ptr implements itest, itest_ptr { 167 | private FFI\CData $data; 168 | public function __construct(FFI\CData $data) { $this->data = $data; } 169 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 170 | public function getData(): FFI\CData { return $this->data; } 171 | public function equals(void_ptr $other): bool { return $this->data == $other->data; } 172 | public function addr(): void_ptr_ptr { return new void_ptr_ptr(FFI::addr($this->data)); } 173 | public function set(itest_ptr $value): void { 174 | FFI::addr($this->data)[0] = $value->getData(); 175 | } 176 | public static function getType(): string { return 'void*'; } 177 | public static function size(): int { return testFFI::sizeof(self::class); } 178 | public function getDefinition(): string { return static::getType(); } 179 | } 180 | class void_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 181 | class void_ptr_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 182 | class void_ptr_ptr_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 183 | class int_ptr implements itest, itest_ptr, \ArrayAccess { 184 | private FFI\CData $data; 185 | public function __construct(FFI\CData $data) { $this->data = $data; } 186 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 187 | public function getData(): FFI\CData { return $this->data; } 188 | public function equals(int_ptr $other): bool { return $this->data == $other->data; } 189 | public function addr(): int_ptr_ptr { return new int_ptr_ptr(FFI::addr($this->data)); } 190 | public function deref(int $n = 0): int { return $this->data[$n]; } 191 | #[\ReturnTypeWillChange] public function offsetGet($offset): int { return $this->deref($offset); } 192 | #[\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 193 | #[\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \Error("Cannot unset C structures"); } 194 | #[\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = $value; } 195 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 196 | /** @return int[] */ public function toArray(int $length): array { $ret = []; for ($i = 0; $i < $length; ++$i) { $ret[] = ($this->data[$i]); } return $ret; } 197 | public function set(int | void_ptr | int_ptr $value): void { 198 | if (\is_scalar($value)) { 199 | $this->data[0] = $value; 200 | } else { 201 | FFI::addr($this->data)[0] = $value->getData(); 202 | } 203 | } 204 | public static function getType(): string { return 'int*'; } 205 | public static function size(): int { return testFFI::sizeof(self::class); } 206 | public function getDefinition(): string { return static::getType(); } 207 | } 208 | class int_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 209 | class int_ptr_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 210 | class int_ptr_ptr_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 211 | (function() { self::$staticFFI = \FFI::cdef(testFFI::TYPES_DEF); self::$__arrayWeakMap = new \WeakMap; })->bindTo(null, testFFI::class)(); -------------------------------------------------------------------------------- /test/cases/basic_strings.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test basic parsing of strings 3 | --FILE-- 4 | 5 | void setFoo(char* value); 6 | 7 | char* getFoo(); 8 | 9 | --EXPECTF-- 10 | ffi = FFI::cdef(self::HEADER_DEF, $pathToSoFile); 37 | } 38 | 39 | public static function cast(itest $from, string $to): itest { 40 | if (!is_a($to, itest::class, true)) { 41 | throw new \LogicException("Cannot cast to a non-wrapper type"); 42 | } 43 | return new $to(self::$staticFFI->cast($to::getType(), $from->getData())); 44 | } 45 | 46 | public static function makeArray(string $class, int|array $elements): itest { 47 | $type = $class::getType(); 48 | if (substr($type, -1) !== "*") { 49 | throw new \LogicException("Attempting to make a non-pointer element into an array"); 50 | } 51 | if (is_int($elements)) { 52 | $cdata = self::$staticFFI->new(substr($type, 0, -1) . "[$elements]"); 53 | } else { 54 | $cdata = self::$staticFFI->new(substr($type, 0, -1) . "[" . count($elements) . "]"); 55 | foreach ($elements as $key => $raw) { 56 | $cdata[$key] = \is_scalar($raw) ? \is_int($raw) && $type === "char*" ? \chr($raw) : $raw : $raw->getData(); 57 | } 58 | } 59 | $object = new $class(self::$staticFFI->cast($type, \FFI::addr($cdata))); 60 | self::$__arrayWeakMap[$object] = $cdata; 61 | return $object; 62 | } 63 | 64 | public static function sizeof($classOrObject): int { 65 | if (is_object($classOrObject) && $classOrObject instanceof itest) { 66 | return FFI::sizeof($classOrObject->getData()); 67 | } elseif (is_a($classOrObject, itest::class, true)) { 68 | return FFI::sizeof(self::$staticFFI->type($classOrObject::getType())); 69 | } else { 70 | throw new \LogicException("Unknown class/object passed to sizeof()"); 71 | } 72 | } 73 | 74 | public function getFFI(): FFI { 75 | return $this->ffi; 76 | } 77 | 78 | 79 | public function __get(string $name) { 80 | switch($name) { 81 | default: return $this->ffi->$name; 82 | } 83 | } 84 | public function __set(string $name, $value) { 85 | switch($name) { 86 | default: return $this->ffi->$name; 87 | } 88 | } 89 | public function __allocCachedString(string $str): FFI\CData { 90 | return $this->__literalStrings[$str] ??= string_::ownedZero($str)->getData(); 91 | } 92 | public function setFoo(void_ptr | string_ | null | string | array $value): void { 93 | $__ffi_internal_refsvalue = []; 94 | if (\is_string($value)) { 95 | $value = string_::ownedZero($value)->getData(); 96 | } elseif (\is_array($value)) { 97 | $_ = $this->ffi->new("char[" . \count($value) . "]"); 98 | $_i = 0; 99 | if ($value) { 100 | if ($_ref = \ReflectionReference::fromArrayElement($value, \key($value))) { 101 | foreach ($value as $_k => $_v) { 102 | $__ffi_internal_refsvalue[$_i] = &$value[$_k]; 103 | $_[$_i++] = $_v ?? 0; 104 | } 105 | $__ffi_internal_originalvalue = $value = $_; 106 | } else { 107 | foreach ($value as $_v) { 108 | $_[$_i++] = $_v ?? 0; 109 | } 110 | $value = $_; 111 | } 112 | } 113 | } else { 114 | $value = $value?->getData(); 115 | if ($value !== null) { 116 | $value = $this->ffi->cast("char*", $value); 117 | } 118 | } 119 | $this->ffi->setFoo($value); 120 | foreach ($__ffi_internal_refsvalue as $_k => &$__ffi_internal_ref_v) { 121 | $__ffi_internal_ref_v = $__ffi_internal_originalvalue[$_k]; 122 | $__ffi_internal_ref_v = \ord($__ffi_internal_ref_v); 123 | } 124 | } 125 | public function getFoo(): ?string_ { 126 | $result = $this->ffi->getFoo(); 127 | return $result === null ? null : new string_($result); 128 | } 129 | } 130 | 131 | class string_ implements itest, itest_ptr, \ArrayAccess { 132 | private FFI\CData $data; 133 | public function __construct(FFI\CData $data) { $this->data = $data; } 134 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 135 | public function getData(): FFI\CData { return $this->data; } 136 | public function equals(string_ $other): bool { return $this->data == $other->data; } 137 | public function addr(): string_ptr { return new string_ptr(FFI::addr($this->data)); } 138 | public function deref(int $n = 0): int { return \ord($this->data[$n]); } 139 | #[\ReturnTypeWillChange] public function offsetGet($offset): int { return $this->deref($offset); } 140 | #[\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 141 | #[\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \Error("Cannot unset C structures"); } 142 | #[\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = \chr($value); } 143 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 144 | /** @return int[] */ public function toArray(?int $length = null): array { $ret = []; if ($length === null) { $i = 0; while ("\0" !== $cur = $this->data[$i++]) { $ret[] = \ord($cur); } } else { for ($i = 0; $i < $length; ++$i) { $ret[] = \ord($this->data[$i]); } } return $ret; } 145 | public function toString(?int $length = null): string { return $length === null ? FFI::string($this->data) : FFI::string($this->data, $length); } 146 | public static function persistent(string $string): self { $str = new self(FFI::cdef()->new("char[" . \strlen($string) . "]", false)); FFI::memcpy($str->data, $string, \strlen($string)); return $str; } 147 | public static function owned(string $string): self { $str = new self(FFI::cdef()->new("char[" . \strlen($string) . "]", true)); FFI::memcpy($str->data, $string, \strlen($string)); return $str; } 148 | public static function persistentZero(string $string): self { return self::persistent("$string\0"); } 149 | public static function ownedZero(string $string): self { return self::owned("$string\0"); } 150 | public function set(int | void_ptr | string_ $value): void { 151 | if (\is_scalar($value)) { 152 | $this->data[0] = \chr($value); 153 | } else { 154 | FFI::addr($this->data)[0] = $value->getData(); 155 | } 156 | } 157 | public static function getType(): string { return 'char*'; } 158 | public static function size(): int { return testFFI::sizeof(self::class); } 159 | public function getDefinition(): string { return static::getType(); } 160 | } 161 | class string_ptr implements itest, itest_ptr, \ArrayAccess { 162 | private FFI\CData $data; 163 | public function __construct(FFI\CData $data) { $this->data = $data; } 164 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 165 | public function getData(): FFI\CData { return $this->data; } 166 | public function equals(string_ptr $other): bool { return $this->data == $other->data; } 167 | public function addr(): string_ptr_ptr { return new string_ptr_ptr(FFI::addr($this->data)); } 168 | public function deref(int $n = 0): string_ { return new string_($this->data[$n]); } 169 | #[\ReturnTypeWillChange] public function offsetGet($offset): string_ { return $this->deref($offset); } 170 | #[\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 171 | #[\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \Error("Cannot unset C structures"); } 172 | #[\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = $value->getData(); } 173 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 174 | /** @return string_[] */ public function toArray(?int $length = null): array { $ret = []; if ($length === null) { $i = 0; while (null !== $cur = $this->data[$i++]) { $ret[] = new string_($cur); } } else { for ($i = 0; $i < $length; ++$i) { $ret[] = new string_($this->data[$i]); } } return $ret; } 175 | public function set(void_ptr | string_ptr $value): void { 176 | FFI::addr($this->data)[0] = $value->getData(); 177 | } 178 | public static function getType(): string { return 'char**'; } 179 | public static function size(): int { return testFFI::sizeof(self::class); } 180 | public function getDefinition(): string { return static::getType(); } 181 | } 182 | class string_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 183 | class string_ptr_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 184 | class void_ptr implements itest, itest_ptr { 185 | private FFI\CData $data; 186 | public function __construct(FFI\CData $data) { $this->data = $data; } 187 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 188 | public function getData(): FFI\CData { return $this->data; } 189 | public function equals(void_ptr $other): bool { return $this->data == $other->data; } 190 | public function addr(): void_ptr_ptr { return new void_ptr_ptr(FFI::addr($this->data)); } 191 | public function set(itest_ptr $value): void { 192 | FFI::addr($this->data)[0] = $value->getData(); 193 | } 194 | public static function getType(): string { return 'void*'; } 195 | public static function size(): int { return testFFI::sizeof(self::class); } 196 | public function getDefinition(): string { return static::getType(); } 197 | } 198 | class void_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 199 | class void_ptr_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 200 | class void_ptr_ptr_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 201 | (function() { self::$staticFFI = \FFI::cdef(testFFI::TYPES_DEF); self::$__arrayWeakMap = new \WeakMap; })->bindTo(null, testFFI::class)(); -------------------------------------------------------------------------------- /test/cases/basic_typedefs.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test basic parsing of typedefs 3 | --FILE-- 4 | typedef long int intmax_t; 5 | typedef unsigned long int uintmax_t; 6 | 7 | intmax_t foo(intmax_t a); 8 | 9 | extern uintmax_t blah(); 10 | 11 | --EXPECTF-- 12 | ffi = FFI::cdef(self::HEADER_DEF, $pathToSoFile); 41 | } 42 | 43 | public static function cast(itest $from, string $to): itest { 44 | if (!is_a($to, itest::class, true)) { 45 | throw new \LogicException("Cannot cast to a non-wrapper type"); 46 | } 47 | return new $to(self::$staticFFI->cast($to::getType(), $from->getData())); 48 | } 49 | 50 | public static function makeArray(string $class, int|array $elements): itest { 51 | $type = $class::getType(); 52 | if (substr($type, -1) !== "*") { 53 | throw new \LogicException("Attempting to make a non-pointer element into an array"); 54 | } 55 | if (is_int($elements)) { 56 | $cdata = self::$staticFFI->new(substr($type, 0, -1) . "[$elements]"); 57 | } else { 58 | $cdata = self::$staticFFI->new(substr($type, 0, -1) . "[" . count($elements) . "]"); 59 | foreach ($elements as $key => $raw) { 60 | $cdata[$key] = \is_scalar($raw) ? \is_int($raw) && $type === "char*" ? \chr($raw) : $raw : $raw->getData(); 61 | } 62 | } 63 | $object = new $class(self::$staticFFI->cast($type, \FFI::addr($cdata))); 64 | self::$__arrayWeakMap[$object] = $cdata; 65 | return $object; 66 | } 67 | 68 | public static function sizeof($classOrObject): int { 69 | if (is_object($classOrObject) && $classOrObject instanceof itest) { 70 | return FFI::sizeof($classOrObject->getData()); 71 | } elseif (is_a($classOrObject, itest::class, true)) { 72 | return FFI::sizeof(self::$staticFFI->type($classOrObject::getType())); 73 | } else { 74 | throw new \LogicException("Unknown class/object passed to sizeof()"); 75 | } 76 | } 77 | 78 | public function getFFI(): FFI { 79 | return $this->ffi; 80 | } 81 | 82 | 83 | public function __get(string $name) { 84 | switch($name) { 85 | default: return $this->ffi->$name; 86 | } 87 | } 88 | public function __set(string $name, $value) { 89 | switch($name) { 90 | default: return $this->ffi->$name; 91 | } 92 | } 93 | public function __allocCachedString(string $str): FFI\CData { 94 | return $this->__literalStrings[$str] ??= string_::ownedZero($str)->getData(); 95 | } 96 | public function foo(int $a): int { 97 | $result = $this->ffi->foo($a); 98 | return $result; 99 | } 100 | public function blah(): int { 101 | $result = $this->ffi->blah(); 102 | return $result; 103 | } 104 | } 105 | 106 | class string_ implements itest, itest_ptr, \ArrayAccess { 107 | private FFI\CData $data; 108 | public function __construct(FFI\CData $data) { $this->data = $data; } 109 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 110 | public function getData(): FFI\CData { return $this->data; } 111 | public function equals(string_ $other): bool { return $this->data == $other->data; } 112 | public function addr(): string_ptr { return new string_ptr(FFI::addr($this->data)); } 113 | public function deref(int $n = 0): int { return \ord($this->data[$n]); } 114 | #[\ReturnTypeWillChange] public function offsetGet($offset): int { return $this->deref($offset); } 115 | #[\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 116 | #[\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \Error("Cannot unset C structures"); } 117 | #[\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = \chr($value); } 118 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 119 | /** @return int[] */ public function toArray(?int $length = null): array { $ret = []; if ($length === null) { $i = 0; while ("\0" !== $cur = $this->data[$i++]) { $ret[] = \ord($cur); } } else { for ($i = 0; $i < $length; ++$i) { $ret[] = \ord($this->data[$i]); } } return $ret; } 120 | public function toString(?int $length = null): string { return $length === null ? FFI::string($this->data) : FFI::string($this->data, $length); } 121 | public static function persistent(string $string): self { $str = new self(FFI::cdef()->new("char[" . \strlen($string) . "]", false)); FFI::memcpy($str->data, $string, \strlen($string)); return $str; } 122 | public static function owned(string $string): self { $str = new self(FFI::cdef()->new("char[" . \strlen($string) . "]", true)); FFI::memcpy($str->data, $string, \strlen($string)); return $str; } 123 | public static function persistentZero(string $string): self { return self::persistent("$string\0"); } 124 | public static function ownedZero(string $string): self { return self::owned("$string\0"); } 125 | public function set(int | void_ptr | string_ $value): void { 126 | if (\is_scalar($value)) { 127 | $this->data[0] = \chr($value); 128 | } else { 129 | FFI::addr($this->data)[0] = $value->getData(); 130 | } 131 | } 132 | public static function getType(): string { return 'char*'; } 133 | public static function size(): int { return testFFI::sizeof(self::class); } 134 | public function getDefinition(): string { return static::getType(); } 135 | } 136 | class string_ptr implements itest, itest_ptr, \ArrayAccess { 137 | private FFI\CData $data; 138 | public function __construct(FFI\CData $data) { $this->data = $data; } 139 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 140 | public function getData(): FFI\CData { return $this->data; } 141 | public function equals(string_ptr $other): bool { return $this->data == $other->data; } 142 | public function addr(): string_ptr_ptr { return new string_ptr_ptr(FFI::addr($this->data)); } 143 | public function deref(int $n = 0): string_ { return new string_($this->data[$n]); } 144 | #[\ReturnTypeWillChange] public function offsetGet($offset): string_ { return $this->deref($offset); } 145 | #[\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 146 | #[\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \Error("Cannot unset C structures"); } 147 | #[\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = $value->getData(); } 148 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 149 | /** @return string_[] */ public function toArray(?int $length = null): array { $ret = []; if ($length === null) { $i = 0; while (null !== $cur = $this->data[$i++]) { $ret[] = new string_($cur); } } else { for ($i = 0; $i < $length; ++$i) { $ret[] = new string_($this->data[$i]); } } return $ret; } 150 | public function set(void_ptr | string_ptr $value): void { 151 | FFI::addr($this->data)[0] = $value->getData(); 152 | } 153 | public static function getType(): string { return 'char**'; } 154 | public static function size(): int { return testFFI::sizeof(self::class); } 155 | public function getDefinition(): string { return static::getType(); } 156 | } 157 | class string_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 158 | class string_ptr_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 159 | class void_ptr implements itest, itest_ptr { 160 | private FFI\CData $data; 161 | public function __construct(FFI\CData $data) { $this->data = $data; } 162 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 163 | public function getData(): FFI\CData { return $this->data; } 164 | public function equals(void_ptr $other): bool { return $this->data == $other->data; } 165 | public function addr(): void_ptr_ptr { return new void_ptr_ptr(FFI::addr($this->data)); } 166 | public function set(itest_ptr $value): void { 167 | FFI::addr($this->data)[0] = $value->getData(); 168 | } 169 | public static function getType(): string { return 'void*'; } 170 | public static function size(): int { return testFFI::sizeof(self::class); } 171 | public function getDefinition(): string { return static::getType(); } 172 | } 173 | class void_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 174 | class void_ptr_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 175 | class void_ptr_ptr_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 176 | class long_int_ptr implements itest, itest_ptr, \ArrayAccess { 177 | private FFI\CData $data; 178 | public function __construct(FFI\CData $data) { $this->data = $data; } 179 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 180 | public function getData(): FFI\CData { return $this->data; } 181 | public function equals(long_int_ptr $other): bool { return $this->data == $other->data; } 182 | public function addr(): long_int_ptr_ptr { return new long_int_ptr_ptr(FFI::addr($this->data)); } 183 | public function deref(int $n = 0): int { return $this->data[$n]; } 184 | #[\ReturnTypeWillChange] public function offsetGet($offset): int { return $this->deref($offset); } 185 | #[\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 186 | #[\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \Error("Cannot unset C structures"); } 187 | #[\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = $value; } 188 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 189 | /** @return int[] */ public function toArray(int $length): array { $ret = []; for ($i = 0; $i < $length; ++$i) { $ret[] = ($this->data[$i]); } return $ret; } 190 | public function set(int | void_ptr | long_int_ptr $value): void { 191 | if (\is_scalar($value)) { 192 | $this->data[0] = $value; 193 | } else { 194 | FFI::addr($this->data)[0] = $value->getData(); 195 | } 196 | } 197 | public static function getType(): string { return 'long_int*'; } 198 | public static function size(): int { return testFFI::sizeof(self::class); } 199 | public function getDefinition(): string { return static::getType(); } 200 | } 201 | class long_int_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 202 | class long_int_ptr_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 203 | class long_int_ptr_ptr_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 204 | class unsigned_long_int_ptr implements itest, itest_ptr, \ArrayAccess { 205 | private FFI\CData $data; 206 | public function __construct(FFI\CData $data) { $this->data = $data; } 207 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 208 | public function getData(): FFI\CData { return $this->data; } 209 | public function equals(unsigned_long_int_ptr $other): bool { return $this->data == $other->data; } 210 | public function addr(): unsigned_long_int_ptr_ptr { return new unsigned_long_int_ptr_ptr(FFI::addr($this->data)); } 211 | public function deref(int $n = 0): int { return $this->data[$n]; } 212 | #[\ReturnTypeWillChange] public function offsetGet($offset): int { return $this->deref($offset); } 213 | #[\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 214 | #[\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \Error("Cannot unset C structures"); } 215 | #[\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = $value; } 216 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 217 | /** @return int[] */ public function toArray(int $length): array { $ret = []; for ($i = 0; $i < $length; ++$i) { $ret[] = ($this->data[$i]); } return $ret; } 218 | public function set(int | void_ptr | unsigned_long_int_ptr $value): void { 219 | if (\is_scalar($value)) { 220 | $this->data[0] = $value; 221 | } else { 222 | FFI::addr($this->data)[0] = $value->getData(); 223 | } 224 | } 225 | public static function getType(): string { return 'unsigned_long_int*'; } 226 | public static function size(): int { return testFFI::sizeof(self::class); } 227 | public function getDefinition(): string { return static::getType(); } 228 | } 229 | class unsigned_long_int_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 230 | class unsigned_long_int_ptr_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 231 | class unsigned_long_int_ptr_ptr_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 232 | (function() { self::$staticFFI = \FFI::cdef(testFFI::TYPES_DEF); self::$__arrayWeakMap = new \WeakMap; })->bindTo(null, testFFI::class)(); 233 | \class_alias(long_int_ptr::class, intmax_t_ptr::class); 234 | \class_alias(long_int_ptr_ptr::class, intmax_t_ptr_ptr::class); 235 | \class_alias(long_int_ptr_ptr_ptr::class, intmax_t_ptr_ptr_ptr::class); 236 | \class_alias(long_int_ptr_ptr_ptr_ptr::class, intmax_t_ptr_ptr_ptr_ptr::class); 237 | \class_alias(unsigned_long_int_ptr::class, uintmax_t_ptr::class); 238 | \class_alias(unsigned_long_int_ptr_ptr::class, uintmax_t_ptr_ptr::class); 239 | \class_alias(unsigned_long_int_ptr_ptr_ptr::class, uintmax_t_ptr_ptr_ptr::class); 240 | \class_alias(unsigned_long_int_ptr_ptr_ptr_ptr::class, uintmax_t_ptr_ptr_ptr_ptr::class); -------------------------------------------------------------------------------- /test/cases/char_returns.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test basic parsing of character returns 3 | --FILE-- 4 | typedef struct LLVMOpaqueModule *LLVMModuleRef; 5 | const char *LLVMGetModuleIdentifier(LLVMModuleRef M, size_t *Len); 6 | --EXPECTF-- 7 | ffi = FFI::cdef(self::HEADER_DEF, $pathToSoFile); 34 | } 35 | 36 | public static function cast(itest $from, string $to): itest { 37 | if (!is_a($to, itest::class, true)) { 38 | throw new \LogicException("Cannot cast to a non-wrapper type"); 39 | } 40 | return new $to(self::$staticFFI->cast($to::getType(), $from->getData())); 41 | } 42 | 43 | public static function makeArray(string $class, int|array $elements): itest { 44 | $type = $class::getType(); 45 | if (substr($type, -1) !== "*") { 46 | throw new \LogicException("Attempting to make a non-pointer element into an array"); 47 | } 48 | if (is_int($elements)) { 49 | $cdata = self::$staticFFI->new(substr($type, 0, -1) . "[$elements]"); 50 | } else { 51 | $cdata = self::$staticFFI->new(substr($type, 0, -1) . "[" . count($elements) . "]"); 52 | foreach ($elements as $key => $raw) { 53 | $cdata[$key] = \is_scalar($raw) ? \is_int($raw) && $type === "char*" ? \chr($raw) : $raw : $raw->getData(); 54 | } 55 | } 56 | $object = new $class(self::$staticFFI->cast($type, \FFI::addr($cdata))); 57 | self::$__arrayWeakMap[$object] = $cdata; 58 | return $object; 59 | } 60 | 61 | public static function sizeof($classOrObject): int { 62 | if (is_object($classOrObject) && $classOrObject instanceof itest) { 63 | return FFI::sizeof($classOrObject->getData()); 64 | } elseif (is_a($classOrObject, itest::class, true)) { 65 | return FFI::sizeof(self::$staticFFI->type($classOrObject::getType())); 66 | } else { 67 | throw new \LogicException("Unknown class/object passed to sizeof()"); 68 | } 69 | } 70 | 71 | public function getFFI(): FFI { 72 | return $this->ffi; 73 | } 74 | 75 | 76 | public function __get(string $name) { 77 | switch($name) { 78 | default: return $this->ffi->$name; 79 | } 80 | } 81 | public function __set(string $name, $value) { 82 | switch($name) { 83 | default: return $this->ffi->$name; 84 | } 85 | } 86 | public function __allocCachedString(string $str): FFI\CData { 87 | return $this->__literalStrings[$str] ??= string_::ownedZero($str)->getData(); 88 | } 89 | public function LLVMGetModuleIdentifier(void_ptr | struct_LLVMOpaqueModule_ptr | null | array $M, void_ptr | size_t_ptr | null | array $Len): ?string_ { 90 | $__ffi_internal_refsM = []; 91 | if (\is_array($M)) { 92 | $_ = $this->ffi->new("struct LLVMOpaqueModule[" . \count($M) . "]"); 93 | $_i = 0; 94 | if ($M) { 95 | if ($_ref = \ReflectionReference::fromArrayElement($M, \key($M))) { 96 | foreach ($M as $_k => $_v) { 97 | $__ffi_internal_refsM[$_i] = &$M[$_k]; 98 | $_[$_i++] = $_v?->getData(); 99 | } 100 | $__ffi_internal_originalM = $M = $_; 101 | } else { 102 | foreach ($M as $_v) { 103 | $_[$_i++] = $_v?->getData(); 104 | } 105 | $M = $_; 106 | } 107 | } 108 | } else { 109 | $M = $M?->getData(); 110 | if ($M !== null) { 111 | $M = $this->ffi->cast("struct LLVMOpaqueModule*", $M); 112 | } 113 | } 114 | $__ffi_internal_refsLen = []; 115 | if (\is_array($Len)) { 116 | $_ = $this->ffi->new("size_t[" . \count($Len) . "]"); 117 | $_i = 0; 118 | if ($Len) { 119 | if ($_ref = \ReflectionReference::fromArrayElement($Len, \key($Len))) { 120 | foreach ($Len as $_k => $_v) { 121 | $__ffi_internal_refsLen[$_i] = &$Len[$_k]; 122 | $_[$_i++] = $_v ?? 0; 123 | } 124 | $__ffi_internal_originalLen = $Len = $_; 125 | } else { 126 | foreach ($Len as $_v) { 127 | $_[$_i++] = $_v ?? 0; 128 | } 129 | $Len = $_; 130 | } 131 | } 132 | } else { 133 | $Len = $Len?->getData(); 134 | if ($Len !== null) { 135 | $Len = $this->ffi->cast("size_t*", $Len); 136 | } 137 | } 138 | $result = $this->ffi->LLVMGetModuleIdentifier($M, $Len); 139 | foreach ($__ffi_internal_refsM as $_k => &$__ffi_internal_ref_v) { 140 | $__ffi_internal_ref_v = $__ffi_internal_originalM[$_k]; 141 | if ($__ffi_internal_ref_v !== null) { 142 | $__ffi_internal_ref_v = new struct_LLVMOpaqueModule($__ffi_internal_ref_v); 143 | } 144 | } 145 | foreach ($__ffi_internal_refsLen as $_k => &$__ffi_internal_ref_v) { 146 | $__ffi_internal_ref_v = $__ffi_internal_originalLen[$_k]; 147 | } 148 | return $result === null ? null : new string_($result); 149 | } 150 | } 151 | 152 | class string_ implements itest, itest_ptr, \ArrayAccess { 153 | private FFI\CData $data; 154 | public function __construct(FFI\CData $data) { $this->data = $data; } 155 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 156 | public function getData(): FFI\CData { return $this->data; } 157 | public function equals(string_ $other): bool { return $this->data == $other->data; } 158 | public function addr(): string_ptr { return new string_ptr(FFI::addr($this->data)); } 159 | public function deref(int $n = 0): int { return \ord($this->data[$n]); } 160 | #[\ReturnTypeWillChange] public function offsetGet($offset): int { return $this->deref($offset); } 161 | #[\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 162 | #[\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \Error("Cannot unset C structures"); } 163 | #[\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = \chr($value); } 164 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 165 | /** @return int[] */ public function toArray(?int $length = null): array { $ret = []; if ($length === null) { $i = 0; while ("\0" !== $cur = $this->data[$i++]) { $ret[] = \ord($cur); } } else { for ($i = 0; $i < $length; ++$i) { $ret[] = \ord($this->data[$i]); } } return $ret; } 166 | public function toString(?int $length = null): string { return $length === null ? FFI::string($this->data) : FFI::string($this->data, $length); } 167 | public static function persistent(string $string): self { $str = new self(FFI::cdef()->new("char[" . \strlen($string) . "]", false)); FFI::memcpy($str->data, $string, \strlen($string)); return $str; } 168 | public static function owned(string $string): self { $str = new self(FFI::cdef()->new("char[" . \strlen($string) . "]", true)); FFI::memcpy($str->data, $string, \strlen($string)); return $str; } 169 | public static function persistentZero(string $string): self { return self::persistent("$string\0"); } 170 | public static function ownedZero(string $string): self { return self::owned("$string\0"); } 171 | public function set(int | void_ptr | string_ $value): void { 172 | if (\is_scalar($value)) { 173 | $this->data[0] = \chr($value); 174 | } else { 175 | FFI::addr($this->data)[0] = $value->getData(); 176 | } 177 | } 178 | public static function getType(): string { return 'char*'; } 179 | public static function size(): int { return testFFI::sizeof(self::class); } 180 | public function getDefinition(): string { return static::getType(); } 181 | } 182 | class string_ptr implements itest, itest_ptr, \ArrayAccess { 183 | private FFI\CData $data; 184 | public function __construct(FFI\CData $data) { $this->data = $data; } 185 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 186 | public function getData(): FFI\CData { return $this->data; } 187 | public function equals(string_ptr $other): bool { return $this->data == $other->data; } 188 | public function addr(): string_ptr_ptr { return new string_ptr_ptr(FFI::addr($this->data)); } 189 | public function deref(int $n = 0): string_ { return new string_($this->data[$n]); } 190 | #[\ReturnTypeWillChange] public function offsetGet($offset): string_ { return $this->deref($offset); } 191 | #[\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 192 | #[\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \Error("Cannot unset C structures"); } 193 | #[\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = $value->getData(); } 194 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 195 | /** @return string_[] */ public function toArray(?int $length = null): array { $ret = []; if ($length === null) { $i = 0; while (null !== $cur = $this->data[$i++]) { $ret[] = new string_($cur); } } else { for ($i = 0; $i < $length; ++$i) { $ret[] = new string_($this->data[$i]); } } return $ret; } 196 | public function set(void_ptr | string_ptr $value): void { 197 | FFI::addr($this->data)[0] = $value->getData(); 198 | } 199 | public static function getType(): string { return 'char**'; } 200 | public static function size(): int { return testFFI::sizeof(self::class); } 201 | public function getDefinition(): string { return static::getType(); } 202 | } 203 | class string_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 204 | class string_ptr_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 205 | class void_ptr implements itest, itest_ptr { 206 | private FFI\CData $data; 207 | public function __construct(FFI\CData $data) { $this->data = $data; } 208 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 209 | public function getData(): FFI\CData { return $this->data; } 210 | public function equals(void_ptr $other): bool { return $this->data == $other->data; } 211 | public function addr(): void_ptr_ptr { return new void_ptr_ptr(FFI::addr($this->data)); } 212 | public function set(itest_ptr $value): void { 213 | FFI::addr($this->data)[0] = $value->getData(); 214 | } 215 | public static function getType(): string { return 'void*'; } 216 | public static function size(): int { return testFFI::sizeof(self::class); } 217 | public function getDefinition(): string { return static::getType(); } 218 | } 219 | class void_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 220 | class void_ptr_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 221 | class void_ptr_ptr_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 222 | class size_t_ptr implements itest, itest_ptr, \ArrayAccess { 223 | private FFI\CData $data; 224 | public function __construct(FFI\CData $data) { $this->data = $data; } 225 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 226 | public function getData(): FFI\CData { return $this->data; } 227 | public function equals(size_t_ptr $other): bool { return $this->data == $other->data; } 228 | public function addr(): size_t_ptr_ptr { return new size_t_ptr_ptr(FFI::addr($this->data)); } 229 | public function deref(int $n = 0): int { return $this->data[$n]; } 230 | #[\ReturnTypeWillChange] public function offsetGet($offset): int { return $this->deref($offset); } 231 | #[\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 232 | #[\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \Error("Cannot unset C structures"); } 233 | #[\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = $value; } 234 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 235 | /** @return int[] */ public function toArray(int $length): array { $ret = []; for ($i = 0; $i < $length; ++$i) { $ret[] = ($this->data[$i]); } return $ret; } 236 | public function set(int | void_ptr | size_t_ptr $value): void { 237 | if (\is_scalar($value)) { 238 | $this->data[0] = $value; 239 | } else { 240 | FFI::addr($this->data)[0] = $value->getData(); 241 | } 242 | } 243 | public static function getType(): string { return 'size_t*'; } 244 | public static function size(): int { return testFFI::sizeof(self::class); } 245 | public function getDefinition(): string { return static::getType(); } 246 | } 247 | class size_t_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 248 | class size_t_ptr_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 249 | class size_t_ptr_ptr_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 250 | (function() { self::$staticFFI = \FFI::cdef(testFFI::TYPES_DEF); self::$__arrayWeakMap = new \WeakMap; })->bindTo(null, testFFI::class)(); 251 | \class_alias(struct_LLVMOpaqueModule_ptr::class, LLVMModuleRef::class); 252 | \class_alias(struct_LLVMOpaqueModule_ptr_ptr::class, LLVMModuleRef_ptr::class); 253 | \class_alias(struct_LLVMOpaqueModule_ptr_ptr_ptr::class, LLVMModuleRef_ptr_ptr::class); 254 | \class_alias(struct_LLVMOpaqueModule_ptr_ptr_ptr_ptr::class, LLVMModuleRef_ptr_ptr_ptr::class); -------------------------------------------------------------------------------- /test/cases/enum_self_references.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test enum self references 3 | --FILE-- 4 | enum A { 5 | A1 = 1, 6 | A2 = A1, 7 | A3 = 2, 8 | }; 9 | typedef enum { 10 | B1 = 1, 11 | B2 = B1, 12 | B3 = B1 | B2, 13 | B4 14 | } B; 15 | 16 | --EXPECTF-- 17 | ffi = FFI::cdef(self::HEADER_DEF, $pathToSoFile); 60 | } 61 | 62 | public static function cast(itest $from, string $to): itest { 63 | if (!is_a($to, itest::class, true)) { 64 | throw new \LogicException("Cannot cast to a non-wrapper type"); 65 | } 66 | return new $to(self::$staticFFI->cast($to::getType(), $from->getData())); 67 | } 68 | 69 | public static function makeArray(string $class, int|array $elements): itest { 70 | $type = $class::getType(); 71 | if (substr($type, -1) !== "*") { 72 | throw new \LogicException("Attempting to make a non-pointer element into an array"); 73 | } 74 | if (is_int($elements)) { 75 | $cdata = self::$staticFFI->new(substr($type, 0, -1) . "[$elements]"); 76 | } else { 77 | $cdata = self::$staticFFI->new(substr($type, 0, -1) . "[" . count($elements) . "]"); 78 | foreach ($elements as $key => $raw) { 79 | $cdata[$key] = \is_scalar($raw) ? \is_int($raw) && $type === "char*" ? \chr($raw) : $raw : $raw->getData(); 80 | } 81 | } 82 | $object = new $class(self::$staticFFI->cast($type, \FFI::addr($cdata))); 83 | self::$__arrayWeakMap[$object] = $cdata; 84 | return $object; 85 | } 86 | 87 | public static function sizeof($classOrObject): int { 88 | if (is_object($classOrObject) && $classOrObject instanceof itest) { 89 | return FFI::sizeof($classOrObject->getData()); 90 | } elseif (is_a($classOrObject, itest::class, true)) { 91 | return FFI::sizeof(self::$staticFFI->type($classOrObject::getType())); 92 | } else { 93 | throw new \LogicException("Unknown class/object passed to sizeof()"); 94 | } 95 | } 96 | 97 | public function getFFI(): FFI { 98 | return $this->ffi; 99 | } 100 | 101 | 102 | public function __get(string $name) { 103 | switch($name) { 104 | default: return $this->ffi->$name; 105 | } 106 | } 107 | public function __set(string $name, $value) { 108 | switch($name) { 109 | default: return $this->ffi->$name; 110 | } 111 | } 112 | public function __allocCachedString(string $str): FFI\CData { 113 | return $this->__literalStrings[$str] ??= string_::ownedZero($str)->getData(); 114 | } 115 | } 116 | 117 | class string_ implements itest, itest_ptr, \ArrayAccess { 118 | private FFI\CData $data; 119 | public function __construct(FFI\CData $data) { $this->data = $data; } 120 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 121 | public function getData(): FFI\CData { return $this->data; } 122 | public function equals(string_ $other): bool { return $this->data == $other->data; } 123 | public function addr(): string_ptr { return new string_ptr(FFI::addr($this->data)); } 124 | public function deref(int $n = 0): int { return \ord($this->data[$n]); } 125 | #[\ReturnTypeWillChange] public function offsetGet($offset): int { return $this->deref($offset); } 126 | #[\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 127 | #[\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \Error("Cannot unset C structures"); } 128 | #[\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = \chr($value); } 129 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 130 | /** @return int[] */ public function toArray(?int $length = null): array { $ret = []; if ($length === null) { $i = 0; while ("\0" !== $cur = $this->data[$i++]) { $ret[] = \ord($cur); } } else { for ($i = 0; $i < $length; ++$i) { $ret[] = \ord($this->data[$i]); } } return $ret; } 131 | public function toString(?int $length = null): string { return $length === null ? FFI::string($this->data) : FFI::string($this->data, $length); } 132 | public static function persistent(string $string): self { $str = new self(FFI::cdef()->new("char[" . \strlen($string) . "]", false)); FFI::memcpy($str->data, $string, \strlen($string)); return $str; } 133 | public static function owned(string $string): self { $str = new self(FFI::cdef()->new("char[" . \strlen($string) . "]", true)); FFI::memcpy($str->data, $string, \strlen($string)); return $str; } 134 | public static function persistentZero(string $string): self { return self::persistent("$string\0"); } 135 | public static function ownedZero(string $string): self { return self::owned("$string\0"); } 136 | public function set(int | void_ptr | string_ $value): void { 137 | if (\is_scalar($value)) { 138 | $this->data[0] = \chr($value); 139 | } else { 140 | FFI::addr($this->data)[0] = $value->getData(); 141 | } 142 | } 143 | public static function getType(): string { return 'char*'; } 144 | public static function size(): int { return testFFI::sizeof(self::class); } 145 | public function getDefinition(): string { return static::getType(); } 146 | } 147 | class string_ptr implements itest, itest_ptr, \ArrayAccess { 148 | private FFI\CData $data; 149 | public function __construct(FFI\CData $data) { $this->data = $data; } 150 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 151 | public function getData(): FFI\CData { return $this->data; } 152 | public function equals(string_ptr $other): bool { return $this->data == $other->data; } 153 | public function addr(): string_ptr_ptr { return new string_ptr_ptr(FFI::addr($this->data)); } 154 | public function deref(int $n = 0): string_ { return new string_($this->data[$n]); } 155 | #[\ReturnTypeWillChange] public function offsetGet($offset): string_ { return $this->deref($offset); } 156 | #[\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 157 | #[\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \Error("Cannot unset C structures"); } 158 | #[\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = $value->getData(); } 159 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 160 | /** @return string_[] */ public function toArray(?int $length = null): array { $ret = []; if ($length === null) { $i = 0; while (null !== $cur = $this->data[$i++]) { $ret[] = new string_($cur); } } else { for ($i = 0; $i < $length; ++$i) { $ret[] = new string_($this->data[$i]); } } return $ret; } 161 | public function set(void_ptr | string_ptr $value): void { 162 | FFI::addr($this->data)[0] = $value->getData(); 163 | } 164 | public static function getType(): string { return 'char**'; } 165 | public static function size(): int { return testFFI::sizeof(self::class); } 166 | public function getDefinition(): string { return static::getType(); } 167 | } 168 | class string_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 169 | class string_ptr_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 170 | class void_ptr implements itest, itest_ptr { 171 | private FFI\CData $data; 172 | public function __construct(FFI\CData $data) { $this->data = $data; } 173 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 174 | public function getData(): FFI\CData { return $this->data; } 175 | public function equals(void_ptr $other): bool { return $this->data == $other->data; } 176 | public function addr(): void_ptr_ptr { return new void_ptr_ptr(FFI::addr($this->data)); } 177 | public function set(itest_ptr $value): void { 178 | FFI::addr($this->data)[0] = $value->getData(); 179 | } 180 | public static function getType(): string { return 'void*'; } 181 | public static function size(): int { return testFFI::sizeof(self::class); } 182 | public function getDefinition(): string { return static::getType(); } 183 | } 184 | class void_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 185 | class void_ptr_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 186 | class void_ptr_ptr_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 187 | class int_ptr implements itest, itest_ptr, \ArrayAccess { 188 | private FFI\CData $data; 189 | public function __construct(FFI\CData $data) { $this->data = $data; } 190 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 191 | public function getData(): FFI\CData { return $this->data; } 192 | public function equals(int_ptr $other): bool { return $this->data == $other->data; } 193 | public function addr(): int_ptr_ptr { return new int_ptr_ptr(FFI::addr($this->data)); } 194 | public function deref(int $n = 0): int { return $this->data[$n]; } 195 | #[\ReturnTypeWillChange] public function offsetGet($offset): int { return $this->deref($offset); } 196 | #[\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 197 | #[\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \Error("Cannot unset C structures"); } 198 | #[\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = $value; } 199 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 200 | /** @return int[] */ public function toArray(int $length): array { $ret = []; for ($i = 0; $i < $length; ++$i) { $ret[] = ($this->data[$i]); } return $ret; } 201 | public function set(int | void_ptr | int_ptr $value): void { 202 | if (\is_scalar($value)) { 203 | $this->data[0] = $value; 204 | } else { 205 | FFI::addr($this->data)[0] = $value->getData(); 206 | } 207 | } 208 | public static function getType(): string { return 'int*'; } 209 | public static function size(): int { return testFFI::sizeof(self::class); } 210 | public function getDefinition(): string { return static::getType(); } 211 | } 212 | class int_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 213 | class int_ptr_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 214 | class int_ptr_ptr_ptr_ptr implements itest, itest_ptr, \ArrayAccess {%a} 215 | (function() { self::$staticFFI = \FFI::cdef(testFFI::TYPES_DEF); self::$__arrayWeakMap = new \WeakMap; })->bindTo(null, testFFI::class)(); -------------------------------------------------------------------------------- /test/generated/basic_enumsTest.h: -------------------------------------------------------------------------------- 1 | enum A { 2 | A1, 3 | A2, 4 | }; 5 | typedef enum { 6 | B1, 7 | B2, 8 | } B; 9 | extern void something(B b); 10 | 11 | -------------------------------------------------------------------------------- /test/generated/basic_enumsTest.php: -------------------------------------------------------------------------------- 1 | ffi = FFI::cdef(self::HEADER_DEF, $pathToSoFile); 49 | } 50 | 51 | public static function cast(itest $from, string $to): itest { 52 | if (!is_a($to, itest::class, true)) { 53 | throw new \\LogicException("Cannot cast to a non-wrapper type"); 54 | } 55 | return new $to(self::$staticFFI->cast($to::getType(), $from->getData())); 56 | } 57 | 58 | public static function makeArray(string $class, int|array $elements): itest { 59 | $type = $class::getType(); 60 | if (substr($type, -1) !== "*") { 61 | throw new \\LogicException("Attempting to make a non-pointer element into an array"); 62 | } 63 | if (is_int($elements)) { 64 | $cdata = self::$staticFFI->new(substr($type, 0, -1) . "[$elements]"); 65 | } else { 66 | $cdata = self::$staticFFI->new(substr($type, 0, -1) . "[" . count($elements) . "]"); 67 | foreach ($elements as $key => $raw) { 68 | $cdata[$key] = \\is_scalar($raw) ? \\is_int($raw) && $type === "char*" ? \\chr($raw) : $raw : $raw->getData(); 69 | } 70 | } 71 | $object = new $class(self::$staticFFI->cast($type, \\FFI::addr($cdata))); 72 | self::$__arrayWeakMap[$object] = $cdata; 73 | return $object; 74 | } 75 | 76 | public static function sizeof($classOrObject): int { 77 | if (is_object($classOrObject) && $classOrObject instanceof itest) { 78 | return FFI::sizeof($classOrObject->getData()); 79 | } elseif (is_a($classOrObject, itest::class, true)) { 80 | return FFI::sizeof(self::$staticFFI->type($classOrObject::getType())); 81 | } else { 82 | throw new \\LogicException("Unknown class/object passed to sizeof()"); 83 | } 84 | } 85 | 86 | public function getFFI(): FFI { 87 | return $this->ffi; 88 | } 89 | 90 | 91 | public function __get(string $name) { 92 | switch($name) { 93 | default: return $this->ffi->$name; 94 | } 95 | } 96 | public function __set(string $name, $value) { 97 | switch($name) { 98 | default: return $this->ffi->$name; 99 | } 100 | } 101 | public function __allocCachedString(string $str): FFI\\CData { 102 | return $this->__literalStrings[$str] ??= string_::ownedZero($str)->getData(); 103 | } 104 | public function something(int $b): void { 105 | $this->ffi->something($b); 106 | } 107 | } 108 | 109 | class string_ implements itest, itest_ptr, \\ArrayAccess { 110 | private FFI\\CData $data; 111 | public function __construct(FFI\\CData $data) { $this->data = $data; } 112 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 113 | public function getData(): FFI\\CData { return $this->data; } 114 | public function equals(string_ $other): bool { return $this->data == $other->data; } 115 | public function addr(): string_ptr { return new string_ptr(FFI::addr($this->data)); } 116 | public function deref(int $n = 0): int { return \\ord($this->data[$n]); } 117 | #[\\ReturnTypeWillChange] public function offsetGet($offset): int { return $this->deref($offset); } 118 | #[\\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 119 | #[\\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \\Error("Cannot unset C structures"); } 120 | #[\\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = \\chr($value); } 121 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 122 | /** @return int[] */ public function toArray(?int $length = null): array { $ret = []; if ($length === null) { $i = 0; while ("\\0" !== $cur = $this->data[$i++]) { $ret[] = \\ord($cur); } } else { for ($i = 0; $i < $length; ++$i) { $ret[] = \\ord($this->data[$i]); } } return $ret; } 123 | public function toString(?int $length = null): string { return $length === null ? FFI::string($this->data) : FFI::string($this->data, $length); } 124 | public static function persistent(string $string): self { $str = new self(FFI::cdef()->new("char[" . \\strlen($string) . "]", false)); FFI::memcpy($str->data, $string, \\strlen($string)); return $str; } 125 | public static function owned(string $string): self { $str = new self(FFI::cdef()->new("char[" . \\strlen($string) . "]", true)); FFI::memcpy($str->data, $string, \\strlen($string)); return $str; } 126 | public static function persistentZero(string $string): self { return self::persistent("$string\\0"); } 127 | public static function ownedZero(string $string): self { return self::owned("$string\\0"); } 128 | public function set(int | void_ptr | string_ $value): void { 129 | if (\\is_scalar($value)) { 130 | $this->data[0] = \\chr($value); 131 | } else { 132 | FFI::addr($this->data)[0] = $value->getData(); 133 | } 134 | } 135 | public static function getType(): string { return \'char*\'; } 136 | public static function size(): int { return testFFI::sizeof(self::class); } 137 | public function getDefinition(): string { return static::getType(); } 138 | } 139 | class string_ptr implements itest, itest_ptr, \\ArrayAccess { 140 | private FFI\\CData $data; 141 | public function __construct(FFI\\CData $data) { $this->data = $data; } 142 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 143 | public function getData(): FFI\\CData { return $this->data; } 144 | public function equals(string_ptr $other): bool { return $this->data == $other->data; } 145 | public function addr(): string_ptr_ptr { return new string_ptr_ptr(FFI::addr($this->data)); } 146 | public function deref(int $n = 0): string_ { return new string_($this->data[$n]); } 147 | #[\\ReturnTypeWillChange] public function offsetGet($offset): string_ { return $this->deref($offset); } 148 | #[\\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 149 | #[\\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \\Error("Cannot unset C structures"); } 150 | #[\\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = $value->getData(); } 151 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 152 | /** @return string_[] */ public function toArray(?int $length = null): array { $ret = []; if ($length === null) { $i = 0; while (null !== $cur = $this->data[$i++]) { $ret[] = new string_($cur); } } else { for ($i = 0; $i < $length; ++$i) { $ret[] = new string_($this->data[$i]); } } return $ret; } 153 | public function set(void_ptr | string_ptr $value): void { 154 | FFI::addr($this->data)[0] = $value->getData(); 155 | } 156 | public static function getType(): string { return \'char**\'; } 157 | public static function size(): int { return testFFI::sizeof(self::class); } 158 | public function getDefinition(): string { return static::getType(); } 159 | } 160 | class string_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 161 | class string_ptr_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 162 | class void_ptr implements itest, itest_ptr { 163 | private FFI\\CData $data; 164 | public function __construct(FFI\\CData $data) { $this->data = $data; } 165 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 166 | public function getData(): FFI\\CData { return $this->data; } 167 | public function equals(void_ptr $other): bool { return $this->data == $other->data; } 168 | public function addr(): void_ptr_ptr { return new void_ptr_ptr(FFI::addr($this->data)); } 169 | public function set(itest_ptr $value): void { 170 | FFI::addr($this->data)[0] = $value->getData(); 171 | } 172 | public static function getType(): string { return \'void*\'; } 173 | public static function size(): int { return testFFI::sizeof(self::class); } 174 | public function getDefinition(): string { return static::getType(); } 175 | } 176 | class void_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 177 | class void_ptr_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 178 | class void_ptr_ptr_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 179 | class int_ptr implements itest, itest_ptr, \\ArrayAccess { 180 | private FFI\\CData $data; 181 | public function __construct(FFI\\CData $data) { $this->data = $data; } 182 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 183 | public function getData(): FFI\\CData { return $this->data; } 184 | public function equals(int_ptr $other): bool { return $this->data == $other->data; } 185 | public function addr(): int_ptr_ptr { return new int_ptr_ptr(FFI::addr($this->data)); } 186 | public function deref(int $n = 0): int { return $this->data[$n]; } 187 | #[\\ReturnTypeWillChange] public function offsetGet($offset): int { return $this->deref($offset); } 188 | #[\\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 189 | #[\\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \\Error("Cannot unset C structures"); } 190 | #[\\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = $value; } 191 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 192 | /** @return int[] */ public function toArray(int $length): array { $ret = []; for ($i = 0; $i < $length; ++$i) { $ret[] = ($this->data[$i]); } return $ret; } 193 | public function set(int | void_ptr | int_ptr $value): void { 194 | if (\\is_scalar($value)) { 195 | $this->data[0] = $value; 196 | } else { 197 | FFI::addr($this->data)[0] = $value->getData(); 198 | } 199 | } 200 | public static function getType(): string { return \'int*\'; } 201 | public static function size(): int { return testFFI::sizeof(self::class); } 202 | public function getDefinition(): string { return static::getType(); } 203 | } 204 | class int_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 205 | class int_ptr_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 206 | class int_ptr_ptr_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 207 | (function() { self::$staticFFI = \\FFI::cdef(testFFI::TYPES_DEF); self::$__arrayWeakMap = new \\WeakMap; })->bindTo(null, testFFI::class)();'; 208 | 209 | protected FFIMe $lib; 210 | protected Printer $printer; 211 | 212 | public function setUp(): void { 213 | $this->lib = new class( 214 | PHP_OS_FAMILY === "Darwin" ? "/usr/lib/libSystem.B.dylib" : "/lib/x86_64-linux-gnu/libc.so.6", 215 | [ 216 | __DIR__, 217 | __DIR__ . '/../include' 218 | ] 219 | ) extends FFIMe { 220 | // Bypass filtering for tests 221 | protected function filterSymbolDeclarations(): void {} 222 | }; 223 | } 224 | 225 | public function tearDown(): void { 226 | @unlink(__DIR__ . '/basic_enumsTest.result.php'); 227 | } 228 | 229 | /** 230 | * @textdox Test basic parsing of enums 231 | */ 232 | public function testCodeGen() { 233 | $this->lib->include(__DIR__ . '/basic_enumsTest.h'); 234 | $this->lib->codeGen("test\\test", __DIR__ . '/basic_enumsTest.result.php'); 235 | $this->assertFileExists(__DIR__ . '/basic_enumsTest.result.php'); 236 | $this->assertStringMatchesFormat(self::EXPECTED, trim(file_get_contents(__DIR__ . '/basic_enumsTest.result.php'))); 237 | } 238 | } -------------------------------------------------------------------------------- /test/generated/basic_stringsTest.h: -------------------------------------------------------------------------------- 1 | 2 | void setFoo(char* value); 3 | 4 | char* getFoo(); 5 | 6 | -------------------------------------------------------------------------------- /test/generated/basic_stringsTest.php: -------------------------------------------------------------------------------- 1 | ffi = FFI::cdef(self::HEADER_DEF, $pathToSoFile); 38 | } 39 | 40 | public static function cast(itest $from, string $to): itest { 41 | if (!is_a($to, itest::class, true)) { 42 | throw new \\LogicException("Cannot cast to a non-wrapper type"); 43 | } 44 | return new $to(self::$staticFFI->cast($to::getType(), $from->getData())); 45 | } 46 | 47 | public static function makeArray(string $class, int|array $elements): itest { 48 | $type = $class::getType(); 49 | if (substr($type, -1) !== "*") { 50 | throw new \\LogicException("Attempting to make a non-pointer element into an array"); 51 | } 52 | if (is_int($elements)) { 53 | $cdata = self::$staticFFI->new(substr($type, 0, -1) . "[$elements]"); 54 | } else { 55 | $cdata = self::$staticFFI->new(substr($type, 0, -1) . "[" . count($elements) . "]"); 56 | foreach ($elements as $key => $raw) { 57 | $cdata[$key] = \\is_scalar($raw) ? \\is_int($raw) && $type === "char*" ? \\chr($raw) : $raw : $raw->getData(); 58 | } 59 | } 60 | $object = new $class(self::$staticFFI->cast($type, \\FFI::addr($cdata))); 61 | self::$__arrayWeakMap[$object] = $cdata; 62 | return $object; 63 | } 64 | 65 | public static function sizeof($classOrObject): int { 66 | if (is_object($classOrObject) && $classOrObject instanceof itest) { 67 | return FFI::sizeof($classOrObject->getData()); 68 | } elseif (is_a($classOrObject, itest::class, true)) { 69 | return FFI::sizeof(self::$staticFFI->type($classOrObject::getType())); 70 | } else { 71 | throw new \\LogicException("Unknown class/object passed to sizeof()"); 72 | } 73 | } 74 | 75 | public function getFFI(): FFI { 76 | return $this->ffi; 77 | } 78 | 79 | 80 | public function __get(string $name) { 81 | switch($name) { 82 | default: return $this->ffi->$name; 83 | } 84 | } 85 | public function __set(string $name, $value) { 86 | switch($name) { 87 | default: return $this->ffi->$name; 88 | } 89 | } 90 | public function __allocCachedString(string $str): FFI\\CData { 91 | return $this->__literalStrings[$str] ??= string_::ownedZero($str)->getData(); 92 | } 93 | public function setFoo(void_ptr | string_ | null | string | array $value): void { 94 | $__ffi_internal_refsvalue = []; 95 | if (\\is_string($value)) { 96 | $value = string_::ownedZero($value)->getData(); 97 | } elseif (\\is_array($value)) { 98 | $_ = $this->ffi->new("char[" . \\count($value) . "]"); 99 | $_i = 0; 100 | if ($value) { 101 | if ($_ref = \\ReflectionReference::fromArrayElement($value, \\key($value))) { 102 | foreach ($value as $_k => $_v) { 103 | $__ffi_internal_refsvalue[$_i] = &$value[$_k]; 104 | $_[$_i++] = $_v ?? 0; 105 | } 106 | $__ffi_internal_originalvalue = $value = $_; 107 | } else { 108 | foreach ($value as $_v) { 109 | $_[$_i++] = $_v ?? 0; 110 | } 111 | $value = $_; 112 | } 113 | } 114 | } else { 115 | $value = $value?->getData(); 116 | if ($value !== null) { 117 | $value = $this->ffi->cast("char*", $value); 118 | } 119 | } 120 | $this->ffi->setFoo($value); 121 | foreach ($__ffi_internal_refsvalue as $_k => &$__ffi_internal_ref_v) { 122 | $__ffi_internal_ref_v = $__ffi_internal_originalvalue[$_k]; 123 | $__ffi_internal_ref_v = \\ord($__ffi_internal_ref_v); 124 | } 125 | } 126 | public function getFoo(): ?string_ { 127 | $result = $this->ffi->getFoo(); 128 | return $result === null ? null : new string_($result); 129 | } 130 | } 131 | 132 | class string_ implements itest, itest_ptr, \\ArrayAccess { 133 | private FFI\\CData $data; 134 | public function __construct(FFI\\CData $data) { $this->data = $data; } 135 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 136 | public function getData(): FFI\\CData { return $this->data; } 137 | public function equals(string_ $other): bool { return $this->data == $other->data; } 138 | public function addr(): string_ptr { return new string_ptr(FFI::addr($this->data)); } 139 | public function deref(int $n = 0): int { return \\ord($this->data[$n]); } 140 | #[\\ReturnTypeWillChange] public function offsetGet($offset): int { return $this->deref($offset); } 141 | #[\\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 142 | #[\\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \\Error("Cannot unset C structures"); } 143 | #[\\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = \\chr($value); } 144 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 145 | /** @return int[] */ public function toArray(?int $length = null): array { $ret = []; if ($length === null) { $i = 0; while ("\\0" !== $cur = $this->data[$i++]) { $ret[] = \\ord($cur); } } else { for ($i = 0; $i < $length; ++$i) { $ret[] = \\ord($this->data[$i]); } } return $ret; } 146 | public function toString(?int $length = null): string { return $length === null ? FFI::string($this->data) : FFI::string($this->data, $length); } 147 | public static function persistent(string $string): self { $str = new self(FFI::cdef()->new("char[" . \\strlen($string) . "]", false)); FFI::memcpy($str->data, $string, \\strlen($string)); return $str; } 148 | public static function owned(string $string): self { $str = new self(FFI::cdef()->new("char[" . \\strlen($string) . "]", true)); FFI::memcpy($str->data, $string, \\strlen($string)); return $str; } 149 | public static function persistentZero(string $string): self { return self::persistent("$string\\0"); } 150 | public static function ownedZero(string $string): self { return self::owned("$string\\0"); } 151 | public function set(int | void_ptr | string_ $value): void { 152 | if (\\is_scalar($value)) { 153 | $this->data[0] = \\chr($value); 154 | } else { 155 | FFI::addr($this->data)[0] = $value->getData(); 156 | } 157 | } 158 | public static function getType(): string { return \'char*\'; } 159 | public static function size(): int { return testFFI::sizeof(self::class); } 160 | public function getDefinition(): string { return static::getType(); } 161 | } 162 | class string_ptr implements itest, itest_ptr, \\ArrayAccess { 163 | private FFI\\CData $data; 164 | public function __construct(FFI\\CData $data) { $this->data = $data; } 165 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 166 | public function getData(): FFI\\CData { return $this->data; } 167 | public function equals(string_ptr $other): bool { return $this->data == $other->data; } 168 | public function addr(): string_ptr_ptr { return new string_ptr_ptr(FFI::addr($this->data)); } 169 | public function deref(int $n = 0): string_ { return new string_($this->data[$n]); } 170 | #[\\ReturnTypeWillChange] public function offsetGet($offset): string_ { return $this->deref($offset); } 171 | #[\\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 172 | #[\\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \\Error("Cannot unset C structures"); } 173 | #[\\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = $value->getData(); } 174 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 175 | /** @return string_[] */ public function toArray(?int $length = null): array { $ret = []; if ($length === null) { $i = 0; while (null !== $cur = $this->data[$i++]) { $ret[] = new string_($cur); } } else { for ($i = 0; $i < $length; ++$i) { $ret[] = new string_($this->data[$i]); } } return $ret; } 176 | public function set(void_ptr | string_ptr $value): void { 177 | FFI::addr($this->data)[0] = $value->getData(); 178 | } 179 | public static function getType(): string { return \'char**\'; } 180 | public static function size(): int { return testFFI::sizeof(self::class); } 181 | public function getDefinition(): string { return static::getType(); } 182 | } 183 | class string_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 184 | class string_ptr_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 185 | class void_ptr implements itest, itest_ptr { 186 | private FFI\\CData $data; 187 | public function __construct(FFI\\CData $data) { $this->data = $data; } 188 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 189 | public function getData(): FFI\\CData { return $this->data; } 190 | public function equals(void_ptr $other): bool { return $this->data == $other->data; } 191 | public function addr(): void_ptr_ptr { return new void_ptr_ptr(FFI::addr($this->data)); } 192 | public function set(itest_ptr $value): void { 193 | FFI::addr($this->data)[0] = $value->getData(); 194 | } 195 | public static function getType(): string { return \'void*\'; } 196 | public static function size(): int { return testFFI::sizeof(self::class); } 197 | public function getDefinition(): string { return static::getType(); } 198 | } 199 | class void_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 200 | class void_ptr_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 201 | class void_ptr_ptr_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 202 | (function() { self::$staticFFI = \\FFI::cdef(testFFI::TYPES_DEF); self::$__arrayWeakMap = new \\WeakMap; })->bindTo(null, testFFI::class)();'; 203 | 204 | protected FFIMe $lib; 205 | protected Printer $printer; 206 | 207 | public function setUp(): void { 208 | $this->lib = new class( 209 | PHP_OS_FAMILY === "Darwin" ? "/usr/lib/libSystem.B.dylib" : "/lib/x86_64-linux-gnu/libc.so.6", 210 | [ 211 | __DIR__, 212 | __DIR__ . '/../include' 213 | ] 214 | ) extends FFIMe { 215 | // Bypass filtering for tests 216 | protected function filterSymbolDeclarations(): void {} 217 | }; 218 | } 219 | 220 | public function tearDown(): void { 221 | @unlink(__DIR__ . '/basic_stringsTest.result.php'); 222 | } 223 | 224 | /** 225 | * @textdox Test basic parsing of strings 226 | */ 227 | public function testCodeGen() { 228 | $this->lib->include(__DIR__ . '/basic_stringsTest.h'); 229 | $this->lib->codeGen("test\\test", __DIR__ . '/basic_stringsTest.result.php'); 230 | $this->assertFileExists(__DIR__ . '/basic_stringsTest.result.php'); 231 | $this->assertStringMatchesFormat(self::EXPECTED, trim(file_get_contents(__DIR__ . '/basic_stringsTest.result.php'))); 232 | } 233 | } -------------------------------------------------------------------------------- /test/generated/basic_typedefsTest.h: -------------------------------------------------------------------------------- 1 | typedef long int intmax_t; 2 | typedef unsigned long int uintmax_t; 3 | 4 | intmax_t foo(intmax_t a); 5 | 6 | extern uintmax_t blah(); 7 | 8 | -------------------------------------------------------------------------------- /test/generated/basic_typedefsTest.php: -------------------------------------------------------------------------------- 1 | ffi = FFI::cdef(self::HEADER_DEF, $pathToSoFile); 40 | } 41 | 42 | public static function cast(itest $from, string $to): itest { 43 | if (!is_a($to, itest::class, true)) { 44 | throw new \\LogicException("Cannot cast to a non-wrapper type"); 45 | } 46 | return new $to(self::$staticFFI->cast($to::getType(), $from->getData())); 47 | } 48 | 49 | public static function makeArray(string $class, int|array $elements): itest { 50 | $type = $class::getType(); 51 | if (substr($type, -1) !== "*") { 52 | throw new \\LogicException("Attempting to make a non-pointer element into an array"); 53 | } 54 | if (is_int($elements)) { 55 | $cdata = self::$staticFFI->new(substr($type, 0, -1) . "[$elements]"); 56 | } else { 57 | $cdata = self::$staticFFI->new(substr($type, 0, -1) . "[" . count($elements) . "]"); 58 | foreach ($elements as $key => $raw) { 59 | $cdata[$key] = \\is_scalar($raw) ? \\is_int($raw) && $type === "char*" ? \\chr($raw) : $raw : $raw->getData(); 60 | } 61 | } 62 | $object = new $class(self::$staticFFI->cast($type, \\FFI::addr($cdata))); 63 | self::$__arrayWeakMap[$object] = $cdata; 64 | return $object; 65 | } 66 | 67 | public static function sizeof($classOrObject): int { 68 | if (is_object($classOrObject) && $classOrObject instanceof itest) { 69 | return FFI::sizeof($classOrObject->getData()); 70 | } elseif (is_a($classOrObject, itest::class, true)) { 71 | return FFI::sizeof(self::$staticFFI->type($classOrObject::getType())); 72 | } else { 73 | throw new \\LogicException("Unknown class/object passed to sizeof()"); 74 | } 75 | } 76 | 77 | public function getFFI(): FFI { 78 | return $this->ffi; 79 | } 80 | 81 | 82 | public function __get(string $name) { 83 | switch($name) { 84 | default: return $this->ffi->$name; 85 | } 86 | } 87 | public function __set(string $name, $value) { 88 | switch($name) { 89 | default: return $this->ffi->$name; 90 | } 91 | } 92 | public function __allocCachedString(string $str): FFI\\CData { 93 | return $this->__literalStrings[$str] ??= string_::ownedZero($str)->getData(); 94 | } 95 | public function foo(int $a): int { 96 | $result = $this->ffi->foo($a); 97 | return $result; 98 | } 99 | public function blah(): int { 100 | $result = $this->ffi->blah(); 101 | return $result; 102 | } 103 | } 104 | 105 | class string_ implements itest, itest_ptr, \\ArrayAccess { 106 | private FFI\\CData $data; 107 | public function __construct(FFI\\CData $data) { $this->data = $data; } 108 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 109 | public function getData(): FFI\\CData { return $this->data; } 110 | public function equals(string_ $other): bool { return $this->data == $other->data; } 111 | public function addr(): string_ptr { return new string_ptr(FFI::addr($this->data)); } 112 | public function deref(int $n = 0): int { return \\ord($this->data[$n]); } 113 | #[\\ReturnTypeWillChange] public function offsetGet($offset): int { return $this->deref($offset); } 114 | #[\\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 115 | #[\\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \\Error("Cannot unset C structures"); } 116 | #[\\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = \\chr($value); } 117 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 118 | /** @return int[] */ public function toArray(?int $length = null): array { $ret = []; if ($length === null) { $i = 0; while ("\\0" !== $cur = $this->data[$i++]) { $ret[] = \\ord($cur); } } else { for ($i = 0; $i < $length; ++$i) { $ret[] = \\ord($this->data[$i]); } } return $ret; } 119 | public function toString(?int $length = null): string { return $length === null ? FFI::string($this->data) : FFI::string($this->data, $length); } 120 | public static function persistent(string $string): self { $str = new self(FFI::cdef()->new("char[" . \\strlen($string) . "]", false)); FFI::memcpy($str->data, $string, \\strlen($string)); return $str; } 121 | public static function owned(string $string): self { $str = new self(FFI::cdef()->new("char[" . \\strlen($string) . "]", true)); FFI::memcpy($str->data, $string, \\strlen($string)); return $str; } 122 | public static function persistentZero(string $string): self { return self::persistent("$string\\0"); } 123 | public static function ownedZero(string $string): self { return self::owned("$string\\0"); } 124 | public function set(int | void_ptr | string_ $value): void { 125 | if (\\is_scalar($value)) { 126 | $this->data[0] = \\chr($value); 127 | } else { 128 | FFI::addr($this->data)[0] = $value->getData(); 129 | } 130 | } 131 | public static function getType(): string { return \'char*\'; } 132 | public static function size(): int { return testFFI::sizeof(self::class); } 133 | public function getDefinition(): string { return static::getType(); } 134 | } 135 | class string_ptr implements itest, itest_ptr, \\ArrayAccess { 136 | private FFI\\CData $data; 137 | public function __construct(FFI\\CData $data) { $this->data = $data; } 138 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 139 | public function getData(): FFI\\CData { return $this->data; } 140 | public function equals(string_ptr $other): bool { return $this->data == $other->data; } 141 | public function addr(): string_ptr_ptr { return new string_ptr_ptr(FFI::addr($this->data)); } 142 | public function deref(int $n = 0): string_ { return new string_($this->data[$n]); } 143 | #[\\ReturnTypeWillChange] public function offsetGet($offset): string_ { return $this->deref($offset); } 144 | #[\\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 145 | #[\\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \\Error("Cannot unset C structures"); } 146 | #[\\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = $value->getData(); } 147 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 148 | /** @return string_[] */ public function toArray(?int $length = null): array { $ret = []; if ($length === null) { $i = 0; while (null !== $cur = $this->data[$i++]) { $ret[] = new string_($cur); } } else { for ($i = 0; $i < $length; ++$i) { $ret[] = new string_($this->data[$i]); } } return $ret; } 149 | public function set(void_ptr | string_ptr $value): void { 150 | FFI::addr($this->data)[0] = $value->getData(); 151 | } 152 | public static function getType(): string { return \'char**\'; } 153 | public static function size(): int { return testFFI::sizeof(self::class); } 154 | public function getDefinition(): string { return static::getType(); } 155 | } 156 | class string_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 157 | class string_ptr_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 158 | class void_ptr implements itest, itest_ptr { 159 | private FFI\\CData $data; 160 | public function __construct(FFI\\CData $data) { $this->data = $data; } 161 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 162 | public function getData(): FFI\\CData { return $this->data; } 163 | public function equals(void_ptr $other): bool { return $this->data == $other->data; } 164 | public function addr(): void_ptr_ptr { return new void_ptr_ptr(FFI::addr($this->data)); } 165 | public function set(itest_ptr $value): void { 166 | FFI::addr($this->data)[0] = $value->getData(); 167 | } 168 | public static function getType(): string { return \'void*\'; } 169 | public static function size(): int { return testFFI::sizeof(self::class); } 170 | public function getDefinition(): string { return static::getType(); } 171 | } 172 | class void_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 173 | class void_ptr_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 174 | class void_ptr_ptr_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 175 | class long_int_ptr implements itest, itest_ptr, \\ArrayAccess { 176 | private FFI\\CData $data; 177 | public function __construct(FFI\\CData $data) { $this->data = $data; } 178 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 179 | public function getData(): FFI\\CData { return $this->data; } 180 | public function equals(long_int_ptr $other): bool { return $this->data == $other->data; } 181 | public function addr(): long_int_ptr_ptr { return new long_int_ptr_ptr(FFI::addr($this->data)); } 182 | public function deref(int $n = 0): int { return $this->data[$n]; } 183 | #[\\ReturnTypeWillChange] public function offsetGet($offset): int { return $this->deref($offset); } 184 | #[\\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 185 | #[\\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \\Error("Cannot unset C structures"); } 186 | #[\\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = $value; } 187 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 188 | /** @return int[] */ public function toArray(int $length): array { $ret = []; for ($i = 0; $i < $length; ++$i) { $ret[] = ($this->data[$i]); } return $ret; } 189 | public function set(int | void_ptr | long_int_ptr $value): void { 190 | if (\\is_scalar($value)) { 191 | $this->data[0] = $value; 192 | } else { 193 | FFI::addr($this->data)[0] = $value->getData(); 194 | } 195 | } 196 | public static function getType(): string { return \'long_int*\'; } 197 | public static function size(): int { return testFFI::sizeof(self::class); } 198 | public function getDefinition(): string { return static::getType(); } 199 | } 200 | class long_int_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 201 | class long_int_ptr_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 202 | class long_int_ptr_ptr_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 203 | class unsigned_long_int_ptr implements itest, itest_ptr, \\ArrayAccess { 204 | private FFI\\CData $data; 205 | public function __construct(FFI\\CData $data) { $this->data = $data; } 206 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 207 | public function getData(): FFI\\CData { return $this->data; } 208 | public function equals(unsigned_long_int_ptr $other): bool { return $this->data == $other->data; } 209 | public function addr(): unsigned_long_int_ptr_ptr { return new unsigned_long_int_ptr_ptr(FFI::addr($this->data)); } 210 | public function deref(int $n = 0): int { return $this->data[$n]; } 211 | #[\\ReturnTypeWillChange] public function offsetGet($offset): int { return $this->deref($offset); } 212 | #[\\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 213 | #[\\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \\Error("Cannot unset C structures"); } 214 | #[\\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = $value; } 215 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 216 | /** @return int[] */ public function toArray(int $length): array { $ret = []; for ($i = 0; $i < $length; ++$i) { $ret[] = ($this->data[$i]); } return $ret; } 217 | public function set(int | void_ptr | unsigned_long_int_ptr $value): void { 218 | if (\\is_scalar($value)) { 219 | $this->data[0] = $value; 220 | } else { 221 | FFI::addr($this->data)[0] = $value->getData(); 222 | } 223 | } 224 | public static function getType(): string { return \'unsigned_long_int*\'; } 225 | public static function size(): int { return testFFI::sizeof(self::class); } 226 | public function getDefinition(): string { return static::getType(); } 227 | } 228 | class unsigned_long_int_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 229 | class unsigned_long_int_ptr_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 230 | class unsigned_long_int_ptr_ptr_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 231 | (function() { self::$staticFFI = \\FFI::cdef(testFFI::TYPES_DEF); self::$__arrayWeakMap = new \\WeakMap; })->bindTo(null, testFFI::class)(); 232 | \\class_alias(long_int_ptr::class, intmax_t_ptr::class); 233 | \\class_alias(long_int_ptr_ptr::class, intmax_t_ptr_ptr::class); 234 | \\class_alias(long_int_ptr_ptr_ptr::class, intmax_t_ptr_ptr_ptr::class); 235 | \\class_alias(long_int_ptr_ptr_ptr_ptr::class, intmax_t_ptr_ptr_ptr_ptr::class); 236 | \\class_alias(unsigned_long_int_ptr::class, uintmax_t_ptr::class); 237 | \\class_alias(unsigned_long_int_ptr_ptr::class, uintmax_t_ptr_ptr::class); 238 | \\class_alias(unsigned_long_int_ptr_ptr_ptr::class, uintmax_t_ptr_ptr_ptr::class); 239 | \\class_alias(unsigned_long_int_ptr_ptr_ptr_ptr::class, uintmax_t_ptr_ptr_ptr_ptr::class);'; 240 | 241 | protected FFIMe $lib; 242 | protected Printer $printer; 243 | 244 | public function setUp(): void { 245 | $this->lib = new class( 246 | PHP_OS_FAMILY === "Darwin" ? "/usr/lib/libSystem.B.dylib" : "/lib/x86_64-linux-gnu/libc.so.6", 247 | [ 248 | __DIR__, 249 | __DIR__ . '/../include' 250 | ] 251 | ) extends FFIMe { 252 | // Bypass filtering for tests 253 | protected function filterSymbolDeclarations(): void {} 254 | }; 255 | } 256 | 257 | public function tearDown(): void { 258 | @unlink(__DIR__ . '/basic_typedefsTest.result.php'); 259 | } 260 | 261 | /** 262 | * @textdox Test basic parsing of typedefs 263 | */ 264 | public function testCodeGen() { 265 | $this->lib->include(__DIR__ . '/basic_typedefsTest.h'); 266 | $this->lib->codeGen("test\\test", __DIR__ . '/basic_typedefsTest.result.php'); 267 | $this->assertFileExists(__DIR__ . '/basic_typedefsTest.result.php'); 268 | $this->assertStringMatchesFormat(self::EXPECTED, trim(file_get_contents(__DIR__ . '/basic_typedefsTest.result.php'))); 269 | } 270 | } -------------------------------------------------------------------------------- /test/generated/char_returnsTest.h: -------------------------------------------------------------------------------- 1 | typedef struct LLVMOpaqueModule *LLVMModuleRef; 2 | const char *LLVMGetModuleIdentifier(LLVMModuleRef M, size_t *Len); 3 | -------------------------------------------------------------------------------- /test/generated/char_returnsTest.php: -------------------------------------------------------------------------------- 1 | ffi = FFI::cdef(self::HEADER_DEF, $pathToSoFile); 38 | } 39 | 40 | public static function cast(itest $from, string $to): itest { 41 | if (!is_a($to, itest::class, true)) { 42 | throw new \\LogicException("Cannot cast to a non-wrapper type"); 43 | } 44 | return new $to(self::$staticFFI->cast($to::getType(), $from->getData())); 45 | } 46 | 47 | public static function makeArray(string $class, int|array $elements): itest { 48 | $type = $class::getType(); 49 | if (substr($type, -1) !== "*") { 50 | throw new \\LogicException("Attempting to make a non-pointer element into an array"); 51 | } 52 | if (is_int($elements)) { 53 | $cdata = self::$staticFFI->new(substr($type, 0, -1) . "[$elements]"); 54 | } else { 55 | $cdata = self::$staticFFI->new(substr($type, 0, -1) . "[" . count($elements) . "]"); 56 | foreach ($elements as $key => $raw) { 57 | $cdata[$key] = \\is_scalar($raw) ? \\is_int($raw) && $type === "char*" ? \\chr($raw) : $raw : $raw->getData(); 58 | } 59 | } 60 | $object = new $class(self::$staticFFI->cast($type, \\FFI::addr($cdata))); 61 | self::$__arrayWeakMap[$object] = $cdata; 62 | return $object; 63 | } 64 | 65 | public static function sizeof($classOrObject): int { 66 | if (is_object($classOrObject) && $classOrObject instanceof itest) { 67 | return FFI::sizeof($classOrObject->getData()); 68 | } elseif (is_a($classOrObject, itest::class, true)) { 69 | return FFI::sizeof(self::$staticFFI->type($classOrObject::getType())); 70 | } else { 71 | throw new \\LogicException("Unknown class/object passed to sizeof()"); 72 | } 73 | } 74 | 75 | public function getFFI(): FFI { 76 | return $this->ffi; 77 | } 78 | 79 | 80 | public function __get(string $name) { 81 | switch($name) { 82 | default: return $this->ffi->$name; 83 | } 84 | } 85 | public function __set(string $name, $value) { 86 | switch($name) { 87 | default: return $this->ffi->$name; 88 | } 89 | } 90 | public function __allocCachedString(string $str): FFI\\CData { 91 | return $this->__literalStrings[$str] ??= string_::ownedZero($str)->getData(); 92 | } 93 | public function LLVMGetModuleIdentifier(void_ptr | struct_LLVMOpaqueModule_ptr | null | array $M, void_ptr | size_t_ptr | null | array $Len): ?string_ { 94 | $__ffi_internal_refsM = []; 95 | if (\\is_array($M)) { 96 | $_ = $this->ffi->new("struct LLVMOpaqueModule[" . \\count($M) . "]"); 97 | $_i = 0; 98 | if ($M) { 99 | if ($_ref = \\ReflectionReference::fromArrayElement($M, \\key($M))) { 100 | foreach ($M as $_k => $_v) { 101 | $__ffi_internal_refsM[$_i] = &$M[$_k]; 102 | $_[$_i++] = $_v?->getData(); 103 | } 104 | $__ffi_internal_originalM = $M = $_; 105 | } else { 106 | foreach ($M as $_v) { 107 | $_[$_i++] = $_v?->getData(); 108 | } 109 | $M = $_; 110 | } 111 | } 112 | } else { 113 | $M = $M?->getData(); 114 | if ($M !== null) { 115 | $M = $this->ffi->cast("struct LLVMOpaqueModule*", $M); 116 | } 117 | } 118 | $__ffi_internal_refsLen = []; 119 | if (\\is_array($Len)) { 120 | $_ = $this->ffi->new("size_t[" . \\count($Len) . "]"); 121 | $_i = 0; 122 | if ($Len) { 123 | if ($_ref = \\ReflectionReference::fromArrayElement($Len, \\key($Len))) { 124 | foreach ($Len as $_k => $_v) { 125 | $__ffi_internal_refsLen[$_i] = &$Len[$_k]; 126 | $_[$_i++] = $_v ?? 0; 127 | } 128 | $__ffi_internal_originalLen = $Len = $_; 129 | } else { 130 | foreach ($Len as $_v) { 131 | $_[$_i++] = $_v ?? 0; 132 | } 133 | $Len = $_; 134 | } 135 | } 136 | } else { 137 | $Len = $Len?->getData(); 138 | if ($Len !== null) { 139 | $Len = $this->ffi->cast("size_t*", $Len); 140 | } 141 | } 142 | $result = $this->ffi->LLVMGetModuleIdentifier($M, $Len); 143 | foreach ($__ffi_internal_refsM as $_k => &$__ffi_internal_ref_v) { 144 | $__ffi_internal_ref_v = $__ffi_internal_originalM[$_k]; 145 | if ($__ffi_internal_ref_v !== null) { 146 | $__ffi_internal_ref_v = new struct_LLVMOpaqueModule($__ffi_internal_ref_v); 147 | } 148 | } 149 | foreach ($__ffi_internal_refsLen as $_k => &$__ffi_internal_ref_v) { 150 | $__ffi_internal_ref_v = $__ffi_internal_originalLen[$_k]; 151 | } 152 | return $result === null ? null : new string_($result); 153 | } 154 | } 155 | 156 | class string_ implements itest, itest_ptr, \\ArrayAccess { 157 | private FFI\\CData $data; 158 | public function __construct(FFI\\CData $data) { $this->data = $data; } 159 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 160 | public function getData(): FFI\\CData { return $this->data; } 161 | public function equals(string_ $other): bool { return $this->data == $other->data; } 162 | public function addr(): string_ptr { return new string_ptr(FFI::addr($this->data)); } 163 | public function deref(int $n = 0): int { return \\ord($this->data[$n]); } 164 | #[\\ReturnTypeWillChange] public function offsetGet($offset): int { return $this->deref($offset); } 165 | #[\\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 166 | #[\\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \\Error("Cannot unset C structures"); } 167 | #[\\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = \\chr($value); } 168 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 169 | /** @return int[] */ public function toArray(?int $length = null): array { $ret = []; if ($length === null) { $i = 0; while ("\\0" !== $cur = $this->data[$i++]) { $ret[] = \\ord($cur); } } else { for ($i = 0; $i < $length; ++$i) { $ret[] = \\ord($this->data[$i]); } } return $ret; } 170 | public function toString(?int $length = null): string { return $length === null ? FFI::string($this->data) : FFI::string($this->data, $length); } 171 | public static function persistent(string $string): self { $str = new self(FFI::cdef()->new("char[" . \\strlen($string) . "]", false)); FFI::memcpy($str->data, $string, \\strlen($string)); return $str; } 172 | public static function owned(string $string): self { $str = new self(FFI::cdef()->new("char[" . \\strlen($string) . "]", true)); FFI::memcpy($str->data, $string, \\strlen($string)); return $str; } 173 | public static function persistentZero(string $string): self { return self::persistent("$string\\0"); } 174 | public static function ownedZero(string $string): self { return self::owned("$string\\0"); } 175 | public function set(int | void_ptr | string_ $value): void { 176 | if (\\is_scalar($value)) { 177 | $this->data[0] = \\chr($value); 178 | } else { 179 | FFI::addr($this->data)[0] = $value->getData(); 180 | } 181 | } 182 | public static function getType(): string { return \'char*\'; } 183 | public static function size(): int { return testFFI::sizeof(self::class); } 184 | public function getDefinition(): string { return static::getType(); } 185 | } 186 | class string_ptr implements itest, itest_ptr, \\ArrayAccess { 187 | private FFI\\CData $data; 188 | public function __construct(FFI\\CData $data) { $this->data = $data; } 189 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 190 | public function getData(): FFI\\CData { return $this->data; } 191 | public function equals(string_ptr $other): bool { return $this->data == $other->data; } 192 | public function addr(): string_ptr_ptr { return new string_ptr_ptr(FFI::addr($this->data)); } 193 | public function deref(int $n = 0): string_ { return new string_($this->data[$n]); } 194 | #[\\ReturnTypeWillChange] public function offsetGet($offset): string_ { return $this->deref($offset); } 195 | #[\\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 196 | #[\\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \\Error("Cannot unset C structures"); } 197 | #[\\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = $value->getData(); } 198 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 199 | /** @return string_[] */ public function toArray(?int $length = null): array { $ret = []; if ($length === null) { $i = 0; while (null !== $cur = $this->data[$i++]) { $ret[] = new string_($cur); } } else { for ($i = 0; $i < $length; ++$i) { $ret[] = new string_($this->data[$i]); } } return $ret; } 200 | public function set(void_ptr | string_ptr $value): void { 201 | FFI::addr($this->data)[0] = $value->getData(); 202 | } 203 | public static function getType(): string { return \'char**\'; } 204 | public static function size(): int { return testFFI::sizeof(self::class); } 205 | public function getDefinition(): string { return static::getType(); } 206 | } 207 | class string_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 208 | class string_ptr_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 209 | class void_ptr implements itest, itest_ptr { 210 | private FFI\\CData $data; 211 | public function __construct(FFI\\CData $data) { $this->data = $data; } 212 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 213 | public function getData(): FFI\\CData { return $this->data; } 214 | public function equals(void_ptr $other): bool { return $this->data == $other->data; } 215 | public function addr(): void_ptr_ptr { return new void_ptr_ptr(FFI::addr($this->data)); } 216 | public function set(itest_ptr $value): void { 217 | FFI::addr($this->data)[0] = $value->getData(); 218 | } 219 | public static function getType(): string { return \'void*\'; } 220 | public static function size(): int { return testFFI::sizeof(self::class); } 221 | public function getDefinition(): string { return static::getType(); } 222 | } 223 | class void_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 224 | class void_ptr_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 225 | class void_ptr_ptr_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 226 | class size_t_ptr implements itest, itest_ptr, \\ArrayAccess { 227 | private FFI\\CData $data; 228 | public function __construct(FFI\\CData $data) { $this->data = $data; } 229 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 230 | public function getData(): FFI\\CData { return $this->data; } 231 | public function equals(size_t_ptr $other): bool { return $this->data == $other->data; } 232 | public function addr(): size_t_ptr_ptr { return new size_t_ptr_ptr(FFI::addr($this->data)); } 233 | public function deref(int $n = 0): int { return $this->data[$n]; } 234 | #[\\ReturnTypeWillChange] public function offsetGet($offset): int { return $this->deref($offset); } 235 | #[\\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 236 | #[\\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \\Error("Cannot unset C structures"); } 237 | #[\\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = $value; } 238 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 239 | /** @return int[] */ public function toArray(int $length): array { $ret = []; for ($i = 0; $i < $length; ++$i) { $ret[] = ($this->data[$i]); } return $ret; } 240 | public function set(int | void_ptr | size_t_ptr $value): void { 241 | if (\\is_scalar($value)) { 242 | $this->data[0] = $value; 243 | } else { 244 | FFI::addr($this->data)[0] = $value->getData(); 245 | } 246 | } 247 | public static function getType(): string { return \'size_t*\'; } 248 | public static function size(): int { return testFFI::sizeof(self::class); } 249 | public function getDefinition(): string { return static::getType(); } 250 | } 251 | class size_t_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 252 | class size_t_ptr_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 253 | class size_t_ptr_ptr_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 254 | (function() { self::$staticFFI = \\FFI::cdef(testFFI::TYPES_DEF); self::$__arrayWeakMap = new \\WeakMap; })->bindTo(null, testFFI::class)(); 255 | \\class_alias(struct_LLVMOpaqueModule_ptr::class, LLVMModuleRef::class); 256 | \\class_alias(struct_LLVMOpaqueModule_ptr_ptr::class, LLVMModuleRef_ptr::class); 257 | \\class_alias(struct_LLVMOpaqueModule_ptr_ptr_ptr::class, LLVMModuleRef_ptr_ptr::class); 258 | \\class_alias(struct_LLVMOpaqueModule_ptr_ptr_ptr_ptr::class, LLVMModuleRef_ptr_ptr_ptr::class);'; 259 | 260 | protected FFIMe $lib; 261 | protected Printer $printer; 262 | 263 | public function setUp(): void { 264 | $this->lib = new class( 265 | PHP_OS_FAMILY === "Darwin" ? "/usr/lib/libSystem.B.dylib" : "/lib/x86_64-linux-gnu/libc.so.6", 266 | [ 267 | __DIR__, 268 | __DIR__ . '/../include' 269 | ] 270 | ) extends FFIMe { 271 | // Bypass filtering for tests 272 | protected function filterSymbolDeclarations(): void {} 273 | }; 274 | } 275 | 276 | public function tearDown(): void { 277 | @unlink(__DIR__ . '/char_returnsTest.result.php'); 278 | } 279 | 280 | /** 281 | * @textdox Test basic parsing of character returns 282 | */ 283 | public function testCodeGen() { 284 | $this->lib->include(__DIR__ . '/char_returnsTest.h'); 285 | $this->lib->codeGen("test\\test", __DIR__ . '/char_returnsTest.result.php'); 286 | $this->assertFileExists(__DIR__ . '/char_returnsTest.result.php'); 287 | $this->assertStringMatchesFormat(self::EXPECTED, trim(file_get_contents(__DIR__ . '/char_returnsTest.result.php'))); 288 | } 289 | } -------------------------------------------------------------------------------- /test/generated/enum_self_referencesTest.h: -------------------------------------------------------------------------------- 1 | enum A { 2 | A1 = 1, 3 | A2 = A1, 4 | A3 = 2, 5 | }; 6 | typedef enum { 7 | B1 = 1, 8 | B2 = B1, 9 | B3 = B1 | B2, 10 | B4 11 | } B; 12 | 13 | -------------------------------------------------------------------------------- /test/generated/enum_self_referencesTest.php: -------------------------------------------------------------------------------- 1 | ffi = FFI::cdef(self::HEADER_DEF, $pathToSoFile); 54 | } 55 | 56 | public static function cast(itest $from, string $to): itest { 57 | if (!is_a($to, itest::class, true)) { 58 | throw new \\LogicException("Cannot cast to a non-wrapper type"); 59 | } 60 | return new $to(self::$staticFFI->cast($to::getType(), $from->getData())); 61 | } 62 | 63 | public static function makeArray(string $class, int|array $elements): itest { 64 | $type = $class::getType(); 65 | if (substr($type, -1) !== "*") { 66 | throw new \\LogicException("Attempting to make a non-pointer element into an array"); 67 | } 68 | if (is_int($elements)) { 69 | $cdata = self::$staticFFI->new(substr($type, 0, -1) . "[$elements]"); 70 | } else { 71 | $cdata = self::$staticFFI->new(substr($type, 0, -1) . "[" . count($elements) . "]"); 72 | foreach ($elements as $key => $raw) { 73 | $cdata[$key] = \\is_scalar($raw) ? \\is_int($raw) && $type === "char*" ? \\chr($raw) : $raw : $raw->getData(); 74 | } 75 | } 76 | $object = new $class(self::$staticFFI->cast($type, \\FFI::addr($cdata))); 77 | self::$__arrayWeakMap[$object] = $cdata; 78 | return $object; 79 | } 80 | 81 | public static function sizeof($classOrObject): int { 82 | if (is_object($classOrObject) && $classOrObject instanceof itest) { 83 | return FFI::sizeof($classOrObject->getData()); 84 | } elseif (is_a($classOrObject, itest::class, true)) { 85 | return FFI::sizeof(self::$staticFFI->type($classOrObject::getType())); 86 | } else { 87 | throw new \\LogicException("Unknown class/object passed to sizeof()"); 88 | } 89 | } 90 | 91 | public function getFFI(): FFI { 92 | return $this->ffi; 93 | } 94 | 95 | 96 | public function __get(string $name) { 97 | switch($name) { 98 | default: return $this->ffi->$name; 99 | } 100 | } 101 | public function __set(string $name, $value) { 102 | switch($name) { 103 | default: return $this->ffi->$name; 104 | } 105 | } 106 | public function __allocCachedString(string $str): FFI\\CData { 107 | return $this->__literalStrings[$str] ??= string_::ownedZero($str)->getData(); 108 | } 109 | } 110 | 111 | class string_ implements itest, itest_ptr, \\ArrayAccess { 112 | private FFI\\CData $data; 113 | public function __construct(FFI\\CData $data) { $this->data = $data; } 114 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 115 | public function getData(): FFI\\CData { return $this->data; } 116 | public function equals(string_ $other): bool { return $this->data == $other->data; } 117 | public function addr(): string_ptr { return new string_ptr(FFI::addr($this->data)); } 118 | public function deref(int $n = 0): int { return \\ord($this->data[$n]); } 119 | #[\\ReturnTypeWillChange] public function offsetGet($offset): int { return $this->deref($offset); } 120 | #[\\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 121 | #[\\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \\Error("Cannot unset C structures"); } 122 | #[\\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = \\chr($value); } 123 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 124 | /** @return int[] */ public function toArray(?int $length = null): array { $ret = []; if ($length === null) { $i = 0; while ("\\0" !== $cur = $this->data[$i++]) { $ret[] = \\ord($cur); } } else { for ($i = 0; $i < $length; ++$i) { $ret[] = \\ord($this->data[$i]); } } return $ret; } 125 | public function toString(?int $length = null): string { return $length === null ? FFI::string($this->data) : FFI::string($this->data, $length); } 126 | public static function persistent(string $string): self { $str = new self(FFI::cdef()->new("char[" . \\strlen($string) . "]", false)); FFI::memcpy($str->data, $string, \\strlen($string)); return $str; } 127 | public static function owned(string $string): self { $str = new self(FFI::cdef()->new("char[" . \\strlen($string) . "]", true)); FFI::memcpy($str->data, $string, \\strlen($string)); return $str; } 128 | public static function persistentZero(string $string): self { return self::persistent("$string\\0"); } 129 | public static function ownedZero(string $string): self { return self::owned("$string\\0"); } 130 | public function set(int | void_ptr | string_ $value): void { 131 | if (\\is_scalar($value)) { 132 | $this->data[0] = \\chr($value); 133 | } else { 134 | FFI::addr($this->data)[0] = $value->getData(); 135 | } 136 | } 137 | public static function getType(): string { return \'char*\'; } 138 | public static function size(): int { return testFFI::sizeof(self::class); } 139 | public function getDefinition(): string { return static::getType(); } 140 | } 141 | class string_ptr implements itest, itest_ptr, \\ArrayAccess { 142 | private FFI\\CData $data; 143 | public function __construct(FFI\\CData $data) { $this->data = $data; } 144 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 145 | public function getData(): FFI\\CData { return $this->data; } 146 | public function equals(string_ptr $other): bool { return $this->data == $other->data; } 147 | public function addr(): string_ptr_ptr { return new string_ptr_ptr(FFI::addr($this->data)); } 148 | public function deref(int $n = 0): string_ { return new string_($this->data[$n]); } 149 | #[\\ReturnTypeWillChange] public function offsetGet($offset): string_ { return $this->deref($offset); } 150 | #[\\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 151 | #[\\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \\Error("Cannot unset C structures"); } 152 | #[\\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = $value->getData(); } 153 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 154 | /** @return string_[] */ public function toArray(?int $length = null): array { $ret = []; if ($length === null) { $i = 0; while (null !== $cur = $this->data[$i++]) { $ret[] = new string_($cur); } } else { for ($i = 0; $i < $length; ++$i) { $ret[] = new string_($this->data[$i]); } } return $ret; } 155 | public function set(void_ptr | string_ptr $value): void { 156 | FFI::addr($this->data)[0] = $value->getData(); 157 | } 158 | public static function getType(): string { return \'char**\'; } 159 | public static function size(): int { return testFFI::sizeof(self::class); } 160 | public function getDefinition(): string { return static::getType(); } 161 | } 162 | class string_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 163 | class string_ptr_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 164 | class void_ptr implements itest, itest_ptr { 165 | private FFI\\CData $data; 166 | public function __construct(FFI\\CData $data) { $this->data = $data; } 167 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 168 | public function getData(): FFI\\CData { return $this->data; } 169 | public function equals(void_ptr $other): bool { return $this->data == $other->data; } 170 | public function addr(): void_ptr_ptr { return new void_ptr_ptr(FFI::addr($this->data)); } 171 | public function set(itest_ptr $value): void { 172 | FFI::addr($this->data)[0] = $value->getData(); 173 | } 174 | public static function getType(): string { return \'void*\'; } 175 | public static function size(): int { return testFFI::sizeof(self::class); } 176 | public function getDefinition(): string { return static::getType(); } 177 | } 178 | class void_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 179 | class void_ptr_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 180 | class void_ptr_ptr_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 181 | class int_ptr implements itest, itest_ptr, \\ArrayAccess { 182 | private FFI\\CData $data; 183 | public function __construct(FFI\\CData $data) { $this->data = $data; } 184 | public static function castFrom(itest $data): self { return testFFI::cast($data, self::class); } 185 | public function getData(): FFI\\CData { return $this->data; } 186 | public function equals(int_ptr $other): bool { return $this->data == $other->data; } 187 | public function addr(): int_ptr_ptr { return new int_ptr_ptr(FFI::addr($this->data)); } 188 | public function deref(int $n = 0): int { return $this->data[$n]; } 189 | #[\\ReturnTypeWillChange] public function offsetGet($offset): int { return $this->deref($offset); } 190 | #[\\ReturnTypeWillChange] public function offsetExists($offset): bool { return !FFI::isNull($this->data); } 191 | #[\\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new \\Error("Cannot unset C structures"); } 192 | #[\\ReturnTypeWillChange] public function offsetSet($offset, $value): void { $this->data[$offset] = $value; } 193 | public static function array(int $size = 1): self { return testFFI::makeArray(self::class, $size); } 194 | /** @return int[] */ public function toArray(int $length): array { $ret = []; for ($i = 0; $i < $length; ++$i) { $ret[] = ($this->data[$i]); } return $ret; } 195 | public function set(int | void_ptr | int_ptr $value): void { 196 | if (\\is_scalar($value)) { 197 | $this->data[0] = $value; 198 | } else { 199 | FFI::addr($this->data)[0] = $value->getData(); 200 | } 201 | } 202 | public static function getType(): string { return \'int*\'; } 203 | public static function size(): int { return testFFI::sizeof(self::class); } 204 | public function getDefinition(): string { return static::getType(); } 205 | } 206 | class int_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 207 | class int_ptr_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 208 | class int_ptr_ptr_ptr_ptr implements itest, itest_ptr, \\ArrayAccess {%a} 209 | (function() { self::$staticFFI = \\FFI::cdef(testFFI::TYPES_DEF); self::$__arrayWeakMap = new \\WeakMap; })->bindTo(null, testFFI::class)();'; 210 | 211 | protected FFIMe $lib; 212 | protected Printer $printer; 213 | 214 | public function setUp(): void { 215 | $this->lib = new class( 216 | PHP_OS_FAMILY === "Darwin" ? "/usr/lib/libSystem.B.dylib" : "/lib/x86_64-linux-gnu/libc.so.6", 217 | [ 218 | __DIR__, 219 | __DIR__ . '/../include' 220 | ] 221 | ) extends FFIMe { 222 | // Bypass filtering for tests 223 | protected function filterSymbolDeclarations(): void {} 224 | }; 225 | } 226 | 227 | public function tearDown(): void { 228 | @unlink(__DIR__ . '/enum_self_referencesTest.result.php'); 229 | } 230 | 231 | /** 232 | * @textdox Test enum self references 233 | */ 234 | public function testCodeGen() { 235 | $this->lib->include(__DIR__ . '/enum_self_referencesTest.h'); 236 | $this->lib->codeGen("test\\test", __DIR__ . '/enum_self_referencesTest.result.php'); 237 | $this->assertFileExists(__DIR__ . '/enum_self_referencesTest.result.php'); 238 | $this->assertStringMatchesFormat(self::EXPECTED, trim(file_get_contents(__DIR__ . '/enum_self_referencesTest.result.php'))); 239 | } 240 | } -------------------------------------------------------------------------------- /test/rebuild.php: -------------------------------------------------------------------------------- 1 | assertStringContainsString(self::EXPECTED, trim(file_get_contents(__DIR__ . ' . $resultFile . ')));'; 81 | } elseif (isset($test[3]['EXPECTF'])) { 82 | $expected = trim($test[3]['EXPECTF']); 83 | $assert = '$this->assertStringMatchesFormat(self::EXPECTED, trim(file_get_contents(__DIR__ . ' . $resultFile . ')));'; 84 | } else { 85 | throw new \LogicException("Unknown test expectation type"); 86 | } 87 | 88 | if ($regenerate) { 89 | $ffime = new class(PHP_OS_FAMILY === "Darwin" ? "/usr/lib/libSystem.B.dylib" : "/lib/x86_64-linux-gnu/libc.so.6") extends FFIMe { 90 | // Bypass filtering for tests 91 | protected function filterSymbolDeclarations(): void {} 92 | }; 93 | $ffime->include($targetFile . '.h'); 94 | $tmp = tempnam(sys_get_temp_dir(), "ffi-rebuild-"); 95 | $ffime->codeGen('test\test', $tmp); 96 | $out = preg_replace(['(namespace \Ktest(?=;)|const __\K(x86_64|aarch64)(?=__)|const SOFILE = \'\K.+(?=\';))', '(class .*_ptr_ptr.*\{\K(.*+|\r?\n\s*+)+?(?=^}))m'], ["%s", "%a"], file_get_contents($tmp)); 97 | file_put_contents($test[0], preg_split("(--EXPECTF--\s+\K)", file_get_contents($test[0]))[0] . $out); 98 | unlink($tmp); 99 | 100 | $expected = trim($out); 101 | } 102 | 103 | 104 | $return = 'lib = new class( 121 | PHP_OS_FAMILY === "Darwin" ? "/usr/lib/libSystem.B.dylib" : "/lib/x86_64-linux-gnu/libc.so.6", 122 | [ 123 | __DIR__, 124 | __DIR__ . ' . var_export($searchPath, true) . ' 125 | ] 126 | ) extends FFIMe { 127 | // Bypass filtering for tests 128 | protected function filterSymbolDeclarations(): void {} 129 | }; 130 | } 131 | 132 | public function tearDown(): void { 133 | @unlink(__DIR__ . ' . $resultFile . '); 134 | } 135 | 136 | /** 137 | * @textdox ' . $test[1] . ' 138 | */ 139 | public function testCodeGen() { 140 | $this->lib->include(__DIR__ . ' . var_export('/' . $relativeTarget . '.h', true) . '); 141 | $this->lib->codeGen("test\\\\test", __DIR__ . ' . $resultFile . '); 142 | $this->assertFileExists(__DIR__ . ' . $resultFile . '); 143 | ' . $assert . ' 144 | } 145 | }'; 146 | file_put_contents($targetFile . '.php', $return); 147 | 148 | } 149 | 150 | 151 | 152 | 153 | 154 | function provideTestsFromDir(string $dir): \Generator { 155 | foreach (new \DirectoryIterator($dir) as $path) { 156 | if (!$path->isDir() || $path->isDot()) { 157 | continue; 158 | } 159 | yield from provideTestsFromDir($path->getPathname()); 160 | } 161 | foreach (new \GlobIterator($dir . '/*.phpt') as $test) { 162 | yield parseTest(realpath($test->getPathname())); 163 | } 164 | } 165 | 166 | function parseTest(string $filename): array { 167 | $sections = []; 168 | $section = ''; 169 | foreach (file($filename) as $line) { 170 | if (preg_match('(^--([_A-Z]+)--)', $line, $result)) { 171 | $section = $result[1]; 172 | $sections[$section] = ''; 173 | continue; 174 | } 175 | if (empty($section)) { 176 | throw new \LogicException("Invalid PHPT file: empty section header"); 177 | } 178 | $sections[$section] .= $line; 179 | } 180 | if (!isset($sections['TEST'])) { 181 | throw new \LogicException("Every test must have a name"); 182 | } 183 | if (isset($sections['FILEEOF'])) { 184 | $sections['FILE'] = rtrim($sections['FILEEOF'], "\r\n"); 185 | unset($sections['FILEEOF']); 186 | } 187 | parseExternal($sections, dirname($filename)); 188 | if (!validate($sections)) { 189 | throw new \LogicException("Invalid PHPT File"); 190 | } 191 | foreach (UNSUPPORTED_SECTIONS as $section) { 192 | if (isset($sections[$section])) { 193 | throw new \LogicException("PHPT $section sections are not supported"); 194 | } 195 | } 196 | return [ 197 | $filename, 198 | trim($sections["TEST"]), 199 | $sections['FILE'], 200 | $sections, 201 | ]; 202 | } 203 | 204 | function parseExternal(array &$sections, string $testdir): void { 205 | foreach (EXTERNAL_SECTIONS as $section) { 206 | if (isset($sections[$section . '_EXTERNAL'])) { 207 | $filename = trim($sections[$section . '_EXTERNAL']); 208 | if (!is_file($testdir . '/' . $filename)) { 209 | throw new \RuntimeException("Could not load external file $filename"); 210 | } 211 | $sections[$section] = file_get_contents($testdir . '/' . $filename); 212 | } 213 | } 214 | } 215 | 216 | function validate(array &$sections): bool { 217 | foreach (REQUIRED_SECTIONS as $section) { 218 | if (is_array($section)) { 219 | foreach ($section as $any) { 220 | if (isset($sections[$any])) { 221 | continue 2; 222 | } 223 | } 224 | return false; 225 | } elseif (!isset($sections[$section])) { 226 | return false; 227 | } 228 | } 229 | return true; 230 | } 231 | --------------------------------------------------------------------------------