Skip to content

Instantly share code, notes, and snippets.

@jack-pappas
Last active May 10, 2023 07:09
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jack-pappas/9725445 to your computer and use it in GitHub Desktop.
Save jack-pappas/9725445 to your computer and use it in GitHub Desktop.
Fixed-length arrays in F#
// Original code posted in fsharp-opensource discussion:
// https://groups.google.com/forum/#!topic/fsharp-opensource/pI73-GkoxbY
namespace Blah
open System.Runtime.InteropServices
[<Struct>]
type vec3_t =
val mutable X : int
val mutable Y : int
val mutable Z : int
[<Struct>]
[<StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)>]
type md3Frame_t =
[<MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)>]
val mutable bounds : vec3_t[];
val mutable localOrigin : vec3_t;
val mutable radius : float32;
[<DefaultValue>]
[<MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)>]
val name : string;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace FixedSizeBuffers
{
internal unsafe struct MyBuffer
{
public fixed char fixedBuffer[128];
}
internal unsafe class MyClass
{
public MyBuffer myBuffer = default(MyBuffer);
}
internal class Program
{
static void Main()
{
MyClass myC = new MyClass();
unsafe
{
// Pin the buffer to a fixed location in memory.
fixed (char* charPtr = myC.myBuffer.fixedBuffer)
{
*charPtr = 'A';
}
}
}
}
}
// This is an F# implementation which compiles to the same IL as C#'s "fixed buffers";
// or rather, would compile -- this code requires the 'fixed' construct, which isn't yet supported in F#.
// Vote to add 'fixed' support in F# here:
// http://fslang.uservoice.com/forums/245727-f-language/suggestions/5663721-add-support-for-fixed
namespace MyProgram
open System.Runtime.CompilerServices
open System.Runtime.InteropServices
#nowarn "9"
[<Struct>]
[<StructLayout(LayoutKind.Sequential, Size = 0x100)>]
[<UnsafeValueType>] // Comment this out for more speed, less safety
type FixedBufferInternal =
val mutable FixedElementField : char
[<Struct>]
type MyBuffer =
[<FixedBuffer(typeof<char>, 0x80)>]
val mutable fixedBuffer : FixedBufferInternal
type MyClass () =
// This is the "fixed length array"
[<DefaultValue>]
val mutable myBuffer : MyBuffer
module Program =
open Microsoft.FSharp.NativeInterop
[<EntryPoint>]
let main argv =
let myC = MyClass ()
// C# version uses 'fixed'; F# doesn't have fixed yet, and this code can't be approximated with GCHandle.
// 'charPtr' would have the type 'char*' or 'nativeptr<char>'
fixed charPtr = myC.myBuffer.fixedBuffer
// Use the functions in the NativePtr module to read/write the array elements.
NativePtr.set charPtr 0 'A'
NativePtr.set charPtr 1 'b'
// 'myC' is unpinned here when 'charPtr' goes out of scope
0 // Exit code
@abelbraaksma
Copy link

abelbraaksma commented Jun 5, 2020

Interesting examples!
FYI, your last example suggests the need for fixed, which is now supported (since F# 4.1): https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/fixed. Original proposal and implementation: https://github.com/fsharp/fslang-design/blob/master/FSharp-4.1/FS-1015-support-for-fixed.md.

The syntax would be something like (not tried):

// fixed can only be used with `use` and inside a function or member, and not inside scripts
use charPtr = fixed myC.myBuffer.fixedBuffer
    // scope

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment