1

我正在使用 DllImport 从我自己的 .net 类中调用 c 包装器库中的方法。c dll中的这个方法创建一个字符串变量并返回字符串的指针。

像这样的东西;

_declspec(dllexport) int ReturnString()
{
 char* retval = (char *) malloc(125);
 strcat(retval, "SOMETEXT");
 strcat(retval, "SOMETEXT MORE");
 return (int)retval;
}

然后我使用 Marshall.PtrToStringAnsi(ptr) 读取字符串。在获得字符串的副本后,我只需调用另一个 c 方法 HeapDestroy,它位于调用 free(ptr) 的 c 包装器库中。

这是问题;最近,虽然它像魅力一样工作,但我开始出现“尝试读取或写入受保护的内存区域”异常。经过更深入的分析,我发现,我相信,虽然我为此指针调用了 free 方法,但指针的值并没有被清除,这会在无人看管的情况下填充堆,并使我的 iis 工作进程抛出这个异常。顺便说一下,它是一个网站项目,在c库中调用了这个方法。

你能帮我解决这个问题吗?

当然,这是 C# 代码;

    [DllImport("MyCWrapper.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
    private extern static int ReturnString();

    [DllImport("MyCWrapper.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
    private extern static void HeapDestroy(int ptr);

    public static string GetString()
    {
        try
        {

            int i = ReturnString();
            string result = String.Empty;
            if (i > 0)
            {
                IntPtr ptr = new IntPtr(i);
                result = Marshal.PtrToStringAnsi(ptr);
                HeapDestroy(i);
            }

            return result;
        }
        catch (Exception e)
        {
            return String.Empty;
        }
    }
4

3 回答 3

7

可能的问题是底层的 C 代码。您没有向 strcat 依赖的字符串添加 NULL 终止符(或检查 malloc 的 NULL 返回)。在这种情况下很容易损坏内存。您可以通过执行以下操作来解决此问题。

retval[0] = '\0';
strcat(retval, "SOMETEXT");

问题的一部分还在于您在系统上耍花招。正确编写它并让系统在正确运行的代码上工作要好得多。第一步是修复本机代码以正确返回字符串。您需要考虑的一件事是,CLR(HGlobal 和 CoTask 分配)只能本地释放某些类型的内存。因此,让我们更改函数签名以返回 achar*并使用不同的分配器。

_declspec(dllexport) char* ReturnString()
{
 char* retval = (char *) CoTaskMemAlloc(125);
 retval[0] = '\0';
 strcat(retval, "SOMETEXT");
 strcat(retval, "SOMETEXT MORE");
 return retval;
}

然后,您可以使用以下 C# 签名并使用 Marshal.FreeCoTaskMem 释放 IntPtr。

[DllImport("SomeDll.dll")]
public static extern IntPtr ReturnString();

甚至更好。编组时,如果 CLR 认为它需要释放内存,它将使用 FreeCoTaskMem 来执行此操作。这通常与字符串返回有关。由于您使用 CoTaskMemAlloc 分配了内存,因此您可以节省编组 + 释放步骤并执行以下操作

[DllImport("SomeDll.dll", CharSet=Ansi)]
public static extern String ReturnString();
于 2009-09-30T15:54:10.987 回答
1

释放内存不会清除它,它只是释放它以便可以重复使用。一些调试版本会为您覆盖内存,以便更容易发现诸如 0xBAADFOOD 之类的值的问题

调用者应该分配内存,永远不要传回分配的内存:

_declspec(dllexport) int ReturnString(char*buffer, int bufferSize)
{
    if (bufferSize < 125) {
        return 125;
    } else {
        strcat(buffer, "SOMETEXT");
        strcat(buffer, "SOMETEXT MORE");
        return 0;
    }
}
于 2009-09-30T15:54:39.160 回答
0

尽管内存是由 DLL 在与您的应用程序相同的堆中分配的,但它可能使用不同的内存管理器,具体取决于它所链接的库。您需要确保您使用的是完全相同的库,或者在 DLL 代码本身中添加代码以释放 DLL 分配的内存。

于 2009-09-30T15:56:14.823 回答