2

我目前正在探索 C# 中的 DLL 导出函数和 P/invoke。我创建了非常简单的 .dll:

测试.h

#ifndef TEST_DLL_H
#define TEST_DLL_H
extern "C" __declspec(dllexport) const char * __cdecl hello ();
extern "C" __declspec(dllexport) const char * __cdecl test ();    
#endif  // TEST_DLL_H

测试.cpp

#include <stdlib.h>
#include "test.h"
#include <string.h>

const char* hello()
{
    char *novi = (char *)malloc(51);
    strcpy(novi, "Test.");

    return novi;
}

const char * test()
{
    return "Test.";
}

我已经编译它并在 C# 项目中使用,如下所示:

    [DllImport("test.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern IntPtr hello();

    [DllImport("test.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern string test();

    private void button1_Click(object sender, EventArgs e)
    {
        MessageBox.Show(test());
        IntPtr a =  hello();
        MessageBox.Show(Marshal.PtrToStringAnsi(a));
    }

但它不起作用。test()被成功调用,我得到了正确的字符串。但hello()只是挂断程序。如果我从hello()定义中删除 malloc 行并返回常量,一切正常,所以我想我现在知道 malloc 存在问题。

另外,我在某处看到返回类型为 char* 时不应使用该字符串。如果这是真的,我们为什么要使用 IntPtr?

4

1 回答 1

3

跨 DLL 边界返回字符串的函数很难从 C 或 C++ 可靠地调用,当你从 C# 中调用时,它并没有变得更好。问题在于调用者将如何释放字符串缓冲区。hello() 需要这样做,而 test() 则不需要。很难猜到的东西。hello() 函数需要使用 free() 函数,使用与调用 malloc() 完全相同的分配器只有当 DLL 和调用者共享相同的 CRT 实现时,这才有效。可能性很小。

pinvoke marshaller 也释放字符串缓冲区,它必须这样做。并且使用唯一合理的选择 CoTaskMemFree()。它使用 COM 使用的默认分配器。这不是一个好的结局,您的 C 代码没有使用 CoTaskMemAlloc()。这可能的结果取决于操作系统。在 Vista 及更高版本上,您的程序将因 AccessViolation 而死,这些 Windows 版本使用严格的堆分配器,旨在使行为不端的程序崩溃。在 XP 上,您会遇到内存泄漏和堆损坏之间的问题,听起来像是您选择了第二个选项。

将返回值声明为 IntPtr 将是一个好的结局。好吧,您的程序不会崩溃,您仍然有无法插入的内存泄漏。没有办法可靠地调用 free() 。或者在您的 C 代码中使用 CoTaskMemAlloc() 以便 pinvoke 编组器的发布调用将起作用。

但实际上,不要像这样编写 C 代码。始终使用调用者分配的内存,因此永远不会猜测谁拥有内存。这需要类似于以下的函数签名:

extern "C" __declspec(dllexport) 
int hello(char* buffer, int bufferSize);
于 2012-07-08T19:47:02.567 回答