无论我尝试什么。当我整理数据时,我似乎得到了垃圾结果!编组副本之后的数据只包含一个看起来像未初始化数据的数组。只是纯垃圾。
提前感谢您的帮助!
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);
}
答案 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个小时才能正确编写函数指针的调用约定,所以这对于胆小的人来说并非如此: - )