在调用返回后在本机代码中使用托管缓冲区

时间:2015-02-02 17:47:42

标签: c# dll pinvoke

我有一个缓冲区,我在C#中分配并传递给本机代码。然后我保存一个指向该缓冲区的指针:

标题定义

// called when a meter is attached
typedef void(__cdecl * lpfnMeterAttachCallback)();

extern lpfnMeterAttachCallback frMeterAttachCallback;

// called when a meter is detached
typedef void(__cdecl * lpfnMeterDetachCallback)();

extern lpfnMeterAttachCallback frMeterDetachCallback;


// called when a meter is detached
typedef void(__cdecl * lpfnMeterReadCompleteCallback)();

extern lpfnMeterReadCompleteCallback frMeterReadCompleteCallback;

extern char* frbuffer;

主要

lpfnMeterAttachCallback frMeterAttachCallback;
lpfnMeterDetachCallback frMeterDetachCallback;
lpfnMeterReadCompleteCallback frMeterReadCompleteCallback;
char* frbuffer;

extern "C" __declspec(dllexport) void __cdecl InitDriver(
    lpfnMeterAttachCallback meterAttachCallback,
    lpfnMeterDetachCallback meterDetachCallback,
    lpfnMeterReadCompleteCallback meterReadCompleteCallback, char* buffer)
{
    frMeterAttachCallback = meterAttachCallback;
    frMeterDetachCallback = meterDetachCallback;
    frMeterReadCompleteCallback = meterReadCompleteCallback;
    frbuffer = buffer;
...
}

稍后在后台调用中,将其填写为:

JSONValue testSS = ss->GetJSON();
std::string help = *testSS;
strcpy(frbuffer, help.c_str());
if (frMeterReadCompleteCallback) frMeterReadCompleteCallback();

但是我的字符串变回空白,或者数据损坏。理想情况下,我希望将一个字符串传递给我的委托,然后使用它,但遇到了试图这样做的InterOp问题。

C#:

[DllImport("MyDLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void InitDriver(MeterAttachCallback meterAttachCallback,
        MeterDetachCallback meterDetachCallback,
        MeterReadCompleteCallback meterReadCompleteCallback, StringBuilder sb);

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void MeterAttachCallback();

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void MeterDetachCallback();


[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void MeterReadCompleteCallback();

static public void OnMeterAttach()
{
    Console.WriteLine("OnMeterAttach");
}

static public void OnMeterDetach()
{
    Console.WriteLine("OnMeterDetach");
}

static StringBuilder sb = new StringBuilder(10 * 1024 * 1024); //10MB

static public void OnMeterReadComplete()
{
    Console.WriteLine("OnMeterReadComplete; JSON:", sb.ToString());
}

static void Main(string[] args)
{
    InitApolloCppDriver(OnMeterAttach, OnMeterDetach, OnMeterReadComplete, sb);

    Console.ReadLine();
}

我是否错误地写入/读取缓冲区,还是应该以不同的方式实现此操作?

1 个答案:

答案 0 :(得分:3)

是的,这不能按预期工作。 pinvoke marshaller具有关于StringBuilder的内置知识。当您正常使用它时,它会首先固定底层缓冲区,以便在本机代码运行时无法移动。然后它调用本机代码,在调用完成后,它直接操作StringBuilder对象的成员,使其与本机字符串一起生成。

在你的情况下没有发生这种情况,你在的pinvoke marshaller返回后在缓冲区上聚会。换句话说,您正在写入缓冲区,其地址不能保证稳定。那非常非常坏,调试损坏的GC堆非常不合适。你碰巧在这种特殊情况下侥幸逃脱,缓冲区太大,下次你不会那么幸运。

当然,StringBuilder的内部成员不会更新,获取空字符串是预期的结果。

并且您不应该这样做的主要原因是,没有从您编写的8位C字符串字符(假设ANSI编码)到StringBuilder存储的utf-16 Unicode代码点进行转换。换句话说,这根本不是优化,总是需要复制字符串。您看到的数据损坏是其他代码重新使用以前由临时非托管缓冲区占用的内存的副作用。你没有达到一个点,你反过来又破坏了位图的记忆。当然完全不可识别。

你需要扔掉它。由于复制是不可避免的,您也可以让本机代码分配缓冲区。