如何在访问DLL时确保代码的安全?

3

要在C中返回数组,需要使用指针。为了澄清我的问题,我创建了一个简单的.c函数,并编译成DLL,并通过C#访问它。C函数如下:

#include <stdio.h>

__declspec(dllexport) int* ReturnArray(int size) {

    int* arr = malloc(size * sizeof(int)); 

    arr[0] = 19;
    arr[1] = 18;
    arr[2] = 17;
    arr[3] = 16;
    arr[4] = 15;

    return arr;
}

在C#端:

namespace ConsoleC2CSExample
{
    class Program
    {
        [DllImport("array.dll", EntryPoint = "ReturnArray", CallingConvention = CallingConvention.Cdecl)]
        public unsafe static extern short* ReturnArray(int size);
        static unsafe void Main(string[] args)
        {
            int arraySize = 5;

            short *ptr = ReturnArray(arraySize);
            
            for(int i = 0; i < arraySize; i++)
                Console.WriteLine(*(ptr + i * sizeof(short)));              

            Console.Read();
        }
    }
}

输出结果如下所示:

enter image description here

我有两个问题。

  1. 不使用unsafe模式,如何完成此操作?
  2. 如果使用unsafe,会有什么危险?

如果你将该函数导入为 public static extern int[] ReturnArray(int size); (或者根据字宽度而定为public static extern short[] ReturnArray(int size);),会发生什么? - Mathias R. Jessen
然后我得到了编译错误:“指针和固定大小缓冲区只能在不安全的上下文中使用” - GNZ
现在,C#代码可以直接传递自己的数组,不需要指针,也不会出现内存泄漏问题。函数原型为:int ReturnArray(int[] array, int size)。 - Hans Passant
2
你不能将使用malloc(或new)分配的内存从c dll传递到c#,并期望其生命周期得到管理 - 内存由dll本地的内存管理器跟踪,而CLR不知道如何管理或释放它。 - 500 - Internal Server Error
1
如果您需要在DLL内分配一个数组并将其传递给托管代码,则需要使用可供双方访问的分配器函数(以便托管代码可以释放数组)。在这些情况下,最简单的解决方案是在托管代码中分配数组,将其传递给DLL以填充数据,然后稍后在托管代码中释放数组 - 没有跨越托管/非托管边界的分配。 - xxbbcc
显示剩余3条评论
1个回答

0

C#/.NET 被称为托管代码,因为它在公共语言运行时(CLR)上安全运行。而 C 代码被称为非托管代码。

CLR 确保我们不会遭受来自 C 代码的典型问题(如缓冲区下溢、缓冲区溢出等),从而导致易受攻击的代码、应用程序崩溃、未知行为等问题。

因此,使用不安全代码的危险在于,您的 C 库中的错误可能会意外终止整个 C#/.NET 应用程序,给您带来很多麻烦。

为了至少摆脱 C# 代码中的不安全上下文,您可以尝试以下方法:

using System.Runtime.InteropServices;

namespace ConsoleC2CSExample
{
    class Program
    {
        [DllImport("array.dll", EntryPoint = "ReturnArray", CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr ReturnArray(int size);

        static void Main(string[] args)
        {
            int arraySize = 5;

            IntPtr ptr = ReturnArray(arraySize);
            
            for(int i = 0; i < arraySize; i++)
                Console.WriteLine(Marshal.ReadInt32(ptr + i * sizeof(int)));

            Console.Read();
        }
    }
}

我认为应该是 Console.WriteLine(Marshal.ReadInt16(ptr + i * sizeof(int))); - GNZ
我认为你的解决方案可行。那么,对于 Console.WriteLine((ptr->GetType()).ToString()); 这种情况下如何使用 Marshal? - GNZ
另一个陷阱。我们需要非常小心地处理大小。在C中,int的宽度为4个字节,因此我们还必须分别使用intInt32在C#中 => Marshal.ReadInt32(ptr + i * sizeof(int)) - Gerald Mayr
@GNZ,我不理解(ptr->GetType()).ToString()的意思。你期望的输出是什么? - Gerald Mayr
我只是想看看我们是否也可以修改ptr->。您已经将指针与IntPtr交换,例如*ptr变为IntPtr ptr。我只想知道如果我们遇到并需要使用->运算符,如何在安全情况下修改和使用ptr->。我发现这个->运算符在C中指向寄存器的结构体中使用最多。我将收到一个C API,并希望使用C#。这就是为什么我正在尝试进入它。我可能需要通过.NET C#使用一些C API函数。 - GNZ
@GNZ 请查看 https://learn.microsoft.com/zh-cn/cpp/dotnet/using-explicit-pinvoke-in-cpp-dllimport-attribute 获取有关Interop的更多详细信息。 - Gerald Mayr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接