将一个std :: vector <uint64_t>从C ++编号为C#</uint64_t>

时间:2015-03-26 14:45:27

标签: c# c++ pinvoke marshalling

无论我尝试什么。当我整理数据时,我似乎得到了垃圾结果!编组副本之后的数据只包含一个看起来像未初始化数据的数组。只是纯垃圾。

提前感谢您的帮助!

C ++

typedef uint64_t TDOHandle;

extern "C" DATAACCESSLAYERDLL_API const TDOHandle * __stdcall DB_GetRecords()
{
    const Database::TDBRecordVector vec = Database::g_Database.GetRecords();
    if (vec.size() > 0)
    {
        return &vec[0];
    }
    return nullptr;
}

C#

声明

    [System.Security.SuppressUnmanagedCodeSecurity()]
    [DllImport("DataCore.dll")]
    static private extern IntPtr DB_GetRecords();

//编组过程

            IntPtr ptr_records = DB_GetRecords();
        if (ptr_records != null)
        {
            Byte[] recordHandles = new Byte[DB_GetRecordCount()*sizeof (UInt64)];
            Marshal.Copy(ptr_records, recordHandles, 0, recordHandles.Length);

            Int64[] int64Array = new Int64[DB_GetRecordCount()];
            Buffer.BlockCopy(recordHandles, 0, int64Array, 0, recordHandles.Length);
        }

2 个答案:

答案 0 :(得分:4)

您将返回本地变量拥有的内存地址。函数返回时,会破坏局部变量。因此,您返回的地址现在毫无意义。

您需要分配动态内存并返回该内存。例如,使用CoTaskMemAlloc分配它。然后消费的C#可以通过调用Marshal.FreeCoTaskMem来解除分配。

或者使用new分配内存,但也可以从可以解除内存释放的非托管代码中导出函数。

例如:

if (vec.size() > 0)
{
    TDOHandle* records = new TDOHandle[vec.size()];
    // code to copy content of vec to records
    return records;
}
return nullptr;

然后你会导出另一个公开deallocator的函数:

extern "C" DATAACCESSLAYERDLL_API void __stdcall DB_DeleteRecords(
    const TDOHandle * records)
{
    if (records)
        delete[] record;
}

所有这一切,似乎你可以在调用函数来填充数组之前获取数组长度。您可以使用DB_GetRecordCount()执行此操作。在这种情况下,您应该在托管代码中创建一个数组,并将其传递给非托管代码以便填充它。这一方面解决了所有内存管理问题。

答案 1 :(得分:1)

我补充说还有另一种方法:

public sealed class ULongArrayWithAllocator
{
    // Not necessary, default
    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    public delegate IntPtr AllocatorDelegate(IntPtr size);

    private GCHandle Handle;

    private ulong[] allocated { get; set; }

    public ulong[] Allocated
    {
        get
        {
            // We free the handle the first time the property is
            // accessed (we are already C#-side when it is accessed)
            if (Handle.IsAllocated)
            {
                Handle.Free();
            }

            return allocated;
        }
    }

    // We could/should implement a full IDisposable interface, but
    // the point of this class is that you use it when you want
    // to let C++ allocate some memory and you want to retrieve it,
    // so you'll access LastAllocated and free the handle
    ~ULongArrayWithAllocator()
    {
        if (Handle.IsAllocated)
        {
            Handle.Free();
        }
    }

    // I'm using IntPtr for size because normally 
    // sizeof(IntPtr) == sizeof(size_t) and vector<>.size() 
    // returns a size_t
    public IntPtr Allocate(IntPtr size)
    {
        if (allocated != null)
        {
            throw new NotSupportedException();
        }

        allocated = new ulong[(long)size];
        Handle = GCHandle.Alloc(allocated, GCHandleType.Pinned);
        return Handle.AddrOfPinnedObject();
    }
}

[DllImport("DataCore.dll", CallingConvention = CallingConvention.StdCall)]
static private extern IntPtr DB_GetRecords(ULongArrayWithAllocator.AllocatorDelegate allocator);

并使用它:

var allocator = new ULongArrayWithAllocator();
DB_GetRecords(allocator.Allocate);

// Here the Handle is freed
ulong[] allocated = allocator.Allocated; 

和C ++方

extern "C" DATAACCESSLAYERDLL_API void __stdcall DB_GetRecords(TDOHandle* (__stdcall *allocator)(size_t)) {
    ...
    // This is a ulong[vec.size()] array, that you can
    // fill C++-side and can retrieve C#-side
    TDOHandle* records = (*allocator)(vec.size());
    ...
}

或类似的东西:-)你将一个委托传递给可以分配内存的C ++函数C#-side :-)然后在C#端你可以检索分配的最后一个内存。重要的是,您不要在一次通话中以这种方式进行多个分配C ++ - 因为您要保存单个LastAllocated引用,即&#34;保护&#34;来自GC的已分配内存(所以不要(*allocator)(vec.size());(*allocator)(vec.size());

请注意,我花了1个小时才能正确编写函数指针的调用约定,所以这对于胆小的人来说并非如此: - )