性能——使用不安全的指针而不是IntPtr和Marshal

时间:2022-02-20 21:17:22

Question

I'm porting a C application into C#. The C app calls lots of functions from a 3rd-party DLL, so I wrote P/Invoke wrappers for these functions in C#. Some of these C functions allocate data which I have to use in the C# app, so I used IntPtr's, Marshal.PtrToStructure and Marshal.Copy to copy the native data (arrays and structures) into managed variables.

我正在将一个C应用程序移植到c#中。C应用程序从第三方DLL调用许多函数,所以我用c#为这些函数编写了P/Invoke包装器。其中一些C函数分配了我在c#应用中使用的数据,所以我使用了IntPtr, Marshal。PtrToStructure和元帅。复制以将本机数据(数组和结构)复制到托管变量中。

Unfortunately, the C# app proved to be much slower than the C version. A quick performance analysis showed that the above mentioned marshaling-based data copying is the bottleneck. I'm considering to speed up the C# code by rewriting it to use pointers instead. Since I don't have experience with unsafe code and pointers in C#, I need expert opinion regarding the following questions:

不幸的是,c#应用程序被证明比C版本慢得多。快速的性能分析表明,上述基于数据的数据复制是瓶颈。我正在考虑通过重写c#代码以使用指针来加快速度。由于我没有在c#中使用不安全代码和指针的经验,我需要对以下问题有专家意见:

  1. What are the drawbacks of using unsafe code and pointers instead of IntPtr and Marshaling? For example, is it more unsafe (pun intended) in any way? People seem to prefer marshaling, but I don't know why.
  2. 使用不安全的代码和指针而不是IntPtr和封送的缺点是什么?例如,它是否更不安全(双关语)?人们似乎更喜欢编组,但我不知道为什么。
  3. Is using pointers for P/Invoking really faster than using marshaling? How much speedup can be expected approximately? I couldn't find any benchmark tests for this.
  4. 对P/调用使用指针真的比使用封送更快吗?预期的加速率大约是多少?我找不到任何基准测试。

Example code

To make the situation more clear, I hacked together a small example code (the real code is much more complex). I hope this example shows what I mean when I'm talking about "unsafe code and pointers" vs. "IntPtr and Marshal".

为了使情况更清楚,我将一个小示例代码(真正的代码要复杂得多)合并在一起。我希望这个例子能说明我所说的“不安全代码和指针”vs。“IntPtr和元帅”。

C library (DLL)

MyLib.h

MyLib.h

#ifndef _MY_LIB_H_
#define _MY_LIB_H_

struct MyData 
{
  int length;
  unsigned char* bytes;
};

__declspec(dllexport) void CreateMyData(struct MyData** myData, int length);
__declspec(dllexport) void DestroyMyData(struct MyData* myData);

#endif // _MY_LIB_H_

MyLib.c

MyLib.c

#include <stdlib.h>
#include "MyLib.h"

void CreateMyData(struct MyData** myData, int length)
{
  int i;

  *myData = (struct MyData*)malloc(sizeof(struct MyData));
  if (*myData != NULL)
  {
    (*myData)->length = length;
    (*myData)->bytes = (unsigned char*)malloc(length * sizeof(char));
    if ((*myData)->bytes != NULL)
      for (i = 0; i < length; ++i)
        (*myData)->bytes[i] = (unsigned char)(i % 256);
  }
}

void DestroyMyData(struct MyData* myData)
{
  if (myData != NULL)
  {
    if (myData->bytes != NULL)
      free(myData->bytes);
    free(myData);
  }
}

C application

Main.c

c

#include <stdio.h>
#include "MyLib.h"

void main()
{
  struct MyData* myData = NULL;
  int length = 100 * 1024 * 1024;

  printf("=== C++ test ===\n");
  CreateMyData(&myData, length);
  if (myData != NULL)
  {
    printf("Length: %d\n", myData->length);
    if (myData->bytes != NULL)
      printf("First: %d, last: %d\n", myData->bytes[0], myData->bytes[myData->length - 1]);
    else
      printf("myData->bytes is NULL");
  }
  else
    printf("myData is NULL\n");
  DestroyMyData(myData);
  getchar();
}

C# application, which uses IntPtr and Marshal

Program.cs

Program.cs

using System;
using System.Runtime.InteropServices;

public static class Program
{
  [StructLayout(LayoutKind.Sequential)]
  private struct MyData
  {
    public int Length;
    public IntPtr Bytes;
  }

  [DllImport("MyLib.dll")]
  private static extern void CreateMyData(out IntPtr myData, int length);

  [DllImport("MyLib.dll")]
  private static extern void DestroyMyData(IntPtr myData);

  public static void Main()
  {
    Console.WriteLine("=== C# test, using IntPtr and Marshal ===");
    int length = 100 * 1024 * 1024;
    IntPtr myData1;
    CreateMyData(out myData1, length);
    if (myData1 != IntPtr.Zero)
    {
      MyData myData2 = (MyData)Marshal.PtrToStructure(myData1, typeof(MyData));
      Console.WriteLine("Length: {0}", myData2.Length);
      if (myData2.Bytes != IntPtr.Zero)
      {
        byte[] bytes = new byte[myData2.Length];
        Marshal.Copy(myData2.Bytes, bytes, 0, myData2.Length);
        Console.WriteLine("First: {0}, last: {1}", bytes[0], bytes[myData2.Length - 1]);
      }
      else
        Console.WriteLine("myData.Bytes is IntPtr.Zero");
    }
    else
      Console.WriteLine("myData is IntPtr.Zero");
    DestroyMyData(myData1);
    Console.ReadKey(true);
  }
}

C# application, which uses unsafe code and pointers

Program.cs

Program.cs

using System;
using System.Runtime.InteropServices;

public static class Program
{
  [StructLayout(LayoutKind.Sequential)]
  private unsafe struct MyData
  {
    public int Length;
    public byte* Bytes;
  }

  [DllImport("MyLib.dll")]
  private unsafe static extern void CreateMyData(out MyData* myData, int length);

  [DllImport("MyLib.dll")]
  private unsafe static extern void DestroyMyData(MyData* myData);

  public unsafe static void Main()
  {
    Console.WriteLine("=== C# test, using unsafe code ===");
    int length = 100 * 1024 * 1024;
    MyData* myData;
    CreateMyData(out myData, length);
    if (myData != null)
    {
      Console.WriteLine("Length: {0}", myData->Length);
      if (myData->Bytes != null)
        Console.WriteLine("First: {0}, last: {1}", myData->Bytes[0], myData->Bytes[myData->Length - 1]);
      else
        Console.WriteLine("myData.Bytes is null");
    }
    else
      Console.WriteLine("myData is null");
    DestroyMyData(myData);
    Console.ReadKey(true);
  }
}

6 个解决方案

#1


26  

It's a little old thread, but I recently made excessive performance tests with marshaling in C#. I need to unmarshal lots of data from a serial port over many days. It was important to me to have no memory leaks (because the smallest leak will get significant after a couple of million calls) and I also made a lot of statistical performance (time used) tests with very big structs (>10kb) just for the sake of it (an no, you should never have a 10kb struct :-) )

这是一个有点旧的线程,但是我最近在c#中进行了过多的性能测试。我需要在许多天内从一个串行端口中释放大量数据。我没有内存泄漏很重要(因为最小的泄漏后将得到显著的几百万调用),我也做了许多的统计性能(时间)使用测试非常大的结构(> 10 kb)只是为了它(不,你不应该有一个10 kb结构:-))

I tested the following three unmarshalling strategies (I also tested the marshalling). In nearly all cases the first one (MarshalMatters) outperformed the other two. Marshal.Copy was always slowest by far, the other two were mostly very close together in the race.

我测试了以下三种反编组策略(我还测试了编组)。在几乎所有的案例中,第一个(马歇尔事件)的表现都优于另外两个。元帅。到目前为止,拷贝总是最慢的,其他两个在比赛中几乎是非常接近的。

Using unsafe code can pose a significant security risk.

使用不安全的代码会带来严重的安全风险。

First:

第一:

public class MarshalMatters
{
    public static T ReadUsingMarshalUnsafe<T>(byte[] data) where T : struct
    {
        unsafe
        {
            fixed (byte* p = &data[0])
            {
                return (T)Marshal.PtrToStructure(new IntPtr(p), typeof(T));
            }
        }
    }

    public unsafe static byte[] WriteUsingMarshalUnsafe<selectedT>(selectedT structure) where selectedT : struct
    {
        byte[] byteArray = new byte[Marshal.SizeOf(structure)];
        fixed (byte* byteArrayPtr = byteArray)
        {
            Marshal.StructureToPtr(structure, (IntPtr)byteArrayPtr, true);
        }
        return byteArray;
    }
}

Second:

