-1

I'm trying to import some unmanaged ada code into C# and copy it into a byte array with Marshal Copy, however I am getting a System.AccessViolationException. Do you have any ideas why this is the case.

    [DllImport(@"Interpreter.dll", EntryPoint = "ReadErrors", CallingConvention = CallingConvention.Cdecl)]
    [return: MarshalAs(UnmanagedType.AsAny)]
    public static extern void ReadErrors(out IntPtr errors);
    
    static IntPtr _errors;

    static unsafe void Main(string[] args)                                                                                      
    {
        ReadErrors(out _errors);
        var managedArray = CopyToByteArrayWithMarshalCopy(_errors);
    }
    static byte[] CopyToByteArrayWithMarshalCopy(IntPtr errors)
    {
        byte[] managedArray = new byte[Marshal.SizeOf(errors)];      
        try
        {
            Marshal.Copy(errors, managedArray, 0, managedArray.Length);
        }
        catch
        {
            Marshal.FreeHGlobal(errors);
        }
        finally
        {
            Marshal.FreeHGlobal(errors);
        }
        return managedArray;
    }
kev0h1
  • 25
  • 5
  • So what is the signature of `ReadErrors` in the Ada library? And what kind of data are you actually trying to copy? The new array `managedArray` is initialized with a length equal to the size of the pointer `errors` itself (4 or 8 bytes). This is unlikely to be correct. – DeeDee Aug 25 '20 at 12:07
  • procedure Read_Errors_S (Error : Error_T); This is the signature were Error_T is an unsigned array char of 8 – kev0h1 Aug 25 '20 at 13:15
  • but well and truly the size of the array isnt the problem, because the Marshal copy would only do it until the length specified. – kev0h1 Aug 25 '20 at 13:29

2 Answers2

4

Given the limited information in the question regarding the Ada subprogram being called, and therefore assuming that the caller must allocate the character array (string buffer), the minimal example below works (based on the marshaling documentation here and the SO answer on building DLL libraries with GNAT here):

src/foo.ads

with Interfaces.C;

package Foo is

   package C renames Interfaces.C;   
   
   procedure Initialize
     with Export, Convention => C;
  
   procedure Finalize
     with Export, Convention => C;

   
   subtype Error_T is C.char_array (1 .. 8);
   
   procedure Read_Errors_S (Error : in out Error_T)
     with Export, Convention => C;

end Foo;

src/foo.adb

package body Foo is
   
   ----------------
   -- Initialize --
   ----------------
   
   procedure Initialize is
      procedure fooinit with Import;       --  Generated by binder.
   begin
      fooinit;
   end Initialize;

   --------------
   -- Finalize --
   --------------
   
   procedure Finalize is
      procedure foofinal with Import;      --  Generated by binder.
   begin
      foofinal;
   end Finalize;
   
   -------------------
   -- Read_Errors_S --
   -------------------
   
   procedure Read_Errors_S (Error : in out Error_T) is      
   begin      
      Error := C.To_C ("Error 1");      
   end Read_Errors_S;

end Foo;

foo.gpr

library project Foo is

   for Library_Kind use "dynamic";
   for Library_Name use "foo";
   for Library_Interface use ("foo");
   for Library_Auto_Init use "False";

   for Library_Dir use "lib";
   for Object_Dir use "obj";
   for Source_Dirs use ("src");

end Foo;

lib/libfoo.def

LIBRARY   LIBFOO
EXPORTS
    initialize
    finalize
    read_errors_s

Program.cs

using System;
using System.Runtime.InteropServices;
using System.Text;

namespace ConsoleApp
{
    internal static class LibFoo
    {
        [DllImport(@"libfoo.dll",
            EntryPoint = "initialize",
            CallingConvention = CallingConvention.Cdecl)]
        public static extern void Init();

        [DllImport(@"libfoo.dll",
            EntryPoint = "finalize",
            CallingConvention = CallingConvention.Cdecl)]
        public static extern void Final();

        [DllImport(@"libfoo.dll",
            EntryPoint = "read_errors_s",
            CallingConvention = CallingConvention.Cdecl)]
        public static extern void ReadErrors(StringBuilder error);
    }

    public static class Program
    {
        public static void Main()
        {
            LibFoo.Init();
            
            // Using StringBuilder to allocate a string buffer.
            var sb = new StringBuilder(8);
            LibFoo.ReadErrors(sb);
            Console.WriteLine(sb.ToString());

            LibFoo.Final();
        }
    }
}
DeeDee
  • 5,654
  • 7
  • 14
0

Turns out I just needed to pass a byte array into the function call and Marshal it as an LPArray.

    [DllImport("Interpreter.dll", EntryPoint = "ReadErrors", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
    public static extern void ReadErrors([Out, MarshalAs(UnmanagedType.LPArray)] byte [] errors);

    class Program
    {
        static void Main(string[] args)
        {
            byte[] errors = new byte[8];
            ReadErrors(errors);
        }
    }
kev0h1
  • 25
  • 5