【问题标题】:Return array of pointers from c++ to c#将指针数组从 c++ 返回到 c#
【发布时间】:2015-06-24 11:16:45
【问题描述】:

下面我有一个来自 c++ 的代码 sn-p。 我需要返回指针数组(到 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));            
} 

【问题讨论】:

  • 你不是说例如(*outPtr)[0]? outPtr 参数不应该是 tripple 指针(即TempStruct*** outPtr),因为您分配了一个指针数组?您在分配内存时所做的转换掩盖了最后一个错误,永远不要转换 new 的结果。

标签: c# c++ pinvoke


【解决方案1】:

你做错了。

您正在混合指针类型。

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

现在... TempStruct** outPtr 应该是 TempStruct*** outPtr (因为您想返回 (*) 一个数组 (*) 指针 (*)... 或者 TempStruct**&amp; 如果您愿意:-)

改变这一行

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://stackoverflow.com/a/620634/613130。 C#签名是一样的。

【讨论】:

  • 我仍然只能阅读第一项。我将参数更改为三重指针,并按照您在上面编写的那样更改了 c# 代码
  • delete[] ptr 还不够。问题中的代码也要求删除每个项目。这就是为什么我认为 C++ 代码是错误的。
  • @DavidHeffernan 是的...已更正。
  • @John 查看完整示例。
  • @John 示例中的outPtr 通常不是NULL。如果您在 C++ 中使用该方法,您将:TempStruct **ptr; int size; GetResult(&amp;ptr, &amp;size),因此您将传递一个 outPtr 的值:&amp;ptr。在 C# 中也是如此,您使用 out ptr。显然,您可以添加NULL 检查,因为有人可以进行GetResult(NULL, NULL)。从技术上讲,用 C++ 编写它的“正确”方式是void GetResult(TempStruct **&amp;outPtr, int &amp;size),因为outPtrsize 都不能是NULL(参见stackoverflow.com/a/620634/613130
【解决方案2】:

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]? :)
  • @JoachimPileborg 是的。谢谢你。我在 C++ 上简直是垃圾!
  • @DavidHeffernan 唯一的问题是您不能轻松地使用具有该语法的参数化构造函数。有人问过here 以及可怕的解决方案。可以使用this one,但我更愿意去看牙医... :-)
  • @xanatos 是的,但我更喜欢所有额外的分配和间接。如果调用者可以知道需要多少元素,那么整个事情可以由 p/invoke 编组器完成,而无需显式分配。在 C++ 代码中,我只需创建一个返回新值的函数:TempStruct MakeNewStruct(const char* str, const int i)
猜你喜欢
  • 2014-03-30
  • 1970-01-01
  • 1970-01-01
  • 2010-12-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-21
相关资源
最近更新 更多