【问题标题】:How to manage a buffer between C and C#如何管理 C 和 C# 之间的缓冲区
【发布时间】:2012-04-30 19:08:17
【问题描述】:

问题

我有一个通过 System.Runtime.Interop 调用 C 函数的 C# 脚本。我设法调用了 C 函数,但在管理 C 和 C# 之间的缓冲区时遇到问题。

在我的情况下,C 是(数据)生产者,C# 是消费者。

我的问题是当我在 C# 中读取数据时,有时我得到正确的值,但有时我得到 NULL。

这个问题已经解决了。我将我的错误方法和正确方法粘贴在这里与您分享。

背景

C# 代码是一个统一脚本(Mono 开发的一部分),C 代码在 Xcode 中,这意味着我不能在我的 C 代码中使用 .Net 框架函数。

错误的方法(不时给我NULL)

这是我的 C 代码(写入缓冲区并从缓冲区读取):

static char InteropBF[INTEROP_BUFFER_SIZE];
static int mutex = 0;
// for the c code to put its message in buffer
void PutToBuffer(char* name, char* content)
{
    while (mutex>0);
    mutex++;
    strcat(InteropBF, name);
    strcat(InteropBF, ",");
    strcat(InteropBF, content);
    strcat(InteropBF, ",");
    printf("Interop Buffer: %s\n", InteropBF);
    mutex--;
}


// for the C# code to poll and read from C
void* ReadFromBuffer(void* temp)
{
    while (mutex>0);
    mutex++;
    strcpy(temp, InteropBF);
    // memcpy(temp, InteropBF, INTEROP_BUFFER_SIZE);
    strcpy(InteropBF, "");
    mutex--;
    return temp;
}

我将函数 ReadFromBuffer() 暴露给 C#:

[DllImport ("CCNxPlugin")]
public static extern IntPtr ReadFromBuffer(IntPtr temp);

然后,我这样调用函数:

        IntPtr temp = Marshal.AllocCoTaskMem(8912);
        CCN.ReadFromBuffer(temp);
        string news = Marshal.PtrToStringAuto(temp);
        if(news != "")
        {
            print (news);
        }
        Marshal.FreeCoTaskMem(temp);

使用此代码有时我会获得正确的缓冲区内容,但更频繁地我会从 Marshal.PtrToStringAuto 函数中获得 NULL。

正确的方法(非常感谢大家!)

我想粘贴我在此处找到的工作代码和参考资料 --

C 函数:

struct bufnode
{
    char* name;
    char* content;
    struct bufnode *next;
};

struct bufnode* bufhead = NULL;
struct bufnode* buftail = NULL;
// for the c code to put its message in buffer
void PutToBuffer(char* name, char* content)
{
    struct bufnode *temp = malloc(sizeof(struct bufnode));
    temp->name = malloc(256);
    temp->content = malloc(256);
    strcpy(temp->name,name);
    strcpy(temp->content,content);
    temp->next = NULL;

    if (bufhead == NULL && buftail == NULL) {
        bufhead = temp;
        buftail = temp;
    }
    else if(bufhead != NULL && buftail != NULL){
        buftail->next = temp;
        buftail = temp;
    }
    else {
        printf("Put to buffer error.\n");
    }    
}

// for the C# code to poll and read from C
struct bufnode* ReadFromBuffer()
{
    if (bufhead != NULL && buftail != NULL) {
        // temp->name = bufhead->name;
        // temp->content = bufhead->content;
        // temp->next = NULL;
        struct bufnode* temp = bufhead;
        if (bufhead == buftail) {
            bufhead = NULL;
            buftail = NULL;
        }
        else {
            bufhead = bufhead->next;
        }

        return temp;
    }
    else if(bufhead == NULL && buftail == NULL)
    {
        return NULL;
    }
    else {
        return NULL;
    }
}

C# 包装器:

[StructLayout (LayoutKind.Sequential)]
public struct bufnode 
{
    public string name;
        public string content;
        public IntPtr next;
}


[DllImport ("CCNxPlugin")]
public static extern IntPtr ReadFromBuffer();

在 C# 中调用函数:

        CCN.bufnode BufNode;
        BufNode.name = "";
        BufNode.content = "";
        BufNode.next = IntPtr.Zero;

        IntPtr temp = CCN.ReadFromBuffer();
        if(temp != IntPtr.Zero)
        {
            BufNode = (CCN.bufnode)Marshal.PtrToStructure(temp, typeof(CCN.bufnode));
            print(BufNode.name);
            print(BufNode.content);
            Marshal.FreeCoTaskMem(temp);
        }

总结

  1. char[] 似乎不是 C 和 C# 之间的良好缓冲区(至少在我使用 Unity-Mono 和 Xcode 的情况下)。我的建议是在结构中组织数据并将结构作为参数或作为返回值传递给 C#。我找到了关于传递类和结构的很好的文档,但我还没有找到任何关于单独传递 char 数组的信息。所以我想将 char[] 包装在结构或类中总是更好。
  2. 可以将 C 结构封送为 C# 类或 C# 结构。将包装 class 作为参数传递给非托管函数将起作用。将包装 struct 作为参数传递给非托管函数也可以。从非托管函数返回一个指向 struct 的指针是可以的。从非托管函数返回一个指向的指针是好的。 (找到了一个很棒的教程:http://www.mono-project.com/Interop_with_Native_Libraries#Summary

