-
Notifications
You must be signed in to change notification settings - Fork 7.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FFI] Allow to pass CData into struct and/or union fields. #11934
Comments
I think code responsible for that is here: ffi.c#L734. Though, I'm not sure how viable is adding CData. However you can do some trickery to have similar effect: <?php
// HackA
$ffi = FFI::new('struct { uint64_t field; }');
$hack = FFI::cast('uint32_t*', FFI::addr($ffi));
$hack[1] = 0x42; // First 32 bytes
$hack[0] = 0xDEAD_BEEF; // Second 32 bytes
var_dump(dechex($ffi->field)); // x64 only
var_dump(bin2hex(FFI::String($ffi->field, 8)));
// HackB
$ffi = FFI::new('struct { uint64_t field; }');
FFI::memcpy($ffi, strrev(hex2bin('00000042DEADBEEF')), 8);
var_dump(bin2hex(FFI::String($ffi->field, 8)));
// HackC
$ffi = FFI::new('struct { uint64_t field; }');
$hack = FFI::cast('struct { uint32_t lo; uint32_t hi; }', $ffi);
$hack->hi = 0x42; // First 32 bytes
$hack->lo = 0xDEAD_BEEF; // Second 32 bytes
var_dump(bin2hex(FFI::String($ffi->field, 8))); Also note that calling some methods statically will be deprecated in PHP 8.3: https://wiki.php.net/rfc/deprecate_functions_with_overloaded_signatures#fficast_ffinew_and_ffitype |
Yes, I know, but I used a more readable and convenient option as an example. The point here is not that it is impossible to fix this problem. But rather, that you have to fence a lot of non-obvious code (overengineer) for this. P.S. there is another hack through set by pointer, but I could not check the correctness of this option: $struct = FFI::new('struct { uint64_t value; }');
FFI::memcpy(FFI::addr($struct->value), pack('LL', 2**32-1, 42), 8); |
Assigning CData would be basically |
Ah, I was also looking at it and only now see your message. A simple PoC would be: diff --git a/ext/ffi/ffi.c b/ext/ffi/ffi.c
index f2d03699ba..50ca250a28 100644
--- a/ext/ffi/ffi.c
+++ b/ext/ffi/ffi.c
@@ -771,10 +771,24 @@ static zend_always_inline zend_result zend_ffi_zval_to_cdata(void *ptr, zend_ffi
lval = zval_get_long(value);
*(int16_t*)ptr = lval;
break;
- case ZEND_FFI_TYPE_UINT32:
- lval = zval_get_long(value);
+ case ZEND_FFI_TYPE_UINT32: {
+ bool failed;
+ lval = zval_try_get_long(value, &failed);
+ if (failed) {
+ if (Z_TYPE_P(value) == IS_OBJECT && Z_OBJCE_P(value) == zend_ffi_cdata_ce) {
+ zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(value);
+ if (zend_ffi_is_compatible_type(type, ZEND_FFI_TYPE(cdata->type)) &&
+ type->size == ZEND_FFI_TYPE(cdata->type)->size) {
+ memcpy(ptr, cdata->ptr, type->size);
+ return SUCCESS;
+ }
+ }
+ zend_ffi_assign_incompatible(value, type);
+ return FAILURE;
+ }
*(uint32_t*)ptr = lval;
break;
+ }
case ZEND_FFI_TYPE_SINT32:
lval = zval_get_long(value);
*(int32_t*)ptr = lval;
i.e. using the same strategy as the default case, and using a fallible |
I was thinking about using |
Depends on the interpretation of "changing every case" ig :P |
Shouldn't endianness be the same for types with the same sizes? I'm assuming you cannot assign |
It is, but what I mean by that is when the cases are generalised into a single one, then we'll have to convert |
From the point of view of BC: Maybe it makes sense to introduce the $struct = FFI::cdef('struct { int value; }');
$struct->value; // int(0)
FFI::get($string->value); // object(CData) { cdata: int(0) }
// And
$struct->value = 42;
FFI::set($string->value, 42); // Error: CData expected
FFI::set($string->value, FFI::new('int')); // Ok P.S. And it can do the same with functions. char* example(void);
///
$ffi->example(); // string(4) "test"
FFI::get($ffi->example()); // object(CData) { [0] => 't' } ...although I don't remember exactly in which cases the return results of functions are converted to PHP types, instead of CData |
I'm not sure what the best way to approach the BC is. I guess the BC break isn't that bad because we're replacing a warning of something that didn't work with something that works? |
I'm not sure what you mean, generalized would be only for CData, and this could be even limited to the same size CData.
CData wasn't supposed to be supported at all for fields, so "none". Existing assignments would remain the same. |
I feel like we actually want to approach this the same way, but we're having some miscommunication. In any case, feel free to work on this if you want, I don't want to steal this from you :) |
Yes, exactly, sorry about that.
I don't mind :D I've assumed nobody really wants to touch FFI. I'm really impressed how many things you're doing here! |
…ields Implementation based on a discussion with KapitanOczywisty.
No worries, glad we sorted this out.
:D I've filed #11935 that implements this. It's too late for 8.3 because we're past the feature freeze, but better late than never. |
Hmm, I think FFI is not popular enough to take this into account: https://packagist.org/?query=ffi&tags=ffi Almost all popular packages are written by me. I think it makes sense to ask release managers (like Pierrick Charron. Alas, I do not know his nickname on the github to ping) if this is it possible to add this feature/fix. |
Co-authored-by: KapitanOczywisty <44417092+KapitanOczywisty@users.noreply.github.com> Closes GH-11935.
Merged for 8.3 just in time. |
Feature/Bug
The following code not working now:
Or more real world usage:
Resulted in this output:
I think it makes sense to do automatic boxing/unboxing to int, at least in the same way as it is done with GMP. However, I still have no idea how to implement this without breaking backwards compatibility =\\
The text was updated successfully, but these errors were encountered: