使用 SWIG 从 C++ 初始化对 C# 共享指针的引用

Posted

技术标签:

【中文标题】使用 SWIG 从 C++ 初始化对 C# 共享指针的引用【英文标题】:Initialize a reference to a C# shared pointer from C++ using SWIG 【发布时间】:2017-07-26 08:07:06 【问题描述】:

我正在尝试在 C++ 中初始化对从 C# 传递的 boost 共享指针的引用,同时尝试维护继承树。我希望能够在 C# 中创建一个变量,然后将其传递给我的 DLL,该 DLL 将在内部实例化它(这样我就可以在同一上下文中管理所有内存分配)。

这是我的 C++ 测试用例(在文件 SwigTest.h 中):

#include <string>
#include <boost/shared_ptr.hpp>

template<class T>
using SRef = boost::shared_ptr<T>;

class FactoryClass

   public:
      FactoryClass() 
      ~FactoryClass() 

      //Normally I would only like to use the return value, but SWIG uses polymorphism to simulate templates
      //and polymorphism on the return type only is not supported by C#. (I get a Identifier 'getReference' redefined error on SwigTest.i compilation).
      //So I thought I’d pass the SRef& as a parameter instead of having no parameters at all.
       template <class  T>
       static SRef<T> getReference( SRef<T>& inout )  inout = SRef<T>( new T(1) ); return inout; 

       void testing( int& toto ) ;
;

class BaseClass

   public:
      BaseClass()  m_flag = -1; ;
      BaseClass(int i)  m_flag = i; ;
      virtual ~BaseClass() ;

      virtual std::string getName() = 0;

      int m_flag;
;

class FirstClass : public BaseClass

   public:
      FirstClass() ;
      FirstClass(int i) : BaseClass( i ) ;
      virtual ~FirstClass() ;

      virtual std::string getName()  return "First Class"; ;
;

class SecondClass : public BaseClass

   public:
      SecondClass() ;
      SecondClass(int i) : BaseClass( i ) ;
      virtual ~SecondClass() ;

      virtual std::string getName()  return "Second Class"; ;
;

这是我当前的 SWIG 接口文件:

%module SwigTest
%include "std_string.i"
%include "boost_shared_ptr.i"

%
#include "../include/SwigTest.h"
%

%shared_ptr( BaseClass );
%shared_ptr( FirstClass );
%shared_ptr( SecondClass );

%include "../include/SwigTest.h"

%template(BoostBasePtr) boost::shared_ptr<BaseClass>;
%template(SharedBase) SRef<BaseClass>;

%template(BoostFirstPtr) boost::shared_ptr<FirstClass>;
%template(SharedFirst)SRef<FirstClass>;
%template(getReference)FactoryClass::getReference<FirstClass>;

%template(BoostSecondPtr) boost::shared_ptr<SecondClass>;
%template(SharedSecond)SRef<SecondClass>;
%template(getReference)FactoryClass::getReference<SecondClass>;

以下是 C# 中有效和无效的内容:

public class SwigInterface

   private FirstClass m_first;
   private SecondClass m_second = new SecondClass(-2);

   void TestSwig ()
   
      m_first = FactoryClass.getReference( m_first ); //The returned reference is copied from C++
      FactoryClass.getReference( m_second ); //m_second is never changed

      FirstClass anotherFirst = new FirstClass(-1) ;
      BaseClass bc = FactoryClass.getReference( anotherFirst); //bc does equal the new instance of FirstClass but anotherFirst is not correctly replaced

       //This is the case I would ideally want to use
       SecondClass nothing ;
       FactoryClass.getReference( nothing ) ; //NullReferenceException (because the SWIG generated interface uses nothing.CPtr() to pass by pointer as it does with all refs).
    

我尝试了很多 %typename 体操来将 FactoryClass.getReference( FirstClass ) 的签名替换为 FactoryClass.getReference( out FirstClass )(甚至是 ref FirstClass),但我找不到正确的方法。