第二:

public class Adam_Robinson
{

    private static T BytesToStruct<T>(byte[] rawData) where T : struct
    {
        T result = default(T);
        GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned);
        try
        {
            IntPtr rawDataPtr = handle.AddrOfPinnedObject();
            result = (T)Marshal.PtrToStructure(rawDataPtr, typeof(T));
        }
        finally
        {
            handle.Free();
        }
        return result;
    }

    /// <summary>
    /// no Copy. no unsafe. Gets a GCHandle to the memory via Alloc
    /// </summary>
    /// <typeparam name="selectedT"></typeparam>
    /// <param name="structure"></param>
    /// <returns></returns>
    public static byte[] StructToBytes<T>(T structure) where T : struct
    {
        int size = Marshal.SizeOf(structure);
        byte[] rawData = new byte[size];
        GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned);
        try
        {
            IntPtr rawDataPtr = handle.AddrOfPinnedObject();
            Marshal.StructureToPtr(structure, rawDataPtr, false);
        }
        finally
        {
            handle.Free();
        }
        return rawData;
    }
}

Third:

第三:

/// <summary>
/// http://*.com/questions/2623761/marshal-ptrtostructure-and-back-again-and-generic-solution-for-endianness-swap
/// </summary>
public class DanB
{
    /// <summary>
    /// uses Marshal.Copy! Not run in unsafe. Uses AllocHGlobal to get new memory and copies.
    /// </summary>
    public static byte[] GetBytes<T>(T structure) where T : struct
    {
        var size = Marshal.SizeOf(structure); //or Marshal.SizeOf<selectedT>(); in .net 4.5.1
        byte[] rawData = new byte[size];
        IntPtr ptr = Marshal.AllocHGlobal(size);

        Marshal.StructureToPtr(structure, ptr, true);
        Marshal.Copy(ptr, rawData, 0, size);
        Marshal.FreeHGlobal(ptr);
        return rawData;
    }

    public static T FromBytes<T>(byte[] bytes) where T : struct
    {
        var structure = new T();
        int size = Marshal.SizeOf(structure);  //or Marshal.SizeOf<selectedT>(); in .net 4.5.1
        IntPtr ptr = Marshal.AllocHGlobal(size);

        Marshal.Copy(bytes, 0, ptr, size);

        structure = (T)Marshal.PtrToStructure(ptr, structure.GetType());
        Marshal.FreeHGlobal(ptr);

        return structure;
    }
}

#2


5  

Considerations in Interoperability explains why and when Marshaling is required and at what cost. Quote:

互操作性中的考虑解释了为什么和什么时候需要封送以及以什么代价进行封送。引用:

  1. Marshaling occurs when a caller and a callee cannot operate on the same instance of data.
  2. 当调用者和被调用者不能对相同的数据实例进行操作时,就会发生封送。
  3. repeated marshaling can negatively affect the performance of your application.
  4. 重复封送会对应用程序的性能产生负面影响。

Therefore, answering your question if

因此,回答你的问题如果

... using pointers for P/Invoking really faster than using marshaling ...

…使用指针进行P/调用比使用封送更快……

first ask yourself a question if the managed code is able to operate on the unmanaged method return value instance. If the answer is yes then Marshaling and the associated performance cost is not required. The approximate time saving would be O(n) function where n of the size of the marshalled instance. In addition, not keeping both managed and unmanaged blocks of data in memory at the same time for the duration of the method (in "IntPtr and Marshal" example) eliminates additional overhead and the memory pressure.

首先问自己一个问题,托管代码是否能够对非托管方法返回值实例进行操作。如果答案是肯定的,那么封送和相关的性能成本就不是必需的。节省时间的近似方法是O(n)函数,其中n为编组实例的大小。此外,在方法的持续时间(在“IntPtr和Marshal”示例中),不同时保存内存中的托管数据块和非托管数据块,消除了额外的开销和内存压力。

What are the drawbacks of using unsafe code and pointers ...

使用不安全的代码和指针有什么缺点……

The drawback is the risk associated with accessing the memory directly through pointers. There is nothing less safe to it than using pointers in C or C++. Use it if needed and makes sense. More details are here.

缺点是直接通过指针访问内存的风险。没有什么比在C或c++中使用指针更安全的了。如果需要,可以使用它。更多细节在这里。

There is one "safety" concern with the presented examples: releasing of allocated unmanaged memory is not guaranteed after the managed code errors. The best practice is to

