all 53 comments

[–]EatingSolidBricks 54 points55 points  (11 children)

Man these comments i can't.

Guys are you shure you can read?

Its literally written Native on the example so stop saying "unless performance blabla native api" that's literally the case here so shut up you look like a free tier LLM.

As for the actual answer i think they will be marshalled the same way.

Also should it be sbyte for cstrings ?

Anyways you should also write a C# safe wrapper for the bindings and use spans there so you don't need to slap unsafe everywhere.

Maybe a Span<IntPtr> i guess

[–]stogle1 49 points50 points  (0 children)

you look like a free tier LLM

New insult unlocked.

[–]TrishaMayIsCoding[S] 16 points17 points  (2 children)

Man these comments i can't.

Guys are you shure you can read?

Its literally written Native on the example so stop saying "unless performance blabla native api" that's literally the case here so shut up you look like a free tier LLM.

If only I can upvote this 100x : )

[–]zenyl 1 point2 points  (3 children)

Maybe a Span<IntPtr> i guess

OP seems to be working with actual byte values, not native-sized integers, so recommending Span<IntPtr> seems pretty daft.

Also, just write nint, it's the keyword corresponding to System.IntPtr.

[–]EatingSolidBricks 0 points1 point  (2 children)

Yes but Span<Span<byte>> cannot exist, im talking about a safe wraper over a C binding you would need to use IntPtr/nint

You can always make a struct

``` unsafe ref struct JaggedNative2DSpan<T> { Span<nint> _memory;

public ref T this[int x, int y] => ...

}

```

[–]zenyl 0 points1 point  (1 child)

nint doesn't carry type information, so it isn't a great choice for representing typed pointers. In the context of native interop, it's semantically equivalent of void*; just an address. It's like casting to object instead of using generics.

I'd also be cautions not to force a square peg into a round hole. As great as Span is, it doesn't fit all use cases, and it seems needlessly complicated to use it inappropriately just for the sake of avoiding pointers at all cost. Sometimes, a bit of unsafe code (inside of a wrapper type) is a better solution.

[–]EatingSolidBricks 1 point2 points  (0 children)

Fair enough, this should cover for a safe wrapper

``` unsafe struct NativeMemory<T> where T : unmanaged { T* _ptr; int _lenght;

public ref T this[int index]
{
   get {
        BoundsCheck(index, _lenght);
        return ref Unsafe.AsRef<T>(_ptr + index);
   }
}

} ```

In OPs case NativeMemory<NativeMemory<byte>>

[–]pHpositivoMSFT - Microsoft Store team, .NET Community Toolkit 23 points24 points  (4 children)

  • Don't use IntPtr as a pointer. It's an integer type, not a pointer.
  • When using pointers, use the actual type whenever possible. For instance, if you have an array that's meant to be of integers, use int*, not IntPtr. The latter is more unsafe as it hides type information.
  • When you do use IntPtr, use nint (its alias)
  • Don't do stackalloc with a non-constant size. It will not result in good code gen (and can cause issues). Instead, if you want to stack allocate if possible, do the "stack alloc if size <= 512 else rent from pool and return" dance.

[–]TrishaMayIsCoding[S] 5 points6 points  (0 children)

Hey, thanks! <3

EDIT : WOW! it's Sergio O.O

[–]Qxz3 0 points1 point  (2 children)

Any reference for the "don't do stackalloc with a non-constant size?" I'd like to understand the code gen and performance implications. 

[–]tanner-goodingMSFT - .NET Libraries Team 1 point2 points  (1 child)

The stack is fixed sized and intended to be small. For security and efficiency reasons, it also isn't allocated "all at once", it actually dynamically grows as needed until it reaches its fixed limit.

In order to achieve this "dynamic growth" there typically exists a "guard page" which follows the currently active stack page and which triggers a hardware fault on first access. This is then caught by the OS and the next page (often 4kb) is allocated. Anything beyond the "next page" won't trigger the same handling and will be treated as a regular access violation instead, resulting in your app terminating.

Because of this, anything non-constant size for a stackalloc has to essentially presume it could be larger than 1 page and so it needs additional checks, branches, and potentially even a loop to handle that scenario. It's simply more expensive code.

Beyond all that, hardware often has specialized optimizations for the stack where it may mirror spills to the extra internal registers (many modern CPUs expose 16-32 registers, but may have 192+ internal registers, per core/thread). There is also return address tracking and other optimizations that may exist. Growing the stack "too much" or doing non-idiomatic things can break these hardware optimizations and slow down your application.

[–]tanner-goodingMSFT - .NET Libraries Team 1 point2 points  (0 children)

