Prevent garbage collection for managed reference which is used in unmanaged code
Asked Answered
B

2

10

My C# application uses wrapped C++ code for calculations.

C++ header:

__declspec(dllexport) void SetVolume(BYTE* data, unsigned int width);

C++/CLI wrapper:

void SetVolume(array<Byte>^ data, UInt32 width) 
{
    cli::pin_ptr<BYTE> pdata = &data[0];
    pal->SetVolume(pdata, width); 
}

C# :

public startCalc()
{
    byte[] voxelArr = File.ReadAllBytes("Filtered.rec");
    palw.SetVolume(voxelArr, 490);
    //GC.KeepAlive(voxelArr); makes no sense
}

The C++ SetVolume function starts asynchronous calculations. voxelArr is not referenced from the managed side any longer and is garbage collected.

How can I prevent the garbage collection for this reference until the unmanaged code finished it's work without to declare voxelArr as global variable? Creating a copy of array isn't an option as there is really a lot of data. Active wait inside of startCalc() isn't good too.

Bus answered 6/2, 2013 at 17:47 Comment(13)
keeping it from being collected is not your only problem, you will also need to prevent it from being moved around in memory. So you also have to pin it (as Reed Copsey suggests)Harlanharland
@Harlanharland I thought pin_ptr prevents moving it, doesn't it?Bus
Do consider if creating a copy isn't the best approach here. At least you'll be relieved from unpinning it and the GC is a lot happier as well.Convoy
yes, that means it is pinned (and also exempt from garbage collection) If you only need it for as long as the cli SetVolume runs, I don't think you need anything else. However it will be unpinned and may be collected or moved as soon as the wrapper setVolume function endsHarlanharland
@Harlanharland I've tried to declare byte[] voxelArr as global var and everything worked just fine. After moving it's declaration into the function scope, c++ can't access the memory some time after palw.SetVolume(voxelArr, 490); is executed. Is there some other reason other than it is garbage collected?Bus
@HansPassant this array contains sometimes more than 10Gb of data, the copy will simply not fit in the memory.Bus
it is either garbagabe collected or moved to a different address (most likely collected). Since the pin_ptr you use will only last until the wrapper function ends, it is insufficient to prevent either. (edit: if the array is that large, then the garbage collection will not move it, only collect it. But this is an implementation detail of GC that may change without notice, you should pin it anyway.)Harlanharland
@Bus If it's 10gb, it's not being moved (since it'd be on the LOH), so it's being GC'ed. That being said, I wouldn't worry about it - you MUST pin it in any case, or do something to prevent it from being GCedFulvia
Well, if it is ten jiggabytes then pinning won't matter, it won't be moved. Only objects less than 80KB get moved.Convoy
@HansPassant Isn't the cutoff 85,000 bytes [for everything except double arrrays]? (not that it matters)Fulvia
Come to think of it, the statement is nonsense. A managed object can't be larger than 2 jiggabytes, even in 64-bit mode.Convoy
@HansPassant - Good point - you can be over 2gb with gcAllowVeryLargeObjects, but not with a (one dimensional) byte array ;)Fulvia
@HansPassant thanks for the info, I tested my code with smaller (700mb) files so couldn't note this. What about unmanaged objects? Do you know a good article about this subject?Bus
F
13

You can use GCHandle.Alloc(voxelArr,GCHandleType.Pinned) to pin the array manually so the GC won't move or clean it.

You would then have to Free the handle when you knew the method was complete, which would require some form of callback for the completion.

Fulvia answered 6/2, 2013 at 17:50 Comment(3)
thanks, looks like what I need. Does that mean that I do not need to pin data variable in c++cli?Bus
@Bus If you're pinning it in C#, you wouldn't need to pin it in C++. That being said, the pin_ptr gives you access to the data as a pointer, so you likely will still want it to exist. You could also just persist the pin_ptr on the C++ side, as it's just doing this internally.Fulvia
@Bus Note that you can do this entirely in your C++/CLI wrapper instead of doing it in C#, as well. That'd be my preference, as it'd make the API cleaner...Fulvia
H
1

Another Alternative to your current approach:

Consider making the array global again, create and pin it once at the start, then reuse it whenever you need it. Objects as large as these should not be created on a whim, pool them. Only unpin and release it when you need to recreate it with a larger size

Storing the object in a global pool will prevent it from being garbage collected. You do not strictly speaking have to worry about pinning an object this large, but do so for consistency

Harlanharland answered 6/2, 2013 at 18:15 Comment(1)
well, it could be an option, if I had to reuse this array, but I really need it only once to load the data and pass it to c++ calculations.Bus

© 2022 - 2024 — McMap. All rights reserved.