【问题标题】:Marshal.AllocHGlobal is allocating more memory than expectedMarshal.AllocHGlobal 分配的内存超出预期
【发布时间】:2019-05-25 18:17:31
【问题描述】:

我正在使用Marshal.AllocHGlobal c# 方法为 IntPtr 分配内存,然后复制 byte[] 的包含:

byte[] clientIpAddr = System.Text.Encoding.ASCII.GetBytes("127.0.0.1")
IntPtr client_ipAddr_p = IntPtr.Zero;
ipAddress_p = Marshal.AllocHGlobal(clientIpAddr.Length);
Marshal.Copy(clientIpAddr, 0, ipAddress_p, clientIpAddr.Length);

然后我将指针传递给接收我的变量的 C++ 外部库,如下所示:

int open_socket(char *clientIpAddr_p)
{
    <open a socket>
}

根据AllocHGlobal 的定义,它最终可以保留比预期更多的内存(我可以保证,因为我现在正在忍受它)。问题是我需要我的IntPtr 具有我所要求的大小,因为我需要精确的 IP 以便之后打开套接字。

是否有更好的方法来分配外部内存以确保char * 拥有我的正确 IP?

【问题讨论】:

  • 不,不... char* 指针 clientIpAddr_p 指向的 C/C++ 文本需要以零结尾。以便 open_socket 函数可以正确确定字符串的长度。因此,您需要分配一个大小为 atleast 字符串长度 + 1 的缓冲区(可以分配更多),紧跟在字符串字符之后的 byte/char 是 \0 byte/字符。 (C/C++ 中的指针不携带有关它指向的数据的长度/大小的任何信息。)我想花一些时间学习 C/C++ 教程涵盖指针和零终止字符串将是最好的时间跨度>
  • 那么这就是我的问题的答案,谢谢!
  • 我不知道您是如何从 .NET 调用 C/C++ 代码的。如果您可以在 P/Invoking 来自 .NET 的本机 C/C++ 函数时直接将字符串作为参数传递,那么 .NET 编组器可以自动将 .NET 字符串转换为指向零的指针。终止的 C 字符串,省去了为 C/C++ 函数手动分配和填充字符/字符串缓冲区的麻烦,从而使您的代码既简单又可能更健壮...
  • @elgonzo 为什么不发布答案而不是 cmets?

标签: c# c++ memory-management


【解决方案1】:

Marshal.AllocHGlobal 分配的内存量大于指定的内存量不是问题。您的代码遇到了一个不同且很可能是致命的问题。

open_socket(char *clientIpAddr_p) 需要一个指向 C 风格 null-terminated string 的指针(也称为 NUL 终止字符串、零终止字符串或 ASCIIZ)。但是,您的 C# 代码会创建一个缓冲区并用字符串字符/字节填充它,而不用空字符终止它。这很可能会使 open_socket 函数在搜索内存(从指针给出的地址开始)以查找丢失的空字符时崩溃或以其他方式出现异常。

一个可能的解决方法相当简单:分配大小为clientIpAddr.Length + 1 的缓冲区,考虑终止空字符。零终止符(一个空字节)必须明确设置,因为 Marshal.AllocHGlobal 确实 not 用空字节初始化/填充分配的缓冲区。由于我们在这里处理的是 ASCII/ANSI 字符串,所以空字符只是一个值为 0 的字节。

byte[] clientIpAddr = System.Text.Encoding.ASCII.GetBytes("127.0.0.1")
IntPtr client_ipAddr_p = Marshal.AllocHGlobal(clientIpAddr.Length + 1);
Marshal.Copy(clientIpAddr, 0, ipAddress_p, clientIpAddr.Length);
Marshal.WriteByte(client_ipAddr_p, clientIpAddr.Length, 0);


好吧,忘记我刚刚写的代码。 ;-)

为什么?因为该问题有一个更简单的“一站式”解决方案,使您免于手动分配和填充 C 样式空终止字符串的缓冲区的麻烦:Marshal.StringToHGlobalAnsi 方法,它将转换您的 .NET将带有 IP 地址的字符串直接转换为相应的 C 风格以空字符结尾的 ANSI 字符串。

string ipAddress = "127.0.0.1";
IntPtr client_ipAddr_p = Marshal.StringToHGlobalAnsi(ipAddress);

(除非open_socket函数调用后需要clientIpAddr_p指向的字符串缓冲区继续存在,调用后使用Marshal.FreeHGlobal方法释放分配的缓冲区本机函数,无论缓冲区是使用 Marshal.AllocHGlobal 还是 Marshal.StringToHGlobalAnsi 分配的。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-12-02
    • 1970-01-01
    • 1970-01-01
    • 2012-06-23
    • 2020-05-31
    • 1970-01-01
    • 2013-10-23
    • 2018-10-22
    相关资源
    最近更新 更多