查看: 5101|回复: 2

关于osgDotNet开发的一点经验

[复制链接]

该用户从未签到

发表于 2009-12-22 16:30:51 | 显示全部楼层 |阅读模式
本帖最后由 gavinyu 于 2009-12-22 16:48 编辑

    看到一些人想用C#进行osg的开发,想说下自己在用osgDotNet时碰到的一些问题和经验,希望能对用到的人有些帮助。

首先是osgDotNet有很多接口没有输出,需要手动添加。

1、普通接口函数添加:

osgDotNet不支持标准模板库的各种类型的自动转换,所以如果需要用到某个接口,参数类型或返回类型是标准模板库,需要自己手动添加。
下面以 Node 的 getParentalNodePaths 方法为例,说一下添加的方式。
C++ 中 Node 的 getParentalNodePaths 原型:

NodePathList Node::getParentalNodePaths(osg::Node* haltTraversalAtNode)const

NodePathList的定义:

typedef std::vector< Node*> NodePath;
typedef std::vector< NodePath> NodePathList;

在osgDotNet自动生成的代码中,这个函数没有输出:
/* unsupported methodgetParentalNodePaths */

我们可以在 Node.h 中按如下方式添加:

typedef System::Collections::Generic::IList< Node^ > NodePath;
typedef System::Collections::Generic::IList<NodePath ^ > NodePathList;

public refclass Node :Osg::Object
{
      

       /* unsupportedmethod getParentalNodePaths */
       NodePathList ^ getParentalNodePaths(Osg::Node ^ haltTraversalAtNode);
};

实现代码:

NodePathList ^ Node::getParentalNodePaths(Osg::Node ^ haltTraversalAtNode)
{
     osg::NodePathListnpl;
     npl = ___vtnp(this)->getParentalNodePaths(___vtnp(haltTraversalAtNode));

     typedef System::Collections::Generic::List< Osg::Node ^ > TNList;
     typedef System::Collections::Generic::List< Osg::NodePath ^ > TNPList;

     Osg::NodePathList^ nodePathList = gcnewTNPList();
     for(unsigned int i=0;i<npl.size();++i)
     {
        osg:: NodePath&np = npl[i];
        Osg::NodePath^ nodePath = gcnewTNList();
        for(unsigned int j=0;j<np.size();++j)
        {
             Osg::Node^ n = gcnew Osg::Node(np[j], false);
             nodePath->Add(n);
        }

        nodePathList->Add(nodePath);
     }

     return nodePathList;
}

2、添加回调处理:

我们经常需要根据不同的需求对节点设置更新、拣选等回调处理, osg::NodeCallback类有个非常关键的操作 operator() ,其原型如下:

      virtual void operator()(Node* node, NodeVisitor* nv)
      {
           // note, callback is responsible for scenegraphtraversal so
           // they must call traverse(node,nv) to ensure that the
           // scene graph subtree (and associated callbacks) aretraversed.
           traverse(node,nv);
       }

但是osgDotNet自动生成的代码中,对应的托管类 ___NodeCallback_adapter 没有重载 operator(),因而不支持在C#类中的回调处理。可以按如下方式添加:

public ref classNodeCallback : Osg::Object
{
       //添加虚函数
       virtual void operator()(Osg::Node ^ node, Osg::NodeVisitor^ nv){}
};

class ___NodeCallback_adapter: public osg::NodeCallback
{
       //重载
       virtual void operator()(osg::Node * node, osg::NodeVisitor* nv);
};

实现代码
void ___NodeCallback_adapter::operator()(osg::Node * node, osg::NodeVisitor * nv)
{
       ___mh()->operator()( ___WS::VTableTypeWrapperFactory::getWrapper(node),___WS::VTableTypeWrapperFactory::getWrapper(nv));
}

在对应的C#类会生成新的接口Osg.NodeCallback.op_FunctionCall(Osg.Node, Osg.NodeVisitor),从而可以写自己的类重载此方法进行相应处理:

public class myNodeCallBack: Osg.NodeCallback
{
       public overridevoid op_FunctionCall(Osg.Node node, Osg.NodeVisitor nv)
       {
           //处理
       }
}

对回调的使用设置与C++中类似。


该用户从未签到

 楼主| 发表于 2009-12-22 16:31:31 | 显示全部楼层
本帖最后由 gavinyu 于 2009-12-22 16:44 编辑

下面再说说应用osgDotNet中发现的一些问题,以及解决方式。

1.  每帧内存增加

例如在C#中生成一个新Node对象:

Osg.Node node = new Osg.Node()

Node::Node() : Osg::Object(___WS::InitializedSentinel())
{
___init(new ___AdapterType());
}

___AdapterType  即 ___Node_adapter,从osg::Node继承,其中有成员变量:
Osg.Node ___m_managed 存放了其对应的托管对象。
对于从托管环境生成的Node对象,C++对象中对应有一个___Node_adapter对象,所以需要传递到托管环境时,使用 ___mh() 就可以得到其托管对象;
而对于OSG内部生成的对象(new osg::Node()), 例如对一个 osg::Node* node 对象(非___Node_adapter),要传递到托管环境时,通过 ___WS::VTableTypeWrapperFactory::getWrapper(node) 来生成一个临时托管对象,此函数会调用到

template <typename StaticNativeType, typename DynamicNativeType>

        static System::Object ^dynamicTypeWrapperFactory(void *native, bool isReference)

        {

            typedef typename GetUnknownObjectWrapper<DynamicNativeType>::type UnknownObjectWrapper;

            return gcnew UnknownObjectWrapper(dynamic_cast<DynamicNativeType *>(static_cast<StaticNativeType *>(native)), !isReference);

        }



此过程临时生成一个 ___Node_unknown_object 托管对象来包裹 node,同时造成node引用计数的增加(ref class ___Node_unknown_object_ : Node)
在 frame() 过程中各类 visitor 对象会大量出现这种现象,在垃圾回收之前,会造成内存的不停增长。

解决方式:
在C#重载的处理结束后调用这类临时对象的Dispose()方法,例如:

        public override void op_FunctionCall(Osg.Node node, Osg.NodeVisitor nv)

        {
            …
            nv.Dispose();
        }


2. osg::computeLocalToWorld异常

代码中经常会用到 osg::computeLocalToWorld,运行中发现调用此函数后,程序就会出现异常:

Matrix osg::computeLocalToWorld(const NodePath& nodePath, bool ignoreCameras)
{
    Matrix matrix;
    TransformVisitor tv(matrix,TransformVisitorOCAL_TO_WORLD,ignoreCameras);
    tv.accumulate(nodePath);
    return matrix;
}

其中在accumulate调用过程中:

transform.computeLocalToWorldMatrix(_matrix,this);//this -- TransformVisitor

而这个方法会调用到Transform托管类的computeLocalToWorldMatrix:

bool ___MatrixTransform_adapter::computeLocalToWorldMatrix(osg::Matrixd & matrix, osg::NodeVisitor * x) const

{

    Osg::Matrix p1_(matrix);

    bool ret_ = ___mh()->computeLocalToWorldMatrix(%p1_, ___WS::VTableTypeWrapperFactory::getWrapper(x));

    pin_ptr<Osg::Matrix::___BufferType> p1_1_ = &p1_.___getNativeBuffer();

    matrix = *reinterpret_cast<osg::Matrixd *>(p1_1_);

    return ret_;

}

___WS::VTableTypeWrapperFactory::getWrapper(x) 会以非托管C++的临时变量tv的地址(&tv)作为NativeType* native生成一个对应的托管对象(_mh_tvVisitor,类型为___NodeVisitor_unknown_object_)。
而函数osg::computeLocalToWorld执行完后临时变量&tv已经无效,但垃圾回收时托管对象_mh_tvVisitor.Dispose()会执行 tv.unref() 而引发异常。

解决方式:
以Node为例先看看Node 构造、初始化和析构的代码:

Node::Node(___WS::InitializedSentinel) : Osg::Object(___WS::InitializedSentinel())
{
}

Node::Node(___NativeType *native, bool useRefCount) : Osg::Object(___WS::InitializedSentinel())
{
    ___init(native, useRefCount);
}

Node::~Node()
{
}

Node::!Node()
{
    if (___m_useRefCount)
    {
        if (___m_native->referenceCount() > 1)
        {
            if (___AdapterType *adapter = dynamic_cast<___AdapterType *>(___m_native))
            {
                // There is still a native reference to this object, so
                // just deactivate it in the managed world and let the
                // native ref counting take care of deletion.  If it
                // does get passed back to the managed world again it
                // will get reactivated.
                adapter->___deactivate();
            }
        }      
    }
}

void Node::___init(___NativeType *native, bool useRefCount)
{
    ___m_useRefCount = useRefCount;
    ___m_native = native;
    if (___m_useRefCount) ___vtnp(this)->ref();
}