上述示例中有一个“安全”问题:在托管代码错误之后,不能保证释放分配的非托管内存。最好的做法是

CreateMyData(out myData1, length);

if(myData1!=IntPtr.Zero) {
    try {
        // -> use myData1
        ...
        // <-
    }
    finally {
        DestroyMyData(myData1);
    }
}

#3


4  

Two answers,

两个答案,

  1. Unsafe code means it is not managed by the CLR. You need to take care of resources it uses.

    不安全代码意味着它不是由CLR管理的。你需要注意它使用的资源。

  2. You cannot scale the performance because there are so many factors effecting it. But definitely using pointers will be much faster.

    你不能衡量性能,因为影响性能的因素太多了。但是使用指针肯定会快得多。

#4


3  

Just wanted to add my experience to this old thread: We used Marshaling in sound recording software - we received real time sound data from mixer into native buffers and marshaled it to byte[]. That was real performance killer. We were forced to move to unsafe structs as the only way to complete the task.

我只是想把我的经验添加到这个旧的线程中:我们在录音软件中使用了封送——我们从混合器接收实时的声音数据到本地缓冲区中,并将其封送到byte[]。那才是真正的表演杀手。我们*转移到不安全的结构,作为完成这项任务的唯一途径。

In case you don't have large native structs and don't mind that all data is filled twice - Marshaling is more elegant and much, much safer approach.

如果您没有大型的本机结构体,并且不介意所有数据被填充两次,那么封送是一种更优雅、更安全的方法。

#5


3  

Because you stated that your code calls to 3rd-party DLL, I think the unsafe code is more suited in you scenario. You ran into a particular situation of wapping variable-length array in a struct; I know, I know this kind of usage occurs all the time, but it's not always the case after all. You might want to have a look of some questions about this, for example:

因为您声明您的代码调用了第三方DLL,所以我认为不安全的代码更适合您的场景。您遇到了一个特殊的情况,即在一个结构中处理变长数组;我知道,我知道这种用法经常出现,但它并不总是这样。你可能想看看关于这个的一些问题,例如:

How do I marshal a struct that contains a variable-sized array to C#?

如何将包含可变大小数组的结构体编组到c# ?

If .. I say if .. you can modify the third party libraries a bit for this particular case, then you might consider the following usage:

如果. .我说如果. .对于这种特殊情况,您可以稍微修改一下第三方库,然后您可以考虑以下用法:

using System.Runtime.InteropServices;

public static class Program { /*
    [StructLayout(LayoutKind.Sequential)]
    private struct MyData {
        public int Length;
        public byte[] Bytes;
    } */

    [DllImport("MyLib.dll")]
    // __declspec(dllexport) void WINAPI CreateMyDataAlt(BYTE bytes[], int length);
    private static extern void CreateMyDataAlt(byte[] myData, ref int length);

    /* 
    [DllImport("MyLib.dll")]
    private static extern void DestroyMyData(byte[] myData); */

    public static void Main() {
        Console.WriteLine("=== C# test, using IntPtr and Marshal ===");
        int length = 100*1024*1024;
        var myData1 = new byte[length];
        CreateMyDataAlt(myData1, ref length);

        if(0!=length) {
            // MyData myData2 = (MyData)Marshal.PtrToStructure(myData1, typeof(MyData));

            Console.WriteLine("Length: {0}", length);

            /*
            if(myData2.Bytes!=IntPtr.Zero) {
                byte[] bytes = new byte[myData2.Length];
                Marshal.Copy(myData2.Bytes, bytes, 0, myData2.Length); */
            Console.WriteLine("First: {0}, last: {1}", myData1[0], myData1[length-1]); /*
            }
            else {
                Console.WriteLine("myData.Bytes is IntPtr.Zero");
            } */
        }
        else {
            Console.WriteLine("myData is empty");
        }

        // DestroyMyData(myData1);
        Console.ReadKey(true);
    }
}

As you can see much of your original marshalling code is commented out, and declared a CreateMyDataAlt(byte[], ref int) for a coresponding modified external unmanaged function CreateMyDataAlt(BYTE [], int). Some of the data copy and pointer check turns to be unnecessary, that says, the code can be even simpler and probably runs faster.

正如您所看到的,原始的编组代码被注释掉了,并为修改后的外部非托管函数CreateMyDataAlt(byte[], ref int)声明了一个CreateMyDataAlt(byte[], ref int)。有些数据复制和指针检查是不必要的,也就是说,代码可以更简单,可能运行得更快。