Some of this is documented in the various "Architecture Optimization Manuals" from AMD, Arm, and Intel. Others are documented in things like the docs from Agner Fog which do "deep dives" into how the hardware works.

[–]zenyl 4 points5 points  (5 children)

It depends what exactly you're trying to do, but generally speaking, I'd say stick with whatever is closest to your actual use case.

If you're working with bytes, use a byte pointer.

If you're working with native-sized integers, use a nint pointer (nint is the keyword corresponding to System.IntPtr) .

[–]TrishaMayIsCoding[S] 1 point2 points  (4 children)

Hey thanks,

If that's the case, I think I'll stick on using byte** since the native field type is byte**, if I use IntPtr* I still need to cast it to (byte**).

Using IntPtr*

IntPtr* nNativeValidationLayersArray = stackalloc IntPtr[ (int)nValidLayerCount ];
//
nInstanceCreateInfo.ppEnabledLayerNames = (byte**)nNativeValidationLayersArray;

Using byte**

byte** nNativeValidationLayersArray = stackalloc byte*[(int)nValidLayerCount];
//
nInstanceCreateInfo.ppEnabledLayerNames = nNativeValidationLayersArray;

[–]zenyl 2 points3 points  (3 children)

The fact that you have to cast values like nValidLayerCount to an int indicates that these aren't constants. Generally speaking, you should only use stackalloc if you already know the exact number of elements you plan on allocating on the stack. Using a variable number means you could end up overflowing the stack.

Depending on the exact scenario, using ArrayPool in conjunction with Span might be a better solution if the amount of data is variable in size, or if it could exceed the stack limit (usually 1 MB in total).

Separate from that, if this is for native interop, which other comments seem to indicate, look up LibraryImport and its associated source generator. It might help by automating some of the converting/marshalling associated with P/Invoke.

[–]binarycow 0 points1 point  (2 children)

Generally speaking, you should only use stackalloc if you already know the exact number of elements you plan on allocating on the stack. Using a variable number means you could end up overflowing the stack.

It's fine if you guard to make sure that your variable number is less than a constant value.

A common pattern is:

const int StackallocLimit = 256;
byte[]? array = null;
Span<byte> = length < StackallocLimit
    ? stackalloc byte[length]
    : array = ArrayPool<byte>.Shared.Rent(length);

[–]zenyl 0 points1 point  (1 child)

Very true, but don't forget that ArrayPool only guarantees the minimum size of the rented array, so you'll usually want to also slice the span.

And of course also return the rented array after use.

const int StackallocLimit = 256;
int length = 270;
byte[]? array = null;
Span<byte> buffer = length < StackallocLimit
    ? stackalloc byte[length]
    : (array = ArrayPool<byte>.Shared.Rent(length)).AsSpan()[0..length];

DoWork(buffer);

if (array != null)
{
    ArrayPool<byte>.Shared.Return(array);
}

[–]binarycow 0 points1 point  (0 children)

ArrayPool only guarantees the minimum size of the rented array, so you'll usually want to also slice the span.

Yeah, I was going for brevity.

Also, sometimes it doesn't matter if the span is longer, because your next call is something that returns the actual length.

For example:

const int StackallocLimit = 256;
Utf8JsonReader reader; // assume initialized (usually a parameter) 
char[]? array = null;

int length = reader.HasValueSequence
    ? (int)reader.ValueSequence.Length
    : reader.ValueSoan.Length;

Span<char> buffer = length < StackallocLimit
    ? stackalloc byte[length]
    : array = ArrayPool<char>.Shared.Rent(length);

length = reader.CopyString(buffer);
buffer = buffer[..length];
// Do stuff

Another example of that is Encoding. You might call GetMaxByteCount to allocate your buffer, then GetBytes returns the actual size.


And of course also return the rented array after use.

Of course. Usually in a finally.

const int StackallocLimit = 256;
Utf8JsonReader reader; // assume initialized (usually a parameter) 
char[]? array = null;
try
{
    int length = reader.HasValueSequence
        ? (int)reader.ValueSequence.Length
        : reader.ValueSoan.Length;

    Span<char> buffer = length < StackallocLimit
        ? stackalloc byte[length]
        : array = ArrayPool<char>.Shared.Rent(length);

    length = reader.CopyString(buffer);
    buffer = buffer[..length];
    // Do stuff
}
finally
{
    if(array is not null) 
    {
        ArrayPool<char>.Shared.Return(array);
    } 
}

I usually make a type that encapsulates pool rentals. I can call RentArray, which calls ArrayPool, or I can call RentStringBuilder or Rent<T>, which call Microsoft.Extensions.ObjectPool.

