Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to manage a buffer between C and C#

Problem

I have a C# script that calls C functions via System.Runtime.Interop. I managed to invoke C functions, but I have problem managing a buffer between C and C#.

In my situation C is the (data) producer and C# is the consumer.

My problem is when I read data in C#, sometime I get the correct value but sometimes I get NULL.

This problem is already solved. I am pasting my wrong approach AND my right approach here to share with you.

Background

The C# code is a unity script(part of Mono development), and the C code is in Xcode, which means I cannot use .Net framework functions in my C code.

Wrong approach (gives me NULL from time to time)

Here is my C code (write to buffer and read from buffer):

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;
}

I exposed the function ReadFromBuffer() to C#:

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

Then, I call the function like this:

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

Using this code I sometimes get the correct buffer content but more frequently I get a NULL from the Marshal.PtrToStringAuto function.

Right Approach (Many thanks to you all!)

I'd like to paste my working code and references I found here --

C function:

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# wrapper:

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


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

Calling function in 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);
        }

Summary

  1. char[] does not seem like a good buffer between C and C# (at least in my case where I am using Unity-Mono and Xcode). My suggestion is to organize data in a struct and pass the struct as parameter or as return value to C#. I've found great documentation about passing classes and structures but I haven't found any about passing char array just by itself. So I guess it is always better to wrap a char[] in a struct or in a class.
  2. A C struct can be marshaled as a C# class or a C# struct. Passing a wrapping class to unmanaged function as a parameter will work. Passing a wrapping struct to unmanaged function as a parameter will also work. Returning a pointer to a struct from unmanaged function is okay. Returning a pointer to a class from unmanaged function is NOT okay. (Found a great tutorial about this: http://www.mono-project.com/Interop_with_Native_Libraries#Summary)
like image 529
Zening Qu Avatar asked Apr 30 '12 19:04

Zening Qu


1 Answers

You're passing back a stack variable. That variable can be "collected" or freed in C/C++ upon return from the method--which will randomly result in bad memory by the time it gets to PtrToStringAuto. This would explain why it's sometimes null. You need to allocate memory that is passed back to the C# code and that code needs to free the memory (or the C/C++ needs to do it somehow).

You can do that in c/c++ with CoTaskMemAlloc and in C#, free it with Marshal.FreeCoTaskMem.

like image 197
Peter Ritchie Avatar answered Oct 10 '22 23:10

Peter Ritchie