从C++返回指针数组到C#

4

以下是来自 c++ 的代码片段。我需要返回指向 TempStruct 的指针数组。

问题在于,在 c# 侧,我只得到一个元素。而在另一侧,我得到了 AV。

**C++**
extern "C" __declspec(dllexport) void GetResult(TempStruct** outPtr, long *size)
{       
    *outPtr = (TempStruct*)new TempStruct*[2];

     outPtr[0] = new TempStruct("sdf", 123);
     outPtr[1] = new TempStruct("abc", 456);

    *size = 2;      
}

**C#**    
[DllImport("test.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern void GetResult(out IntPtr outPtr, out int size);

IntPtr ptr = IntPtr.Zero;
int length;        
GetResult(out ptr, out length);

int size = Marshal.SizeOf(typeof(TempStruct));
TempStruct[] someData2 = new TempStruct[length];

for (int i = 0; i < length; i++)
{
   IntPtr wskptr = (IntPtr)(ptr.ToInt64() + (size * i));
   someData2[i] = (TempStruct)Marshal.PtrToStructure(wskptr, typeof(TempStruct));            
} 

1
难道你的意思不是例如 (* outPtr)[0]吗?既然你要分配一个指针数组,outPtr 参数不应该是一个 三重 指针(即 TempStruct *** outPtr)吗?当你分配内存时所做的转换掩盖了最后一个错误,永远不要转换 new 的结果。 - Some programmer dude
2个回答

4

你做错了。

你混淆了指针类型。

通过使用new TempStruct(),你创建了一个指向 TempStruct 的指针数组。我给你的例子创建了一个TempStruct数组。看到区别了吗?

现在...TempStruct ** outPtr应该是TempStruct *** outPtr(因为你想返回(*)一个指针(*)数组(*)...或者如果你喜欢的话,可以是TempStruct**& :-)

更改这一行:

someData2[i] = (TempStruct)Marshal.PtrToStructure(Marshal.ReadIntPtr(wskptr), typeof(TempStruct));

因为您必须阅读单指针。
我希望您使用delete删除各种TempStruct并使用。
delete[] ptr;

删除结构体数组的操作符。

完整示例:

C++:

struct TempStruct
{
    char* str;
    int num;

    // Note the strdup. You don't know the source of str.
    // For example if the source is "Foo", then you can't free it.
    // Using strdup solves this problem.
    TempStruct(const char *str, int num) 
        : str(strdup(str)), num(num)
    {
    }

    ~TempStruct()
    {
        free(str);
    }
};

extern "C"
{
    __declspec(dllexport) void GetResult(TempStruct ***outPtr, int *size)
    {
        *outPtr = new TempStruct*[2];

        (*outPtr)[0] = new TempStruct("sdf", 123);
        (*outPtr)[1] = new TempStruct("abc", 456);

        *size = 2;
    }

    __declspec(dllexport) void FreeSomeData(TempStruct **ptr, int size)
    {
        for (int i = 0; i < size; i++)
        {
            delete ptr[i];
        }

        delete[] ptr;
    }
}

C#:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1), Serializable]
internal struct TempStruct
{
    public string str;
    public int num;
}

[DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
static extern void GetResult(out IntPtr outPtr, out int numPtr);

[DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void FreeSomeData(IntPtr ptr, int num);

// C++ will return its TempStruct array in ptr
IntPtr ptr;
int size;

GetResult(out ptr, out size);

TempStruct[] someData2 = new TempStruct[size];

for (int i = 0; i < size; i++)
{
    IntPtr ptr2 = Marshal.ReadIntPtr(ptr, i * IntPtr.Size);
    someData2[i] = (TempStruct)Marshal.PtrToStructure(ptr2, typeof(TempStruct));
}

// Important! We free the TempStruct allocated by C++. We let the
// C++ do it, because it knows how to do it.
FreeSomeData(ptr, size);

请注意,在C#的struct中不需要使用[Serializable]Pack=1
对于C++,更正确的做法是:
__declspec(dllexport) void GetResult(TempStruct **&outPtr, int &size)
{
    outPtr = new TempStruct*[2];

    outPtr[0] = new TempStruct("sdf", 123);
    outPtr[1] = new TempStruct("abc", 456);

    size = 2;
}

这种写法更加严谨,因为 outPtrsize 都不能为 NULL。参见https://dev59.com/wHRB5IYBdhLWcg3wcm6d#620634。C# 的签名也是一样的。


仍然只能读取第一个项目。我将参数更改为三重指针,并按照您上面所写的更改了C#代码。 - John
仅仅使用delete[] ptr是不够的。问题中的代码需要删除每个项目。这就是我认为C++代码是错误的原因。 - David Heffernan
@DavidHeffernan 是的...已经更正了。 - xanatos
@xanatos 在C++中:"outPtr = new TempStruct[2];" 这样做不会导致AV,因为outPtr尚未初始化,而我在这里使用了* - 指针? - John
1
@John 在这个示例中,outPtr 通常不是 NULL。如果您在 C++ 中使用该方法,您可以这样做:TempStruct **ptr; int size; GetResult(&ptr, &size),因此您将为 outPtr 传递一个值:&ptr。在 C# 中也是一样,只需要使用 out ptr 即可。显然,您可以添加一个 NULL 检查,因为某些人可能会执行 GetResult(NULL, NULL)。从技术上讲,在 C++ 中编写它的“正确”方式是 void GetResult(TempStruct **&outPtr, int &size),因为outPtrsize 都不能是 NULL(请参见https://dev59.com/wHRB5IYBdhLWcg3wcm6d#620634)。 - xanatos
显示剩余3条评论

3

C++代码有误。它返回一个结构体指针数组。你对new返回值的强制转换应该提示你犯了错误。你需要返回一个结构体数组。

正确写法如下:

*outPtr = new TempStruct[2];
(*outPtr)[0].str = "sdf";
(*outPtr)[0].i = 123;
(*outPtr)[1].str = "abc";
(*outPtr)[1].i = 456;
*size = 2;      

你是不是想说例如 (*outPtr)[0]? :) - Some programmer dude
@JoachimPileborg 是的。谢谢。我只是对C++很菜! - David Heffernan
@DavidHeffernan 唯一的问题是你不能轻松地使用参数化构造函数来完成这个语法。有人在这里提出了这个问题(https://dev59.com/um445IYBdhLWcg3wpb8P),而解决方案非常糟糕。虽然可以使用[这个](https://dev59.com/um445IYBdhLWcg3wpb8P#4756306),但我宁愿去看牙医... :-) - xanatos
@xanatos 嗯,是的,但我更喜欢这种方式,而不是所有那些额外的分配和间接引用。如果调用者可以知道需要多少元素,那么整个过程就可以由p/invoke marshaller完成,而无需显式分配。在C++代码中,我只需创建一个返回新值的函数:TempStruct MakeNewStruct(const char* str, const int i) - David Heffernan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接