6

問題

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. AC構造体は、C#クラスまたはC#構造体としてマーシャリングできます。ラッピングクラスをアンマネージ関数にパラメーターとして渡すと機能します。ラッピング構造体をアンマネージ関数にパラメーターとして渡すこともできます。アンマネージ関数から構造体へのポインターを返すことは問題ありません。アンマネージ関数からクラスへのポインターを返すことは問題ありません。(これに関する優れたチュートリアルが見つかりました:http ://www.mono-project.com/Interop_with_Native_Libraries#Summary )
4

2 に答える 2

3

スタック変数を返しています。その変数は、メソッドから戻ったときにC / C ++で「収集」または解放できます。これにより、PtrToStringAutoに到達するまでにランダムにメモリが不良になります。これは、それが時々 nullになる理由を説明します。C#コードに戻されるメモリを割り当てる必要があり、そのコードはメモリを解放する必要があります(または、C / C ++が何らかの方法でそれを行う必要があります)。

これは、CoTaskMemAllocを使用してC / C ++で実行でき、C#ではMarshal.FreeCoTaskMemを使用して解放できます。

于 2012-04-30T19:25:36.040 に答える
0

ac#文字列型を渡してSysAllocString、C++内から使用してみてください。

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


于 2012-04-30T20:20:14.187 に答える