Skip to content

(ReadOnly)Span<byte> overloads on GetBuffer to reduce allocations and make using existing buffers easier #423

Open
@timyhac

Description

@timyhac

Would be beneficial if there were (ReadOnly)Span<byte> overloads to reduce allocations and make using existing buffers easier.
I'm happy to do a PR to implement if you are interested.

The native method declarations in C# would need to change since they use byte[] but won't be too much trouble since the native API uses a pointer anyway.

[DllImport(DLL_NAME, EntryPoint = nameof(plc_tag_get_raw_bytes), CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int plc_tag_get_raw_bytes(Int32 tag_id, int start_offset, [Out] byte[] buffer, int buffer_length);
[DllImport(DLL_NAME, EntryPoint = nameof(plc_tag_set_raw_bytes), CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int plc_tag_set_raw_bytes(Int32 tag_id, int start_offset, [In] byte[] buffer, int buffer_length);

https://github.com/libplctag/libplctag/blob/c55bc5876d938dda1c609750cde5ae4812d7b8a8/src/lib/libplctag.h#L505-L506

LIB_EXPORT int plc_tag_set_raw_bytes(int32_t id, int offset, uint8_t *buffer, int buffer_length);
LIB_EXPORT int plc_tag_get_raw_bytes(int32_t id, int offset, uint8_t *buffer, int buffer_length);

There are a few ways to do this, one approach can be found in Memory<T> and Span<T> usage guidelines under:

Rule #9: If you're wrapping a synchronous p/invoke method, your API should accept Span as a parameter.

Alternatively and ideally, would use P/Invoke source generation but this requires .NET 7+. Something like:

[LibraryImport(DLL_NAME, EntryPoint = nameof(plc_tag_get_raw_bytes))]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static partial int plc_tag_get_raw_bytes(Int32 tag_id, int start_offset, [MarshalUsing(CountElementName = nameof(buffer_length))] out Span<byte> buffer, int buffer_length);

[LibraryImport(DLL_NAME, EntryPoint = nameof(plc_tag_set_raw_bytes))]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static partial int plc_tag_set_raw_bytes(Int32 tag_id, int start_offset, ReadOnlySpan<byte> buffer, int buffer_length);

This would also make it possible to create ReadAsync and WriteAsync with Memory<byte> overloads too. Similar to Stream.ReadAsync and Stream.WriteAsync

public ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default);
public ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default);

Originally posted by @MitchRazga in #394 (comment)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions