C++/CLI:返回对非托管对象的引用

Posted

技术标签:

【中文标题】C++/CLI:返回对非托管对象的引用【英文标题】:C++/CLI: return a reference to unmanaged object 【发布时间】:2015-03-13 04:46:45 【问题描述】:

我正在寻找一种方法来返回对作为托管对象成员的另一个对象的引用。这可以在 C++ 中轻松实现,但对于与 C# 一起使用的 C++/CLI 包装器是一个挑战。下面是可重现的场景(代码很多,但都很简单,只是演示问题)

C++ 类:

class NATIVEAPI NativeSlave

public:
    NativeSlave() : x_( 0 ), y_( 0.0 )
    

    NativeSlave( int x, double y ) : x_( x ), y_( y )
    

    int x_;
    double y_;
;

class NATIVEAPI NativeMaster

public:
    __declspec( property( get = getSlave, put = setSlave ) ) NativeSlave& slave;
    NativeSlave& getSlave()
    
        return *pSlave; //returns a reference
    
    void setSlave( const NativeSlave& slave )
    
        *pSlave = slave;
    

public:
    NativeMaster() : pSlave( new NativeSlave( 4, 5.0 ) )
    
    ~NativeMaster()
    
        delete pSlave;
    

private:
    NativeSlave* pSlave;
;

C++ 用法:

NativeSlave slave = NativeSlave( 1, 2.0 ); //now, slave.x==1, slave.y==2.0
NativeMaster master; //now, master.slave.x==4, master.slave.y==5.0

master.slave = slave; //now, master.slave.x==1, master.slave.y==2.0
master.slave.x_ = 6; //now, master.slave.x==6
master.slave.y_ = 10.0; //now, master.slave.y==10.0

所以在 C++ 中,我们可以轻松获取对底层对象的引用并对其方法进行操作(这里,为了简化示例,成员是公共的)。

然后,目标是将其包装在 C++/CLI 中,以在 C# 中实现与上述 C++ 中相同的功能(用法):

C# (需要)

ManagedSlave slave = new ManagedSlave(1, 2.0); //now, slave.x==1, slave.y==2.0
ManagedMaster master = new ManagedMaster(); //desired: master.slave.x==4, master.slave.y==5.0

master.slave = slave; //desired: master.slave.x==1, master.slave.y==2.0
master.slave.x = 6; //no appropriate get() method to change master.slave
master.slave.y = 10.0; //no appropriate get() method to change master.slave

这里尝试写一个包装器:

C++/CLI(get/set 属性方法有问题):

public ref class ManagedSlave

public:
    property int x
    
        int get()
        
            return mSlave->x_;
        
        void set( int x )
        
            mSlave->x_ = x;
        
    
    property double y
    
        double get()
        
            return mSlave->y_;
        
        void set( double y )
        
            mSlave->y_ = y;
        
    
public:
    ManagedSlave( int x, double y ) : mSlave( new NativeSlave( x, y ) )
    
    ~ManagedSlave()
    
        delete mSlave;
    

internal:
    NativeSlave* mSlave;
;

public ref class ManagedMaster

public:
    property ManagedSlave^ slave
    
        ManagedSlave^ get()
        
            //??????????????????????????
        ;
        void set( ManagedSlave^ slave )
        
            //is this correct???????????
            mMaster->slave.x_ = slave->x;
            mMaster->slave.y_ = slave->y;
        ;
    

public:
    ManagedMaster() : mMaster( new NativeMaster() )
    
    ~ManagedMaster()
    
        delete mMaster;
    

internal:
    NativeMaster* mMaster;
;

【问题讨论】:

请注意,您的本机代码使用了不必要的动态分配,并且违反了三规则。 @BenVoigt,是的,你是对的.. 为了示例长度,我省略了复制 ctor 和赋值运算符... 您要求 C++/CLI 提供 C# 程序员从未见过的 C# 使用模型……这可能不是一个好主意。 【参考方案1】:

.NET “引用”与 C++ 引用完全不同。

.NET 中有一些东西相当于 C++ 引用,即限定参数的 ref 关键字。但是除了参数之外,没有其他方法可以使用它。 (在 IL 级别它也可以用于局部变量,但仍然不能用于返回类型)

大多数时候,您可以通过额外的间接层来解决这个问题。

在您的特定情况下,这真的很容易:

ManagedSlave( NativeSlave* s ) : mSlave( s )

~ManagedSlave()

    // empty


ManagedSlave^ ManagedMaster::slave::get()

     return gcnew ManagedSlave( &mMaster->getSlave() );


// remove ManagedMaster::slave::set

基本上,ManagedSlave 没有理由负责分配和释放NativeSlave,因为NativeMaster 已经这样做了。

【讨论】:

非常感谢!我对 .NET 很陌生,但要努力理解......你是说ManagedSlave 不需要释放指针成员?但是如何为这个对象释放内存:ManagedSlave slave = new ManagedSlave(1, 2.0); 如果没有set 方法,那么master.slave = slave; 会失败,因为它是只读的,对吧? @OlegShirokikh:在 C++/CLI 中,它可以正常工作,并调用 ManagedSlave 的复制赋值运算符,它可以复制所有 NativeSlave 字段。在 C# 中,您可以使用 master.slave.CopyFrom(otherSlave) 之类的语法来反映 slave 属性实际上并未被新对象替换,或者您可以使用更简洁但具有误导性的 setter。 @Oleg: ManagedSlave slave = new ManagedSlave(1, 2.0); 是不可能的。命名向我表明,从属对象不应该与它们的主人分开存在。我想你可以尝试在一个班级里同时做这两件事,但这会变得混乱。或者使用继承,以便master.slave 返回一个NonOwningSlave^ManagedSlave 继承自该ManagedSlave 并执行分配和释放。 @OlegShirokikh:如果您希望master.slave = otherSlave; 复制成员,例如值类型,而不是使两者都指向同一个对象实例,那么ManagedSlave 应该是值类型。在这种情况下,您不会包装 NativeSlave 对象,而是在必要时在它们之间进行转换。但这打破了master.slave.x = 6;

以上是关于C++/CLI:返回对非托管对象的引用的主要内容,如果未能解决你的问题,请参考以下文章

C# 托管和非托管混合编程

将非托管方法作为回调传递给托管 C++/CLI 类

是否可以在线程中对非托管函数执行C#回调?

如何使用CString参考调用非托管C ++ DLL

如何将非托管库引用添加到 NUnit 测试

如何从我的 C++/CLI 代码进入非托管 C++ 库