August Feng

some fun interop between c and sharp

About

I wanted to be better.

Unmanaged Side

Let's create some C library:

#include <stdio.h>
#include <string.h>

void bar(void *b) {
  printf("Buffer received at address: %p\n", b);
  strcpy(b, "bonjour"); // XXX: yolo buffer overflow.
}

void baz(const char *s) {
  printf("string received: %s", s);
}

typedef int (*Operation)(int,int);

int qux(int a, int b, Operation op) {
  return op(a,b);
}

void x(char *s) {
  s[0] = 'x';
}

We can build the libfoo.dylib like this:

  cc -shared -o libfoo.dylib -fPIC foo.c

Managed Side

Let's write a F# application that calls the C library:

open System
open System.Runtime.InteropServices


module NativeMethods =
    [<DllImport("libfoo")>]
    extern void bar(void* b);

    [<DllImport("libfoo")>]
    extern void baz(string s);

    type Operation = delegate of int * int -> int

    [<DllImport("libfoo")>]
    extern int qux(int a, int b, Operation operation)

    [<DllImport("libfoo")>]
    extern void x(nativeint s)

module Call =
    let bar () =
        let bytes = "helloworld" |> System.Text.Encoding.UTF8.GetBytes
        let ptr = Marshal.UnsafeAddrOfPinnedArrayElement(bytes, 0)
        NativeMethods.bar ptr
        // let c = bytes.[0]
        printfn $"{bytes |> System.Text.Encoding.UTF8.GetString}"

    let baz () =
        let s = "foobar"
        NativeMethods.baz s

    let qux () =
        let add (a : int) (b : int) = a + b
        let n = NativeMethods.qux(1,2, add)
        printfn $"Computed: {n}"

    let x () =
        let s = "helloworld"
        let ptr = Marshal.StringToHGlobalAnsi(s)
        NativeMethods.x ptr
        let s' = Marshal.PtrToStringAnsi(ptr)
        printfn $"x'd: {s'}"

[<EntryPoint>]
let main _ =
    Call.bar ()
    Call.baz ()
    Call.qux ()
    Call.x ()
    0

Output

And for the curious ones! Here is the output.

Buffer received at address: 0x3000b04a0
bonjourld
Computed: 3
x'd: xelloworld
string received: foobar