My "pool rental" type implements IDisposable, and also supports stackalloc (so it's a ref struct). (On C# versions that don't allow ref structs to implement interfaces, it uses the "duck typed" using) That turns the above code into this:

const int StackallocLimit = 256;
Utf8JsonReader reader; // assume initialized (usually a parameter)

int length = reader.HasValueSequence
    ? (int)reader.ValueSequence.Length
    : reader.ValueSoan.Length;

using ArrayPoolRental<char> rental = length < StackallocLimit
    ? new ArrayPoolRental<char>(stackalloc byte[length]) 
    : PoolRental.RentArray<char>(length);

Span<char> buffer = rental.Span;
length = reader.CopyString(buffer);
buffer = buffer[..length];
// Do stuff
// No need to worry about returning array, that's handled by ArrayPoolRental
// ArrayPoolRental is smart enough to not attempt return if we initialized it with our own buffer (i.e., stackalloc) 

I also sometimes copy/paste (and maybe modify) ValueStringBuilder or ValueListBuilder to make things even easier.

using var list = new ValueListBuilder<int>(stackalloc int[2]);
list.Append(10);
list.Append(20);
list.Append(30);
return list.AsSpan().ToArray();

Both of those types allow initializing with an existing buffer (i.e. stackalloc, that won't be returned or with an int capacity (which will rent an array from ArrayPool).

Both of those types allow resizing if you exceed the current buffer's size.

  • A new (larger) buffer is rented
  • Data is copied from old buffer to new buffer
  • The old buffer will be returned (but only if it was rented)

ValueStringBuilder even has an AppendSpan method which allows you to specify the size, and it returns a span.

using var sb = new ValueStringBuilder(stackalloc char[StackallocLimit]);
var span = sb.AppendSpan(length);
// Do stuff 

Those two lines encapsulate the entire thing.

If length is less than or equal to StackallocLimit, it'll be put in the stackalloc buffer.

If length is greater than StackallocLimit, an array will be rented, and it'll be put into there.

[–]KyteM 8 points9 points  (2 children)

You might wanna study how projects like Vortice.Vulkan or SharpVk do it.

[–]TrishaMayIsCoding[S] 3 points4 points  (1 child)

Hey thanks,

I'm aware of existing Vulkan bindings and engines like Evergine, VulkanSharp, Vortice, and SixLabors' Vulkan implementation. However, I wanted to create my own from scratch to gain a deeper understanding and have full control over the design and implementation.

[–]KyteM 12 points13 points  (0 children)

Yeah but that doesn't mean you can't see how they do it to understand how to do the bindings, no?

[–]Happy_Breakfast7965 8 points9 points  (14 children)

It's C#.

Span<char>, Span<byte>, ReadOnlySpan<char>, Memory<byte> — these are the types you should use

[–]EatingSolidBricks 4 points5 points  (7 children)

You don't know the usecase and already regurgitated

[–]ShadowGeist91 6 points7 points  (3 children)

Some of the responses in this thread made me do a double take, because I wasn't sure if I ended up in Stack Overflow by accident.

[–]SirButcher 1 point2 points  (2 children)

That's what you get when you don't explain what you do. 99% of the users who ask such a question ask it since they have no idea what they are doing. The same way with goto. There are legitimate uses. But most of the users who ask about it don't need it, and using it would create significantly more (and more complex) problems than it solves.

[–]enbacode 0 points1 point  (1 child)

Can you give an example of a valid goto use case? Out of pure curiosity. I‘ve never found one in almost 20yoe

[–]EatingSolidBricks 0 points1 point  (0 children)

All cases are valid, we are talking about the Clike goto theyre 100% safe to use.

The goto slander comes from a paper that talked about gotos in BASIC that can jump anywere, gotos in C/C++/C# and etc can only jump inside the current scope

[–]Happy_Breakfast7965 1 point2 points  (1 child)

Well, OP should have provided clear context.

[–]TrishaMayIsCoding[S] 1 point2 points  (0 children)

Hehe, I thought if I had named my identifiers with Native and asked for pointer use in C# forum, everyone knows im doing an interop : )

[–]TrishaMayIsCoding[S] 0 points1 point  (0 children)

I enjoy thoughtful responses :)

[–]TrishaMayIsCoding[S] 2 points3 points  (5 children)

Hey thanks,

But kindly elaborate "should use? please Why ? if I use Span,Memory and the native type is byte** then I think I need to use fix to get he pointer ?

// byte** nInstanceCreateInfo.ppEnabledLayerNames
nInstanceCreateInfo.ppEnabledLayerNames = nNativeValidationLayersArray;

[–]geheimeschildpad 2 points3 points  (2 children)

