首页 > 解决方案 > 导入非托管 dll 并将指针复制到 c# 中的字节数组

问题描述

我正在尝试将一些非托管 ada 代码导入 C# 并使用 Marshal Copy 将其复制到字节数组中,但是我得到了 System.AccessViolationException。你有什么想法为什么会这样。

    [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;
    }

标签: marshallingadaunmanaged

解决方案


鉴于问题中有关被调用的 Ada 子程序的信息有限,因此假设调用者必须分配字符数组(字符串缓冲区),下面的最小示例有效(基于此处的编组文档和关于构建 DLL 库的 SO 答案这里有 GNAT ):

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

程序.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();
        }
    }
}

推荐阅读