65.9K
CodeProject 正在变化。 阅读更多。
Home

gcref

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2018 年 11 月 12 日

CPOL

3分钟阅读

viewsIcon

9734

downloadIcon

114

安全且功能性地从原生 C++ 中保持托管类型

C++ / CLI:托管/非托管代码中的safe_intrptr_t和函数式gcref

本文介绍了在托管/非托管(原生)代码中保存托管引用的gcroot / intptr_t模式。

它解释了这种模式的风险,并提出了两种替代方案。

背景

借助gcroot类(使用GCHandle::AllocGCHandle::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.EqualsSystem.Object.GetHashCode,以扩展原生端gcref的功能。

© . All rights reserved.