gcref





5.00/5 (2投票s)
安全且功能性地从原生 C++ 中保持托管类型
C++ / CLI:托管/非托管代码中的safe_intrptr_t和函数式gcref
本文介绍了在托管/非托管(原生)代码中保存托管引用的gcroot
/ intptr_t
模式。
它解释了这种模式的风险,并提出了两种替代方案。
背景
借助gcroot
类(使用GCHandle::Alloc
和GCHandle::Free
的托管服务),可以在非托管内存中保存托管实例。遗憾的是,gcroot
本身不能在C++原生代码中使用。在这种情况下,建议用intptr_t
替换/隐藏gcroot
是不安全的,不应在没有采取许多预防措施的情况下使用。
不安全的intptr_t
用intptr_t
隐藏/替换gcroot
是不安全的。
// Header.h
#ifdef _MANAGED
# include <gcroot.h>
# define GCROOT(A) gcroot<A>
#else
# define GCROOT(A) intptr_t // this is not safe
#endif
class Wrapper
{
GCROOT(System::String^) m_value;
public:
Wrapper();
~Wrapper();
void someService();
};
// ManagedSource.cpp /cli
#include "Header.h"
Wrapper::Wrapper()
{
m_value = gcnew System::String("lucky me !");
}
Wrapper::~Wrapper()
{
// ~gcroot on m_value
}
void Wrapper::someService()
{
System::Console::WriteLine(m_value);
}
// UnManagedSource.cpp not /cli
#include "Header.h"
void function()
{
Wrapper wrapper; // ok, gcroot m_value is defined in ManagedSource.cpp
{
Wrapper copy(wrapper); // oops !
// default copy ctor is defined in UnManagedSource
// but destructor will be done in ManagedSource
// handle is Free
}
wrapper.someService(); // handle has been Free, and is no more valid. Crash
}
如果在非托管代码中定义了任何Wrapper
构造函数/复制构造函数/赋值运算符/析构函数,这将附加到intptr_t m_value
上,这对gcroot
(即GCHandle
)没有任何意义。在这个例子中,在intptr_t
上进行了复制构造函数(复制值),但在copy
的析构函数中释放了GCHandle
,导致wrapper.someService
崩溃。
防止这种情况发生的唯一方法是在C++/CLI文件中定义所有这些方法,并防止在非托管源代码中修改句柄(即intptr_t
)。考虑到C++默认和/或内联实现的风险,这对人类来说是一个很大的问题。
safe_intptr_t
safe_intptr_t
的目的是借助我们最好的朋友:编译器,来防止所有这些错误的发生。
// Header.h
#ifdef _MANAGED
# include <gcroot.h>
# define GCROOT(A) gcroot<A>
#else
struct safe_intptr_t
{
private:
// keep all members private
safe_intptr_t(); // do not implement
~safe_intptr_t(); // do not implement
safe_intptr_t(const safe_intptr_t& src); // do not implement
void operator= (const safe_intptr_t& src); // do not implement
intptr_t ptr; };
# define GCROOT(A) safe_intptr_t
#endif
如果在C++原生代码中附加任何构造函数/复制构造函数/析构函数/赋值运算符定义,编译器将引发错误。使用之前的Wrapper
实现和safe_intptr_t
,就会发生此编译器错误。
error C2248: 'safe_intptr_t' : cannot access private member declared in class 'safe_intptr_t'
see declaration of 'safe_intptr_t::safe_intptr_t'
This diagnostic occurred in the compiler generated function 'Wrapper::Wrapper(const Wrapper &)'
这应该足够明确,您可以使用托管源代码声明和实现Wrapper::Wrapper(const Wrapper&)
。
gcref:非托管代码中的函数式gcroot
如果safe_intptr_t
在编译时提供检查以防止任何错误的实现,这非常好,但在运行时它什么也不提供。只要不显式使用托管目标,在托管引用上拥有简单的功能(如空构造函数、复制构造函数、析构函数、比较和赋值运算符)将会很好。
gcref
旨在提供与托管代码中gcroot
相同的服务,以及一些非托管代码中的服务。这可以通过在gcref
类和托管GCHandle
之间使用间接寻址来实现。此外,一个GCHandle
在多个gcref
实例之间共享,避免了多个句柄分配(与gcroot
实现相比)。此实现基于共享的gchandle
类。
// gcref.h
class gchandle
{
private:
int m_ref;
void* m_handle;
gchandle(); // do not implement
gchandle(const gchandle&); // do not implement
void operator= (const gchandle&); // do not implement
~gchandle(); // implemented in gcref.cpp
public:
#ifdef _MANAGED
typedef System::Runtime::InteropServices::GCHandle GCHandle;
gchandle(System::Object^ o)
: m_ref(0)
{
ASSERT(o != nullptr);
m_handle = GCHandle::operator System::IntPtr( GCHandle::Alloc(o) ).ToPointer();
}
System::Object^ Target () const
{
return GCHandle::operator GCHandle(System::IntPtr(m_handle)).Target;
}
#endif
bool same(const gchandle& src) const; // implemented in gcref.cpp
void reference()
{
m_ref += 1;
}
void unreference()
{
if(--m_ref == 0)
delete this;
}
}
// gcref.cpp /cli
#include "gcref.h"
gchandle::~gchandle()
{
GCHandle g = GCHandle::operator GCHandle(System::IntPtr(m_handle));
g.Free();
}
bool gchandle::same(const gchandle& src) const
{
System::Object^ a = this->Target();
System::Object^ b = src.Target();
return a == b;
}
gchandle
并非旨在按原样使用,而是通过gcref
模板类(类似于gcroot
的类)使用。
// gcref.h
template <typename T>
class gcref
{
gchandle* m_handle;
void setHandle(gchandle* handle)
{
if(handle)
handle->reference();
if(m_handle)
m_handle->unreference();
m_handle = handle;
}
public:
gcref() : m_handle(0)
{
}
gcref(const gcref& ref) : m_handle(0)
{
setHandle(ref.m_handle);
}
~gcref()
{
setHandle(0);
}
bool operator == (gcref& r) const
{
return m_handle==r.m_handle || m_handle && r.m_handle && m_handle->same(*r.m_handle);
}
bool operator != (gcref& r) const
{
return ! operator==(r);
}
void operator = (const gcref& r)
{
setHandle(r.m_handle);
}
#ifndef _MANAGED
operator bool () const
{
return m_handle != 0;
}
#else
gcref(T^ t) : m_handle(0)
{
if(t != nullptr)
setHandle(new gchandle(t));
}
void operator= (T^ t)
{
setHandle(t==nullptr ? 0 : new gchandle(t));
}
operator T^ () const
{
return m_handle ? static_cast<T^> (m_handle->Target()) : nullptr;
}
T^ operator -> () const
{
return static_cast<T^> (m_handle->Target());
}
bool operator== (T^ t) const
{
return m_handle ? m_handle->Target()==t : t==nullptr;
}
bool operator!= (T^ t) const
{
return ! operator==(t);
}
#endif
};
gcref
类可以在托管代码中按原样使用。遗憾的是,在原生端这并不直接。托管类型不被识别,并在编译时导致错误。一种解决方法是对托管类型进行typedef
。此外,必须在原生部分创建一个安全类型,以保证编译时的类型检查。
// gcref.h
#ifdef _MANAGED
# define MANAGED_TYPEDEF(A,B) typedef A B
#else
# define MANAGED_TYPEDEF(A,B) typedef struct __managed##B { char* unused; } *B
#endif
瞧!gcref
类已准备好同时在托管和非托管代码中工作。
// Sample.h
#include <gcref.h>
MANAGED_TYPEDEF(System::Array, Array);
class Sample
{
public:
gcref<Array> m_array;
// All can be inline because implementation is valid both in managed and native sides
Sample() { } // empty ctor on gcref
Sample(const Sample& src) : m_array(src.m_array) { } // copy ctor on gcref
~Sample() { } // dtor on gcref
void share(const Sample& src) { m_array = src.m_array; } // assignment on gcref
bool same(const Sample& src) const { return m_array == src.m_array; } // comparison on gcref
};
即使在原生代码中,类型检查也是一致的。
// Sample.h not /cli
#include <gcref.h>
MANAGED_TYPEDEF(System::Array, Array);
MANAGED_TYPEDEF(System::IO::File, File);
MANAGED_TYPEDEF(System::IO::File, Array); // error C2011 at compile time
gcref<Array> array;
gcref<File> file;
file = array; // error C2679 at compile time
空指针
还有一件事,那就是在原生端使用空指针进行比较和赋值将会很好。
这是通过为此单一目的设计一个gcnull struct
来实现的。
// gcref.h
#ifdef _MANAGED
# define MANAGED_TYPEDEF(A,B) typedef A B
#else
# define MANAGED_TYPEDEF(A,B) struct __managed##B { char* unused; } *B
typedef struct __gcnull
{
private:
char* unused;
__gcnull();
__gcnull(const _NullRep& src);
} * _gcnull;
#endif
class gcref
{
public:
#ifndef _MANAGED
void operator= (_gcnull) // 0 is promoted to _gcnull by the compiler
{
sethandle(0);
}
bool operator== (_gcnull) // 0 is promoted to _gcnull by the compiler
{
return m_handle == 0;
}
#endif
};
// Sample.h in unmanaged source
void sample()
{
gcref<Array> array;
array = 0; // OK : 0 is promoted to _gcnull by the compiler
array == 0; // idem
}
更多功能
可以向gchandle
类(然后是gcref
类)添加一些方法,例如访问System.Object.Equals
或System.Object.GetHashCode
,以扩展原生端gcref
的功能。