只要___m_useRefCount = false,垃圾回收时就不会调用到C++对象(adapter)的unref()而引发异常。考虑 ___WS::VTableTypeWrapperFactory::getWrapper() 根据传递参数类型不同的生成托管对象处理方式:

        template <typename N>
        static typename GetWrapper<N>::type ^getWrapper(N *native)
        {
            return getWrapper<N>(native, false);
        }

         template <typename N>
        static typename GetWrapper<N>::type ^getWrapper(const N *native)
        {
            return getWrapper<N>(const_cast<N *>(native), false);
        }

        template <typename N>
        static typename GetWrapper<N>::type ^getWrapper(N &native)
        {
            return getWrapper<N>(&native, true);
        }

        template <typename N>
        static typename GetWrapper<N>::type ^getWrapper(const N &native)
        {
            return getWrapper<N>(const_cast<N *>(&native), true);
        }

在MatrixTransform_adapter::computeLocalToWorldMatrix中作如此修改:
       bool ret_ = ___mh()->computeLocalToWorldMatrix(%p1_, ___WS::VTableTypeWrapperFactory::getWrapper(*x));
传递指针改为传递引用对象,也即调用 getWrapper(N *native, bool isReference)时使isRefence=true, 从而调用Node的第二个构造函数Node::Node(___NativeType *native, bool useRefCount)且useRefCount=false 。

3.  内存无法释放问题

实际使用过程中,发现一个问题,随着运行时间的增长,程序占用内存越来越多。这个问题不同于问题1,即便进行垃圾回收内存也无法释放。
经过调试发现,frame过程中,很多osg内部生成的很多临时C++对象(osg::referenced智能指针管理),当需要传递到托管环境中时,其引用计算_refCount会无限增加,从而无法释放。(这个问题也会引发C#对象的多次Finallize)

原因是很多osg对象在传递到托管环境时会调用

___WS::VTableTypeWrapperFactory::getWrapper(Object),

很多会调用到这:

    template <typename StaticNativeType, typename AdapterType>
    static System::Object ^refCountedAdapterFactory(void *native, bool /* isReference */)
    {
        AdapterType *adapter = dynamic_cast<AdapterType *>(static_cast<StaticNativeType *>(native));

        if (!adapter->___isActive())
            adapter->___reactivate();

//由于osgDotNet-generator生成代码本身的问题,___m_isActive没有初始化,同时在reactivate或//deactivate 时都没有改变其值,每次调用___reactivate()都会增加计数
        return adapter->___mh();
    }

解决方式:(以Group.cpp为例,红色为修改增加代码)

void Group::___init(___AdapterType *adapter)
{
    ___m_useRefCount = true;
    ___m_native = adapter;
    adapter->___m_managed = this;adapter->___m_isActive=true;
    if (___m_useRefCount) ___vtnp(this)->ref();
}

void ___Group_adapter::___reactivate()
{
    // Re-establish a managed code reference for this object, abandoning
    // the GC handle table's strong reference.
    ref();
    ___m_managed.makeWeak();___m_isActive=true;
    // Need to reregister for finalize since the object is back in
    // action in the managed world.
    System::GC::ReRegisterForFinalize(___m_managed);
}


// This function gets invoked from the wrapper class's finalizer.  We
// wait until reactivation to reregister the object for finalization so
// that the finalizer doesn't get invoked during program shutdown on
// deactivated objects, which would result in an extra refcount
// decrement.
void ___Group_adapter::___deactivate()
{
    ___m_managed.makeStrong();___m_isActive=false;
    // unref occurs in osg::Referenced finalizer
}

比较繁琐的是对于每一个类都要作上述修改,可以进行统一替换。

当然以上问题的修改还是头痛医头脚痛医脚,每次osg版本升级也很麻烦,更好的方式还是修改osgDotNet源码,由于时间和水平问题,没有去研究osgDotNet的代码,也希望有人能早日把osgDotNet升级新的版本:)

该用户从未签到

发表于 2009-12-22 17:10:49 | 显示全部楼层
支持一下,呵呵。不过osgDotNet库看似已经不会再更新了,未来OSG的脚本绑定方式应当是以osgSWIG为蓝本
您需要登录后才可以回帖 登录 | 注册

本版积分规则

OSG中国官方论坛-有您OSG在中国才更好

网站简介:osgChina是国内首个三维相关技术开源社区,旨在为国内更多的技术开发人员提供最前沿的技术资讯,为更多的三维从业者提供一个学习、交流的技术平台。

联系我们

  • 工作时间:09:00--18:00
  • 反馈邮箱:1315785073@qq.com
快速回复 返回顶部 返回列表