GCHandle, маршал, управляемая и неуправляемая память: закрепить или не закрепить
Как хочет Hans Passant, вот мой сценарий. У меня есть приложение в смешанном режиме, в котором нативный код выполняет всю тяжелую работу, сохраняя при этом производительность, а управляемый код отвечает только за графический интерфейс. Также пользователи будут участвовать, написав свой собственный код C#. У меня есть C++ для нативных классов, C# для GUI и пользовательского кода и C++/Cli для классов-оболочек между ними. Среди всех моих классов C++ есть класс, который выполняет 90% вычислений и каждый раз создает новый параметр. Давайте назовем это NativeClass. Есть ок. 2000 экземпляров этого NativeClass, и я должен найти правильный экземпляр, связанный с каким-либо параметром, прежде чем он выполнит вычисление. Поэтому для этой цели я разработал hash_map с параметрами, являющимися хеш-кодом. Когда я получаю параметр, я ищу нужный экземпляр в hash_map, нахожу его и вызываю некоторые его методы.
Когда пользователи компенсируют вычисления, написав код C#, и этот класс выполняет эти коды с помощью обратных вызовов. Это тривиально, но иногда мне нужна информация о классах.Net, созданных пользователями. Поэтому мне нужно каким-то образом прикрепить этот конкретный ManagedClass к NativeClass. Моим первым решением является использование GChandle.Alloc() и передача адреса дескрипторов. Но у GC есть некоторые опасения, что она не будет выполнять свою работу должным образом. Ганс рекомендовал Marshal.AllocCoTaskMem() и Marshal.StructureToPtr() для выделения управляемых объектов в неуправляемой памяти, однако я считаю, что это справедливо для классов или структур типов значений. Как насчет реф классов? Как я могу передать ссылку на NativeClass, не позволяя им собирать GC и одновременно заставить работать GC?
Вот пример кода:
class NativeClass
{
private:
int AddressOfManagedHandle;
public:
static NativeClass * GetNativeClassFromHashMap(int SomeParameter)
{
// return NativeClass associated with SomeParameter from NativeClassHashMap;
}
NativeClass(int addr, int SomeParameter) : AddressOfManagedHandle(addr)
{
}
int GetAddress(){return AddressOfManagedHandle;}
void DoCalculation(){
// CALCULATIONS
}
};
public ref class ManagedClass : MarshalByRefObject
{
private:
NativeClass* _nc;
//GCHandle handle;
void FreeManagedClass()
{
Marshal::FreeHGlobal(IntPtr(_nc->GetAddress()));
//if(handle.IsAllocated)
//handle.Free();
delete _nc;
}
public:
ManagedClass()
{
IntPtr addr = (Marshal::AllocHGlobal(Marshal::Sizeof(this))); // Error
Marshal::StructureToPtr(this,addr,true);
//handle = GCHandle.Alloc(this);
//IntPtr addr = handle.ToIntPtr();
_nc = new NativeClass(addr.ToInt32());
}
~ManagedClass() {FreeManagedClass();}
!ManagedClass() {FreeManagedClass();}
int GetAddress() {return _nc->GetAddress();};
static ManagedClass^ GetManagedClass(int SomeParameter)
{
int addr = NativeClass::GetNativeClassFromHashMap(SomeParameter)->GetAddress();
//Object^obj = GCHandle::FromIntPtr(IntPtr(addr)).Target;
Object^ obj = Marshal::PtrToStructure(IntPtr(addr), ManagedClass::typeid );
return dynamic_cast<ManagedClass^>(obj);
}
};
Я сожалею, что это слишком долго и до сих пор не ясно.
3 ответа
Я потратил довольно много времени на борьбу с подобной проблемой, и это план решения, которое сработало для меня....
- Сохраните дескриптор к управляемому классу как
void *
- Сохраните указатель на неуправляемый класс в управляемом классе (как вы сделали)
- Используйте старый добрый
new
а такжеdelete
а не что-нибудь такое, какAllocHGlobal
- Преобразовать
GCHandle
между void * и ссылкой на управляемый объект - Не беспокойтесь о получении от
MarshalByRefObject
- вам это не нужно, так как вы делаете собственный маршаллинг - Помните защитное кодирование при освобождении управляемого класса
Я взял ваш код выше и взломал его, чтобы получить:
class NativeClass
{
private:
void * managedHandle;
public:
static NativeClass * GetNativeClassFromHashMap(int SomeParameter)
{
// return NativeClass associated with SomeParameter from NativeClassHashMap;
}
NativeClass(void *handle, int SomeParameter)
: managedHandle(handle)
{
}
void * ManagedHandle()
{
return managedHandle;
}
void DoCalculation()
{
// CALCULATIONS
}
};
public ref class ManagedClass
{
private:
NativeClass* _nc;
void FreeManagedClass()
{
if (_nc)
{
// Free the handle to the managed object
static_cast<GCHandle>(IntPtr(_nc->ManagedHandle)).Free();
// Delete the native object
delete _nc;
_nc = 0;
}
}
public:
ManagedClass()
{
// Allocate GCHandle of type 'Normal' (see doco for Normal, Weak, Pinned)
GCHandle gch = GCHandle::Alloc(this, GCHandleType::Normal);
// Convert to void*
void *handle = static_cast<IntPtr>(gch).ToPointer();
// Initialise native object, storing handle to native object as void*
_nc = new NativeClass(handle);
}
~ManagedClass() {FreeManagedClass();}
!ManagedClass() {FreeManagedClass();}
static ManagedClass^ GetManagedClass(int SomeParameter)
{
// Native class is retrieved from hash map
NativeClass *nc = NativeClass::GetNativeClassFromHashMap(SomeParameter);
// Extract GCHandle from handle stored in native class
// This is the reverse of the process used in the ManagedClass constructor
GCHandle gch = static_cast<GCHandle>(IntPtr(nc->ManagedHandle()));
// Cast the target of the GCHandle to the managed object
return dynamic_cast<ManagedClass^>(gch.Target);
}
};
Это должно поставить вас на правильный путь.
Хммм.
GCHandle это структура.
В некоторых случаях передача неприкрепленного рефери GCHandle будет делать то, что вы хотите.