I just spent the day figuring out how to Marshal arrays of bytes between C# and 
Nim (C# calling a DLL written in Nim) and wanted to share what I came up with 
can give people the chance to correct me. For some context, this is a Nim 
library that can be used natively or compiled to a DLL, the code presented will 
be an exported DLL function.

One of the Nim exports I have written looks like this:
    
    
    proc transmitAndReceive(i2cAddress: byte, txdata: ptr UncheckedArray[byte], 
rxdata: var ptr UncheckedArray[byte], txlen, rxlen: int ): MyEnum {.exportc, 
dynlib, stdcall.} =
        let convertedTx = @(toOpenArray(txdata, 0, txlen))
        var convertedRx = newSeq[byte](rxlen)
        result = myModule.transmitAndReceive(i2cAddress, convertedTx, 
convertedRx)
        if rxlen > 0 and convertedRx.len >= rxlen:
          rxdata = cast[ptr UncheckedArray[byte]](CoTaskMemAlloc(rxlen))
          copyMem(rxdata, convertedRx[0].addr, rxlen)
    
    
    Run

  * For the unfamiliar I believe CoTaskMemAlloc is how .net allocs and frees 
for "unmanaged" code and possibly for managed code.
  * CoTaskMemAlloc WILL alloc a 0 length block and return a ptr to it if its 
argument is 0.
  * Returns a pointer to a block of memory if successful and null if 
unsuccessful.



`proc CoTaskMemAlloc(n: int): pointer {.importc, dynlib:"ole32.dll".}`'

The C# code for using the DLL:
    
    
    [DllImport(dllname, EntryPoint = "transmitAndReceive", CallingConvention = 
CallingConvention.StdCall)]
    public static extern MyEnum TransmitAndReceive(
      byte i2cAddress,
      [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U8, 
SizeParamIndex = 3)] byte[] txData,
      [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U8, 
SizeParamIndex = 4)] out byte[] rxData,
      int txlen, int rxlen);
    
    
    Run

  * Yes StdCall is the default calling convention (Winapi) but I prefer to 
specify it.
  * I have to specify the `EntryPoint` because of style reasons, I could likely 
make a macro that fixes this automatically but it changes from language to 
language anyway.
  * The Marshal class tells the program how to pass data around, how to Marshal 
it. In this example I am saying that `txData` and `rxData` are C style arrays, 
which if im not wrong is the same as Nim's `ptr UncheckedArray[T]` and 
`ArraySubType` declares the type of the values of the array, in this case an 
unsigned int8, `SizeParamIndex` tells the Marshaller what field of the call 
indicates the length of the array, here that is `txlen` and `rxlen`.
  * The `out` keyword indicates that the called code is responsible for 
initialization.



So how do I use this?
    
    
    byte[] getacq = new byte[] { 3 }; // Tells the thing I'm talking to, to 
return the data from channel 3
    byte[] recvacq; // Where I will receive the data, Null here because no 
initialization or assignment.
    TransmitAndReceive(0x42, getacq, out recvacq, getacq.Length, 5);
    
    
    Run

  * 0x42 is the device address.



I pass the data I want to send (`getacq`) which is then Marshalled as a C style 
array that has length `getacq.length` (1). My Nim DLL then uses that 
information to create a `Seq` that is processed and transmitted, after all that 
happens the DLL allocs a new block of memory the size of `rxlen`, then copies 
that data from `convertedRx` which contains the response from the device in 
question. This should be safe because the `Seq` I make is of length `rxlen` as 
is the block of memory I alloc, so `copyMem` can not read past or overflow and 
I do not alloc if `convertedRx` got smaller in the call chain. (There is code 
missing that does Null checking for the alloc and returns an error). Once the 
call returns the Marshaller will convert the C style array to a .net array 
using the `SizeParamIndex` and `recvacq` becomes a valid not Null array, in the 
case of this code and array of len 5 with the first byte being a status and the 
next 4 a float32. Additionally when deemed appropriate .net will dealloc the 
array which I accidentally proved to myself with a typo. 

Reply via email to