So, what's so different with the modification? The byte array is now marshalled directly without warpping in a struct and passed to the unmanaged side. You don't allocate the memory within the unmanaged code, rather, just filling data to it(implementation details omitted); and after the call, the data needed is provided to the managed side. If you want to present that the data is not filled and should not be used, you can simply set length to zero to tell the managed side. Because the byte array is allocated within the managed side, it'll be collected sometime, you don't have to take care of that.

那么,这些修改有什么不同呢?字节数组现在被直接编组,而不会在结构中发生扭曲,并传递到非托管端。您不会在非托管代码中分配内存,而是只向其填充数据(省略实现细节);在调用之后,需要向托管方提供所需的数据。如果您想表示数据没有被填充并且不应该被使用,您可以简单地将长度设置为0,以告诉托管端。因为字节数组是在管理的端分配的,所以它会在某个时候被收集,你不必去处理它。

#6


2  

For anyone still reading,

对于任何仍然阅读,

Something I don't think I saw in any of the answers, - unsafe code does present something of a security risk. It's not a huge risk, it would be something quite challenging to exploit. However, if like me you work in a PCI compliant organization, unsafe code is disallowed by policy for this reason.

一些我认为我在任何答案中都没有看到的东西——不安全代码确实存在某种安全风险。这不是一个巨大的风险,它将是一个相当具有挑战性的开发。但是,如果您像我一样在一个符合PCI的组织中工作,由于这个原因,策略不允许使用不安全的代码。

Managed code is normally very secure because the CLR takes care of memory location and allocation, preventing you from accessing or writing any memory you're not supposed to.

托管代码通常非常安全,因为CLR负责内存位置和分配,阻止您访问或编写任何不应该访问的内存。

When you use the unsafe keyword and compile with '/unsafe' and use pointers, you bypass these checks and create the potential for someone to use your application to gain some level of unauthorized access to the machine it is running on. Using something like a buffer-overrun attack, your code could be tricked into writing instructions into an area of memory that might then be accessed by the program counter (i.e. code injection), or just crash the machine.

当您使用不安全关键字并使用'/不安全'进行编译并使用指针时,您绕过了这些检查,并为某人创建了使用您的应用程序以获得对正在运行的机器的某种程度的未授权访问的可能性。使用类似于缓冲区溢出攻击的方法,您的代码可能会被欺骗成将指令写入内存区域,然后程序计数器(例如代码注入)可能会访问该内存区域,或者直接导致机器崩溃。

Many years ago, SQL server actually fell prey to malicious code delivered in a TDS packet that was far longer than it was supposed to be. The method reading the packet didn't check the length and continued to write the contents past the reserved address space. The extra length and content were carefully crafted such that it wrote an entire program into memory - at the address of the next method. The attacker then had their own code being executed by the SQL server within a context that had the highest level of access. It didn't even need to break the encryption as the vulnerability was below this point in the transport layer stack.

许多年前,SQL server实际上是TDS包中恶意代码的牺牲品,这些恶意代码的传输时间比预期的要长得多。读取包的方法没有检查长度,而是继续将内容写入保留地址空间之后。额外的长度和内容是精心设计的,这样它就可以在下一个方法的地址将整个程序写到内存中。然后,攻击者在具有最高访问级别的上下文中由SQL服务器执行自己的代码。它甚至不需要打破加密,因为漏洞在传输层堆栈的这个点以下。

#1


26  

It's a little old thread, but I recently made excessive performance tests with marshaling in C#. I need to unmarshal lots of data from a serial port over many days. It was important to me to have no memory leaks (because the smallest leak will get significant after a couple of million calls) and I also made a lot of statistical performance (time used) tests with very big structs (>10kb) just for the sake of it (an no, you should never have a 10kb struct :-) )

这是一个有点旧的线程,但是我最近在c#中进行了过多的性能测试。我需要在许多天内从一个串行端口中释放大量数据。我没有内存泄漏很重要(因为最小的泄漏后将得到显著的几百万调用),我也做了许多的统计性能(时间)使用测试非常大的结构(> 10 kb)只是为了它(不,你不应该有一个10 kb结构:-))

I tested the following three unmarshalling strategies (I also tested the marshalling). In nearly all cases the first one (MarshalMatters) outperformed the other two. Marshal.Copy was always slowest by far, the other two were mostly very close together in the race.

