Open ajpinedam opened 3 years ago
Looks like we explicitly removed it 10+ years ago: https://github.com/xamarin/xamarin-android/blob/4cd3913def29cb445e07a1e0c3a0e03877ad8ee8/src/Mono.Android/metadata#L473.
I'll need to investigate a bit to see if I can figure out why.
You can probably work around it using one of:
Get(byte[] dst);
Get(byte[] dst, int offset, int length);
I'll need to investigate a bit to see if I can figure out why.
I suspect part of the problem is our old favorite covariant return types: ByteByffer.array()
overrides [Buffer.array()
](https://developer.android.com/reference/java/nio/Buffer#array()), which is abstract
:
abstract class Buffer {
public abstract Object array();
}
class ByteBuffer extends Buffer {
public byte[] array() {…}
}
What should a binding for this look like? I can think of only three solutions:
Java.Lang.Object
all the things!Buffer.array()
, bind ByteBuffer.array()
and other overrides "normally"java.lang.Object
and System.Object
-- emitting object
everywhere java.lang.Object
exists -- then using C#9 covariant return types in the override.Java.Lang.Object
all the things!This "works", but really results in a fugly binding:
abstract class Buffer {
public abstract Java.Lang.Object Array();
}
class ByteBuffer : Buffer {
public override Java.Lang.Object Array();
}
Buffer.array()
, bind ByteBuffer.array()
and other overrides "normally"This makes for a "nicer" API, but means Buffer
in-and-of-itself is a potentially "less useful" abstraction:
abstract class Buffer {
public abstract Java.Lang.Object Array();
}
class ByteBuffer : Buffer {
public byte[] Array();
}
C#9 adds covariant return types!
This is a more "pie-in-the-sky" idea, but What If™
abstract class Buffer {
public abstract object Array();
}
class ByteBuffer : Buffer {
public override byte[] Array();
}
This assumes that C#9 covariant return types can have ByteBuffer.Array()
return a byte[]
when the base method returns object
. I don't know if this is actually supportable.
Then there's the "ABI break" concern: we can't bind all instances of java.lang.Object
with System.Object
, it'll break everything, so if we wanted to consider this we'd have to only do this for members added since API-30.
(Then there's the other question of what happens with older compilers trying to use this…)
What a great explanation @jonpryor ! Thanks.
Just wondering if we can avoid breaking things by creating an Extension method (or any Utility method) the same way we have done in other cases (I don't have any example now but sure that I have seen it).
This way we can leave things as they are in terms of the Java APIs but expose our own implementation that will return the desired value.
btw: I tried @jpobst suggestion (Thanks for this)
Get(byte[] dst);
but I am having an exception of type Java.Nio.BufferUnderflowException
This is what I am doing:
var byteBuffer = ByteBuffer.Allocate(2).PutShort(1);
byte[] result = new byte[10];
byteBuffer.Get(result);
I am getting the same exception even while using just
var tbyte = byteBuffer.Get();
I think you are trying to read from the end of the buffer. You will need to "rewind" so that it is pointed at the beginning:
var byteBuffer = ByteBuffer.Allocate (10).PutShort (33);
var result = new byte [byteBuffer.Capacity ()];
byteBuffer.Rewind ();
byteBuffer.Get (result);
Thank you @jpobst .
I think you are trying to read from the end of the buffer. You will need to "rewind" so that it is pointed at the beginning:
var byteBuffer = ByteBuffer.Allocate (10).PutShort (33); var result = new byte [byteBuffer.Capacity ()]; byteBuffer.Rewind (); byteBuffer.Get (result);
If I understand correctly, without the [array()
](https://developer.android.com/reference/java/nio/ByteBuffer#array()) method, in order to access the buffer of a ByteBuffer
instance, I have to allocate and copy to an intermediary byte buffer (in your example, it's the result
buffer).
Due to performance reasons, I'd like to avoid the allocation and copying, is it possible?
Stupid question, can I make a binding myself for the array()
method somehow?
If you want to access the array in C# code, yes, you will need to allocate memory in C# and copy the array there. If there were an array()
method binding it would do exactly this for you, but it would still allocate the memory in C# and copy.
Note that it's actually worse than that. If you then want the ByteBuffer
to have the changes you made in C# you will need to copy your changes back to the ByteBuffer
.
The problem is that the backing byte[]
lives in the Java VM. In order to manipulate it in the .NET runtime you either have to copy it to .NET runtime and then copy it back, or you can manipulate it purely using the Java methods defined on ByteBuffer
like the various get*
and put*
methods.
@jpobst Thanks. But what if the ByteBuffer was directly allocated? (ByteBuffer.directAllocate()
) I was hoping that the combination of direct memory + being able to access that memory by the array()
would have avoided any allocation and copying.
No, there is no way for the .NET runtime to directly access memory owned by the Java runtime.
A few other options that may or may not help:
byte[]
, you can call ByteBuffer.Wrap (byte[])
which will only copy the array once (when it copies it from C# to Java to the initial ByteBuffer
). Note that after the initial copy, modifying it on the C# side will have no effect so this only helps if you no longer need to access it from C#.MyJavaClass.doBufferManipulation (ByteBuffer buf)
)Thanks again. I was looking for a way to serialize some an array of some data (integer and strings) and send the bytes from .NET to Java where it's deserialized in the fastest way possible through some kind of ByteBuffer. It does not have to be pretty at all, speed is the most important.
Recently i had to deal with ByteBuffer
so I stumbled upon this issue. Is this abandoned?
Recently i had to deal with ByteBuffer so I stumbled upon this issue. Is this abandoned?
The status of this issue has not changed. If you need to copy the contents of a ByteBuffer
into a C# array, use the workaround code above.
Thank you for you answer. The thing is that in my use case, I have to edit the content of the ByteBuffer directly and it is quite large (up to 4K video frame). To copy the data out and back into the buffer is quite time consuming. The second alternative is going into direct memory manipulation using unsafe code.
Frankly there are workarounds, but it would be easier to modify the underlying array directly through safe code if there is one...
Even if the array ()
method was available, I do not think it would do what you want. The C# byte[]
it would return would be a copy and would no longer be linked to the Java one, so changing it would not update the Java one.
I think you have 2 options:
Get
to copy the whole (or part) of the array to C#, do your modifications in C#, then use Put
to copy your changes back to Java.Put
methods to change the Java ByteBuffer
directly.
Steps to Reproduce
var byteBuffer = ByteBuffer.Allocate(2).PutShort(timeout);
byteBuffer
should have an Array() method (or Property) to get the array from theByteBuffer
object but it's not.byteBuffer.Array()
Java documentation: https://docs.oracle.com/javase/7/docs/api/java/nio/ByteBuffer.html
C# / Xamarin.Android Doc: https://docs.microsoft.com/en-us/dotnet/api/java.nio.bytebuffer?view=xamarin-android-sdk-9
Expected Behavior
Array method is present in the ByteBuffer object
Actual Behavior
Array method is not present in the ByteBuffer object
Version Information
Visual Studio Community 2019 for Mac Version 8.7.9 (build 9) Installation UUID: 68a8f36e-286f-4402-ad89-4d3d8e5c8fe8 GTK+ 2.24.23 (Raleigh theme) Xamarin.Mac 6.18.0.23 (d16-6 / 088c73638)