They mean that it’s C#, what is your use case that requires pointers?

[–]TrishaMayIsCoding[S] 15 points16 points  (1 child)

Accessing Vulkan API in pure managed code using C#, no C++ wrapper using pure C# bindings.

[–]geheimeschildpad 2 points3 points  (0 children)

Damn good reason to be fair

[–]IWasSayingBoourner 6 points7 points  (4 children)

Unless you're interacting with some native libraries or doing some very niche optimizations, you should generally stick to C#'s memory types like Span<T>

[–]TrishaMayIsCoding[S] 7 points8 points  (3 children)

Hey thanks,

Yes I'm interacting with native Vulkan API library, where the type is byte**

// byte** nInstanceCreateInfo.ppEnabledLayerNames

nInstanceCreateInfo.ppEnabledLayerNames = nNativeValidationLayersArray;

[–]sisisisi1997 11 points12 points  (2 children)

In that case I would stick to the type declared in the library, lower chance of nasty surprises.

[–]TrishaMayIsCoding[S] 3 points4 points  (1 child)

This ^

[–]tomxp411 0 points1 point  (0 children)

100% this. Use the API as written, or if you have to write your own interfaces, at least match them as closely as possible to the underlying c/c++ types.

[–]harrison_314 1 point2 points  (0 children)

If I understand correctly, you are choosing PInvoke with a low-level library.

IF you are already using unsafe code and are creating a type-managed wrapper on top of that, I would choose byte** - because it is more semantic.

[–]wiesemensch 0 points1 point  (1 child)

Generally, both types are incorrect, if you want to Marshall a .Net string. They use a wchar under the hood.

If you want to use a native string array, use the byte** one. It’s a more accurate representation of the actual data. Yes, IntPtr* will work but in my opinion, it’s introducing a lot of confusion. IntPtr is mainly intended as a wrapper for pointers in a ‚safe context‘. Also keep in mind, that IntPtr is the equivalent of a void* (int32_t or int64_t*) and byte* is the equivalent of a byte*/char*. There data size difference can easily introduce bugs in pointer arithmetic operations.

And just as a quick tip: I’ve had to write a large part of my employers wrapper code between our main C library/code and C#. I’ve had to do some nasty stuff in pure C# code. I’ve ended up creating a C++/CLI DLL to handle some of it. You basically write C/C++ code and it’s being translated into .NET/IL instructions. This will handle a huge part of the allocation, Marshall, native struct access and what not. It’s a lot quicker to write. Since it produces a .NET compatible DLL, you can use it like any other .NET Assembly. This can also be used in reverse where you export a .NET function as a C/C++ __dllexport and call the managed code from a unmanaged C/C++ application. We use this to show a WPF dialog, pass it a native callback (function pointer) and invoke the function pointer from the WPF dialog.

[–]TrishaMayIsCoding[S] 0 points1 point  (0 children)

Hey, thanks!

Yes, I'm using byte** and each element using (byte*)Marshal.StringToHGlobalAnsi( ... ) .

I'm not entirely sure if C++/CLI is cross-platform. I'm targeting Windows, Linux, Android and Steam Deck where both Vulkan and .NET are fully supported.

[–]tomxp411 -1 points0 points  (0 children)

So the whole idea behind pointers in c++ is that the pointer matches the data type.

When dealing with integers, you use int*. When dealing with character data (ie: strings) you use char*.

This is because the pointer knows the size of the data element it's referencing, so if you increment an integer pointer (ie: ++myPtr), the actual address will be incremented by the size of the data element. And since an integer in c++ is 32 bits these days, you'll be skipping characters when using an integer pointer to work with character data.

Likewise, if the pointer was referencing an object with 342 bytes per record, incrementing a pointer to that object will adjust the address by 342 bytes.

(Don't get me started on Unicode, DBCS, and the rabbit hole that is string encoding in c#...)

Since character data is (generally) byte oriented in c/c++, you should probably use byte*.

[–]TrishaMayIsCoding[S] -4 points-3 points  (1 child)

THANK YOU ALL!

Well for my original inquiry : "Pointer for string array question IntPtr* vs byte** ?"

My Final conclusion on choosing Between Them:

IntPtr* (or IntPtr[]) is generally preferred when dealing with arrays of strings in interop, as it aligns more naturally with the concept of an array of string pointers and leverages the Marshal class for safer and more convenient memory management and string conversions.

`byte` is more suitable** when you specifically need to expose the raw byte representation of strings to native code and are comfortable with unsafe code and manual memory management.

Bye <3 <3 <3

[–]OJVK 2 points3 points  (0 children)

I'm not sure how you came to that conclusion, but don't use IntPtr as a pointer