【问题讨论】:

  • 请同时发布相关的C和C#代码。
  • 是的,太笼统了,什么都可以
  • @vcsjones 抱歉,我刚刚编辑并发布了我的代码
  • 您可能想寻找更好的线程同步方法。循环直到 mutex 大于 0 将使用 100% 的 CPU,直到 mutex 大于 0。如果你有一个 CPU,你可能会出现挂起。
  • @PeterRitchie 谢谢彼得,这只是一个早期的演示,所以我非常关心获得正确的结果。我想在我得到正确的缓冲区值之后,我肯定会使用其他方式而不是忙着等待。现在我觉得编码很容易:)

标签: c# xcode memory-management interop unity3d


【解决方案1】:

您正在传回一个堆栈变量。该变量可以在从方法返回时在 C/C++ 中“收集”或释放——这将在它到达 PtrToStringAuto 时随机导致内存损坏。这可以解释为什么它有时为空。您需要分配传回 C# 代码的内存,并且该代码需要释放内存(或者 C/C++ 需要以某种方式执行此操作)。

您可以在 c/c++ 中使用 CoTaskMemAlloc 执行此操作,在 C# 中使用 Marshal.FreeCoTaskMem 释放它。

【讨论】:

  • 谢谢!所以我的 InteropBF[] 被释放了?或者 ReadFromBuffer() 中的 temp[] 正在被释放?还是两者兼而有之?
  • temp 正在被释放(嗯,它更像是被重复使用)
  • 好的,谢谢!我将尝试使用临时的 CoTaskMemAlloc,稍后再回复您!
  • 不不,我做不到。我不能调用 CoTaskMemAlloc。也许是因为我使用的是 Xcode?我可以在 Xcode 中使用 .Net 框架吗?还是有解决办法?
  • XCode? C# 部分是在 Mono 中完成的吗?如果包含在您的问题中,那将是非常有用的信息...
【解决方案2】:

尝试传入一个 c# 字符串类型并在 C++ 中使用SysAllocString

// for the C# code to poll and read from C
void ReadFromBuffer(BSTR* pstrRet)
{
    while (mutex>0);
    mutex++;

    *pstrRet=SysAllocString(InteropBF)

    strcpy(InteropBF, "");
    mutex--;
}

//calling from c# 
string news ="";
ReadFromBuffer(out news);
if(news != "")
{
    print (news);
}


否则,您可以尝试在 c# 中为您的返回值创建一个足够大小的字节数组,并将指向内存的底层指针传递给您的 xcode。像这样:

// for the C# code to poll and read from C
unsigned int ReadFromBuffer(char* pstrRet, unsigned int cbSize)
{
    unsigned int uiRet;

    while (mutex>0);
    mutex++;

    uiRet=strlen(InteropBF);

    if (uiRet > 0 && cbSize > uiRet) 
    {    
        strcpy(pstrRet, InteropBF);
    }
    else //error
    {        
        uiRet=0;
    }

    strcpy(InteropBF, "");
    mutex--;

    return uiRet;
}


//calling from c# 
[DllImport ("CCNxPlugin")]
public static extern UInt32 ReadFromBuffer(IntPtr pData, UInt32 cbData);
.
.
. 

String news;
UInt32 cbData=INTEROP_BUFFER_SIZE; //you need to define this in c# ;)
Byte[]  abyData=new byte[cbData];

try
{     
    //kindly request GC gives us a data address & leaves this memory alone for a bit
    GCHandle oGCHData = GCHandle.Alloc(abyData, GCHandleType.Pinned);
    IntPtr pbyData = oGCHData.AddrOfPinnedObject();

    UInt32 cbCopied=ReadFromBuffer(pbyData, cbData);

    oGCHData.Free();

    if(cbCopied > 0)
    { 
        System.Text.Encoding enc = System.Text.Encoding.ASCII;
        news = enc.GetString(abyData,0,cbCopied);
    }
}
catch (Exception e)
{
    System.Diagnostics.Debug.WriteLine(e);
}


【讨论】:

  • 谢谢!棘手的是我正在使用 Xcode,我发现在其中使用 .Net 函数有点困难。我可以在 C# 中使用 .Net 函数。我正在尝试从 Cocoa 框架中寻找类似的功能,如果您有推荐,请告诉我。
  • 啊。您可能想在您的问题中包含该事实并可能添加一个 xcode 标记;)〜我添加了另一种可能对您有用的方法 - 我不确定这是最好的方法..
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-03-09
  • 2016-04-13
  • 1970-01-01
  • 1970-01-01
  • 2014-07-31
  • 2012-04-24
  • 1970-01-01
相关资源
最近更新 更多