我测试了以下三种反编组策略(我还测试了编组)。在几乎所有的案例中,第一个(马歇尔事件)的表现都优于另外两个。元帅。到目前为止,拷贝总是最慢的,其他两个在比赛中几乎是非常接近的。

Using unsafe code can pose a significant security risk.

使用不安全的代码会带来严重的安全风险。

First:

第一:

public class MarshalMatters
{
    public static T ReadUsingMarshalUnsafe<T>(byte[] data) where T : struct
    {
        unsafe
        {
            fixed (byte* p = &data[0])
            {
                return (T)Marshal.PtrToStructure(new IntPtr(p), typeof(T));
            }
        }
    }

    public unsafe static byte[] WriteUsingMarshalUnsafe<selectedT>(selectedT structure) where selectedT : struct
    {
        byte[] byteArray = new byte[Marshal.SizeOf(structure)];
        fixed (byte* byteArrayPtr = byteArray)
        {
            Marshal.StructureToPtr(structure, (IntPtr)byteArrayPtr, true);
        }
        return byteArray;
    }
}

Second:

第二:

public class Adam_Robinson
{

    private static T BytesToStruct<T>(byte[] rawData) where T : struct
    {
        T result = default(T);
        GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned);
        try
        {
            IntPtr rawDataPtr = handle.AddrOfPinnedObject();
            result = (T)Marshal.PtrToStructure(rawDataPtr, typeof(T));
        }
        finally
        {
            handle.Free();
        }
        return result;
    }

    /// <summary>
    /// no Copy. no unsafe. Gets a GCHandle to the memory via Alloc
    /// </summary>
    /// <typeparam name="selectedT"></typeparam>
    /// <param name="structure"></param>
    /// <returns></returns>
    public static byte[] StructToBytes<T>(T structure) where T : struct
    {
        int size = Marshal.SizeOf(structure);
        byte[] rawData = new byte[size];
        GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned);
        try
        {
            IntPtr rawDataPtr = handle.AddrOfPinnedObject();
            Marshal.StructureToPtr(structure, rawDataPtr, false);
        }
        finally
        {
            handle.Free();
        }
        return rawData;
    }
}

Third:

第三:

/// <summary>
/// http://*.com/questions/2623761/marshal-ptrtostructure-and-back-again-and-generic-solution-for-endianness-swap
/// </summary>
public class DanB
{
    /// <summary>
    /// uses Marshal.Copy! Not run in unsafe. Uses AllocHGlobal to get new memory and copies.
    /// </summary>
    public static byte[] GetBytes<T>(T structure) where T : struct
    {
        var size = Marshal.SizeOf(structure); //or Marshal.SizeOf<selectedT>(); in .net 4.5.1
        byte[] rawData = new byte[size];
        IntPtr ptr = Marshal.AllocHGlobal(size);

        Marshal.StructureToPtr(structure, ptr, true);
        Marshal.Copy(ptr, rawData, 0, size);
        Marshal.FreeHGlobal(ptr);
        return rawData;
    }

    public static T FromBytes<T>(byte[] bytes) where T : struct
    {
        var structure = new T();
        int size = Marshal.SizeOf(structure);  //or Marshal.SizeOf<selectedT>(); in .net 4.5.1
        IntPtr ptr = Marshal.AllocHGlobal(size);

        Marshal.Copy(bytes, 0, ptr, size);

        structure = (T)Marshal.PtrToStructure(ptr, structure.GetType());
        Marshal.FreeHGlobal(ptr);

        return structure;
    }
}

#2


5  

Considerations in Interoperability explains why and when Marshaling is required and at what cost. Quote:

互操作性中的考虑解释了为什么和什么时候需要封送以及以什么代价进行封送。引用:

  1. Marshaling occurs when a caller and a callee cannot operate on the same instance of data.
  2. 当调用者和被调用者不能对相同的数据实例进行操作时,就会发生封送。
  3. repeated marshaling can negatively affect the performance of your application.
  4. 重复封送会对应用程序的性能产生负面影响。

Therefore, answering your question if

因此,回答你的问题如果

... using pointers for P/Invoking really faster than using marshaling ...

…使用指针进行P/调用比使用封送更快……

first ask yourself a question if the managed code is able to operate on the unmanaged method return value instance. If the answer is yes then Marshaling and the associated performance cost is not required. The approximate time saving would be O(n) function where n of the size of the marshalled instance. In addition, not keeping both managed and unmanaged blocks of data in memory at the same time for the duration of the method (in "IntPtr and Marshal" example) eliminates additional overhead and the memory pressure.