在一个完美的世界中,我只想在 C# 中的 FactoryClass 中生成一个 void getReference&lt;T&gt;( ref T inout ),但即使使用 SWIG %extend 关键字添加方法也不会生成它,因为我必须实例化我需要的每个模板.

【问题讨论】:

不应该在shared_ptr&lt;FirstClass&gt; 等而不是FirstClass 上模板化getReference 函数吗? 如果我理解正确,则没有必要,因为 SWIG 接口中的 %shared_ptr( FirstClass ); 指令已经将 FirstClass 的所有实例包装在一个共享指针中。但是,如果您有这样的解决方案,我会非常乐意阅读它:) 【参考方案1】:

如果我理解您的意思,您已达到 SWIG 限制。我遇到了同样的问题。每个指针(无论是否智能)在 C# 中都会丢失一次多态性。

您需要使用 %pragma(csharp) 指令在 C# 端为您的 BaseClass 提供一个特殊的工厂。然后你需要用这样的 csout 类型映射来映射你的指针:

%typemap(csout, excode=SWIGEXCODE)
  BaseClass*, std::shared_ptr<BaseClass>, std::shared_ptr<BaseClass> & 
    System.IntPtr cPtr = $imcall;
    // The following line is a call to your special factory
    BaseClass ret = $imclassname.createBaseClass(cPtr, $owner);$excode
    return ret;

你的工厂可能是这样的:

%pragma(csharp) imclasscode=%
    public static BaseClass createBaseClass(System.IntPtr cPtr, bool owner)
    
        BaseClass ret = null;
        if (cPtr == System.IntPtr.Zero) 
          return ret;
        
        string name = ($imclassname.BaseClass_getName(new System.Runtime.InteropServices.HandleRef(null, cPtr)));
        switch (name)
        
        case "First Class":
            return new FirstClass(cPtr, owner);
        case "Second Class":
            return new SecondClass(cPtr, owner);
        default:
            return null;
        
    
%

我没有测试它,也许有一些东西需要改变或适应。但它应该可以工作。

【讨论】:

感谢@QuentinS 的回复。但如果我真的理解你的提议,它并不能解决我的问题。你的建议是在 C# 中创建另一个完全独立的工厂,而我想在 C++ DLL 中创建我的实例,如果我误解了你,我很抱歉。 是的,但我的回答并没有在 C# 中创建实例,它只是为 C++ 实例创建了一个适当的代理类。如您所见,我使用正确的 IntPtr (new FirstClass(cPtr, owner)) 调用 SWIG 生成的内部构造函数,它将正确包装 C++ 实例。我以为这是你的意思,我的错。 这是我的意思,你说得对,我没有完全理解你的答案。 cPtrowner 参数的用途是什么?此外,在这个例子中,我展示了非常简单的类,但我的最终“产品”将具有更复杂的数十个类的多个工厂,因此用 C# 重写整个工厂也可能很麻烦。该解决方案并不理想,但如果它是使其工作的唯一方法,我可能别无选择。我会调查一下,谢谢@Quentin cPtr 是指向 C++ 实例的指针。我假设不知道所有者代表什么,但需要调用 SWIG 生成的构造函数。我给你的答案是一个起点,简单易懂。我做了一个相当复杂的实现,使用动态生成的字典来避免 switch case。但是,对于基于基类的每个系列,您需要编写并调整它们以适应 SWIG 指令。祝你好运! 谢谢@Quentin,我会看看我能用你提出的解决方案做什么。

以上是关于使用 SWIG 从 C++ 初始化对 C# 共享指针的引用的主要内容,如果未能解决你的问题,请参考以下文章

使用 SWIG 时如何将 int 数组和 List<string> 作为参数从 C# 传递给 C++

将字符串 (const char*) 从 C++ 传递到 C# 时,SWIG_csharp_string_callback 会导致内存泄漏

如何使用 SWIG 从 C 调用 C# 方法?

Swig C++ to C#:如何从 C++ 包装类以使模板类中的方法在 C# 的派生类中可用?

SWIG:对 Java 的 wchar_t 支持

使用 SWIG,如何将 C++ void func(Class& out) 包装为 C# Class func()?