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

Using a managed buffer in native code after the call returns

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

Header 定义

// 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();
}

我 writing/reading 缓冲区不正确,还是我应该以不同的方式实现它?

是的,这无法按预期工作。 pinvoke 编组器具有有关 StringBuilder 的内置知识。当你正常使用它时,它首先固定底层缓冲区,因此当本机代码为 运行 时它不能移动。然后它调用本机代码,调用完成后它直接操作 StringBuilder 对象的成员,使其与本机字符串一致。

None 这种情况发生在你的情况下,你在缓冲区 pinvoke 编组器返回后聚会。换句话说,您正在写入一个地址不能保证稳定的缓冲区。这非常,非常 糟糕,调试损坏的 GC 堆非常无趣。在这种情况下你确实侥幸逃脱了,缓冲区太大了,下次你就不会这么幸运了。

当然,StringBuilder 的内部成员不会得到更新,得到一个空字符串是预期的结果。

您不应该这样做的主要原因是,您编写的 8 位 C 字符串字符(假定为 ANSI 编码)没有转换为 StringBuilder 存储的 utf-16 Unicode 代码点。换句话说,这根本不是优化,字符串总是需要被复制。您看到的数据损坏是其他代码重新使用以前由临时非托管缓冲区占用的内存的副作用。您还没有达到反过来破坏位图内存的地步。当然完全无法诊断。

你需要把这个扔掉。由于复制是不可避免的,您还不如让本机代码分配缓冲区。