首先问自己一个问题,托管代码是否能够对非托管方法返回值实例进行操作。如果答案是肯定的,那么封送和相关的性能成本就不是必需的。节省时间的近似方法是O(n)函数,其中n为编组实例的大小。此外,在方法的持续时间(在“IntPtr和Marshal”示例中),不同时保存内存中的托管数据块和非托管数据块,消除了额外的开销和内存压力。

What are the drawbacks of using unsafe code and pointers ...

使用不安全的代码和指针有什么缺点……

The drawback is the risk associated with accessing the memory directly through pointers. There is nothing less safe to it than using pointers in C or C++. Use it if needed and makes sense. More details are here.

缺点是直接通过指针访问内存的风险。没有什么比在C或c++中使用指针更安全的了。如果需要,可以使用它。更多细节在这里。

There is one "safety" concern with the presented examples: releasing of allocated unmanaged memory is not guaranteed after the managed code errors. The best practice is to

上述示例中有一个“安全”问题:在托管代码错误之后,不能保证释放分配的非托管内存。最好的做法是

CreateMyData(out myData1, length);

if(myData1!=IntPtr.Zero) {
    try {
        // -> use myData1
        ...
        // <-
    }
    finally {
        DestroyMyData(myData1);
    }
}

#3


4  

Two answers,

两个答案,

  1. Unsafe code means it is not managed by the CLR. You need to take care of resources it uses.

    不安全代码意味着它不是由CLR管理的。你需要注意它使用的资源。

  2. You cannot scale the performance because there are so many factors effecting it. But definitely using pointers will be much faster.

    你不能衡量性能,因为影响性能的因素太多了。但是使用指针肯定会快得多。

#4


3  

Just wanted to add my experience to this old thread: We used Marshaling in sound recording software - we received real time sound data from mixer into native buffers and marshaled it to byte[]. That was real performance killer. We were forced to move to unsafe structs as the only way to complete the task.

我只是想把我的经验添加到这个旧的线程中:我们在录音软件中使用了封送——我们从混合器接收实时的声音数据到本地缓冲区中,并将其封送到byte[]。那才是真正的表演杀手。我们*转移到不安全的结构,作为完成这项任务的唯一途径。

In case you don't have large native structs and don't mind that all data is filled twice - Marshaling is more elegant and much, much safer approach.

如果您没有大型的本机结构体,并且不介意所有数据被填充两次,那么封送是一种更优雅、更安全的方法。

#5


3  

Because you stated that your code calls to 3rd-party DLL, I think the unsafe code is more suited in you scenario. You ran into a particular situation of wapping variable-length array in a struct; I know, I know this kind of usage occurs all the time, but it's not always the case after all. You might want to have a look of some questions about this, for example:

因为您声明您的代码调用了第三方DLL,所以我认为不安全的代码更适合您的场景。您遇到了一个特殊的情况,即在一个结构中处理变长数组;我知道,我知道这种用法经常出现,但它并不总是这样。你可能想看看关于这个的一些问题,例如:

How do I marshal a struct that contains a variable-sized array to C#?

如何将包含可变大小数组的结构体编组到c# ?

If .. I say if .. you can modify the third party libraries a bit for this particular case, then you might consider the following usage:

如果. .我说如果. .对于这种特殊情况,您可以稍微修改一下第三方库,然后您可以考虑以下用法:

using System.Runtime.InteropServices;

public static class Program { /*
    [StructLayout(LayoutKind.Sequential)]
    private struct MyData {
        public int Length;
        public byte[] Bytes;
    } */

    [DllImport("MyLib.dll")]
    // __declspec(dllexport) void WINAPI CreateMyDataAlt(BYTE bytes[], int length);
    private static extern void CreateMyDataAlt(byte[] myData, ref int length);

    /* 
    [DllImport("MyLib.dll")]
    private static extern void DestroyMyData(byte[] myData); */

    public static void Main() {
        Console.WriteLine("=== C# test, using IntPtr and Marshal ===");
        int length = 100*1024*1024;
        var myData1 = new byte[length];
        CreateMyDataAlt(myData1, ref length);

        if(0!=length) {
            // MyData myData2 = (MyData)Marshal.PtrToStructure(myData1, typeof(MyData));

            Console.WriteLine("Length: {0}", length);

            /*
            if(myData2.Bytes!=IntPtr.Zero) {
                byte[] bytes = new byte[myData2.Length];
                Marshal.Copy(myData2.Bytes, bytes, 0, myData2.Length); */
            Console.WriteLine("First: {0}, last: {1}", myData1[0], myData1[length-1]); /*
            }
            else {
                Console.WriteLine("myData.Bytes is IntPtr.Zero");
            } */
        }
        else {
            Console.WriteLine("myData is empty");
        }

        // DestroyMyData(myData1);
        Console.ReadKey(true);
    }
}

As you can see much of your original marshalling code is commented out, and declared a CreateMyDataAlt(byte[], ref int) for a coresponding modified external unmanaged function CreateMyDataAlt(BYTE [], int). Some of the data copy and pointer check turns to be unnecessary, that says, the code can be even simpler and probably runs faster.

正如您所看到的,原始的编组代码被注释掉了,并为修改后的外部非托管函数CreateMyDataAlt(byte[], ref int)声明了一个CreateMyDataAlt(byte[], ref int)。有些数据复制和指针检查是不必要的,也就是说,代码可以更简单,可能运行得更快。

So, what's so different with the modification? The byte array is now marshalled directly without warpping in a struct and passed to the unmanaged side. You don't allocate the memory within the unmanaged code, rather, just filling data to it(implementation details omitted); and after the call, the data needed is provided to the managed side. If you want to present that the data is not filled and should not be used, you can simply set length to zero to tell the managed side. Because the byte array is allocated within the managed side, it'll be collected sometime, you don't have to take care of that.

那么,这些修改有什么不同呢?字节数组现在被直接编组,而不会在结构中发生扭曲,并传递到非托管端。您不会在非托管代码中分配内存,而是只向其填充数据(省略实现细节);在调用之后,需要向托管方提供所需的数据。如果您想表示数据没有被填充并且不应该被使用,您可以简单地将长度设置为0,以告诉托管端。因为字节数组是在管理的端分配的,所以它会在某个时候被收集,你不必去处理它。

#6


2  

For anyone still reading,

对于任何仍然阅读,

Something I don't think I saw in any of the answers, - unsafe code does present something of a security risk. It's not a huge risk, it would be something quite challenging to exploit. However, if like me you work in a PCI compliant organization, unsafe code is disallowed by policy for this reason.

一些我认为我在任何答案中都没有看到的东西——不安全代码确实存在某种安全风险。这不是一个巨大的风险,它将是一个相当具有挑战性的开发。但是,如果您像我一样在一个符合PCI的组织中工作,由于这个原因,策略不允许使用不安全的代码。

Managed code is normally very secure because the CLR takes care of memory location and allocation, preventing you from accessing or writing any memory you're not supposed to.

托管代码通常非常安全,因为CLR负责内存位置和分配,阻止您访问或编写任何不应该访问的内存。

When you use the unsafe keyword and compile with '/unsafe' and use pointers, you bypass these checks and create the potential for someone to use your application to gain some level of unauthorized access to the machine it is running on. Using something like a buffer-overrun attack, your code could be tricked into writing instructions into an area of memory that might then be accessed by the program counter (i.e. code injection), or just crash the machine.

当您使用不安全关键字并使用'/不安全'进行编译并使用指针时,您绕过了这些检查,并为某人创建了使用您的应用程序以获得对正在运行的机器的某种程度的未授权访问的可能性。使用类似于缓冲区溢出攻击的方法,您的代码可能会被欺骗成将指令写入内存区域,然后程序计数器(例如代码注入)可能会访问该内存区域,或者直接导致机器崩溃。

Many years ago, SQL server actually fell prey to malicious code delivered in a TDS packet that was far longer than it was supposed to be. The method reading the packet didn't check the length and continued to write the contents past the reserved address space. The extra length and content were carefully crafted such that it wrote an entire program into memory - at the address of the next method. The attacker then had their own code being executed by the SQL server within a context that had the highest level of access. It didn't even need to break the encryption as the vulnerability was below this point in the transport layer stack.

许多年前,SQL server实际上是TDS包中恶意代码的牺牲品,这些恶意代码的传输时间比预期的要长得多。读取包的方法没有检查长度,而是继续将内容写入保留地址空间之后。额外的长度和内容是精心设计的,这样它就可以在下一个方法的地址将整个程序写到内存中。然后,攻击者在具有最高访问级别的上下文中由SQL服务器执行自己的代码。它甚至不需要打破加密,因为漏洞在传输层堆栈的这个点以下。