Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[C#] - inheritance lost in vector #1007

Closed
schullq opened this issue Jun 21, 2017 · 13 comments
Closed

[C#] - inheritance lost in vector #1007

schullq opened this issue Jun 21, 2017 · 13 comments
Labels

Comments

@schullq
Copy link
Contributor

schullq commented Jun 21, 2017

Hi,

I found out a situation where a C++ method returning a std::vector of pointers to class "A", once wrapped in C# returns a List-like class which lost all notion of inheritance.

Example:

C++ side:

class A { 
public:
    string name;
    void someMethod();
... }

class B : public A { ... }

class C : public A { ... }

std::vector<std::shared_ptr<A>> someRandomMethod();

C# side:

A_Vector = someRandomMethod()

Imagine I check there using the name property which class is at index 0, A, B or C. After that, I am sure what class it is (let's say it' class B). Then:

(B)A_Vector[0].someMethod();

This is the exception I get:

"Unable to cast object of type 'A' to type 'B'."

To go further, I tried some manipulations in the SWIG to understand from where this error could come. I wrote a little "ToArray()" method, that just copies the list in an array of templated type. So it uses the getitemcopy method implemented in the std_vector.i. With this, it works, but other way, when the wrapper uses the getitem which should get a const reference to the C++-side item, it doesn't.

@schullq
Copy link
Contributor Author

schullq commented Jun 21, 2017

For sure, I tried again, it seems that returning the element by copy preserves the inheritance, but by const reference it doesn't.
However, the reason why escapes me at all.

@schullq
Copy link
Contributor Author

schullq commented Jun 21, 2017

It seems it doesn't come from a wrong vector wrapper implementation, but from a problem that has already been fedback. Same issue in Java here;

@Deadpikle
Copy link

Heyo @schullq , have you tried https://stackoverflow.com/a/7160064/3938401? I'm using it in my C# SWIG-based project for casting from a parent to a child, and it may work similarly for you here.

public class SwigHelper
{
    public static T CastTo<T>(object from, bool cMemoryOwn)
    {
        System.Reflection.MethodInfo CPtrGetter = from.GetType().GetMethod("getCPtr", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
        return CPtrGetter == null ? default(T) : (T) System.Activator.CreateInstance
        (
            typeof(T),
            System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance,
            null,
            new object[] { ((HandleRef) CPtrGetter.Invoke(null, new object[] { from })).Handle, cMemoryOwn },
            null
        );
    }
}

@schullq
Copy link
Contributor Author

schullq commented Jun 22, 2017

Thanks for your answer.

However, I was aware of this way of doing, it just fixes the problem partially.
As explained there in the doc, I have a problem after creating a vector of objects using a factory. I fixed the factory so when I create one object, it works great and I can easely cast it. But it does'nt when I create a vector using this factory. That's why I am pretty sure the problem come from the vector.

To be more precise, I think the problem comes from the "getitem(int index)" method defined in std_vector.i.
There is also a method named "getitemcopy(int index)", which does exactly the same as getitem but return a copy of the type, when getitem returns a const ref of the type.
When i use getitemcopy, it works, but it doesn't when using getitem.

@ojwb ojwb added the C# label Jun 23, 2017
@wsfulton
Copy link
Member

@schullq this is a problem in your C++ code. Read around "object slicing".

@schullq
Copy link
Contributor Author

schullq commented Jun 23, 2017

I may have been unclear, but I have a vector of pointers to class A. So the rules of object slicing shouldn't happen. I am still trying to find the problem out anyway, maybe I missed something and I am wrong. I'll let you know.
PS: I edit the post, it is unclear that I use pointers.

@wsfulton
Copy link
Member

Are you using pointers or shared_ptr? If shared_ptr as you put in your edited post, you are not using the shared_ptr typemaps and the exception message in your post is not possible. I think it would help if you gave all the details of exactly what you are doing with a small standalone example.

@schullq
Copy link
Contributor Author

schullq commented Jun 30, 2017

Ok well:

readerprovider.hpp:

class ReaderProvider : public std::enable_shared_from_this < ReaderProvider >
{
public:
    vitrual ~ReaderProvider();

    virtual void release() = 0;

    virtual bool refreshReaderList() = 0;

    virtual const std::vector<std::shared_ptr<ReaderUnit>>& getReaderList() = 0;

    virtual std::string getName() = 0;
}

examplereaderprovider.hpp:

class ExampleReaderProvider : public ReaderProvider
{
public:
    ExampleReaderProvider();

    ~ExampleReaderProvider();

    virtual void release();

    virtual bool refreshReaderList();

    virtual const std::vector<std::shared_ptr<ReaderUnit>>& getReaderList();

    virtual std::string getName() { return "Example"; }

    std::string getExampleId() { return d_id; }

private:
    std::string d_id;
}

core.i

%{
#include <readerunit.hpp>
#include <readerprovider.hpp>
%}

%pragma(csharp) imclasscode=%{
	public static System.Collections.Generic.Dictionary<string, System.Type> readerUnitDictionary;

	public static ReaderUnit createReaderUnit(System.IntPtr cPtr, bool owner)
	{
		ReaderUnit ret = null;
		if (cPtr == System.IntPtr.Zero) {
		  return ret;
		}
		string rpt = ($imclassname.ReaderUnit_getRPType(new System.Runtime.InteropServices.HandleRef(null, cPtr)));
		if (readerUnitDictionary == null)
			readerUnitDictionary = createDictionary<ReaderUnit>();
        if (readerUnitDictionary.ContainsKey(rpt))
        {
            System.Reflection.BindingFlags flags = System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance;
            ret = (ReaderUnit)System.Activator.CreateInstance(readerUnitDictionary[rpt], flags, null, new object[] { cPtr, owner }, null);
        }
		return ret;
	}
%}

%typemap(csout, excode=SWIGEXCODE)
  ReaderUnit*, std::shared_ptr<ReaderUnit> {
    System.IntPtr cPtr = $imcall;
    ReaderUnit ret = core.createReaderUnit(cPtr, $owner);$excode
    return ret;
  }

%include <readerunit.hpp>
%include <readerprovider.hpp>

%template(ReaderProviderEnableShared) std::enable_shared_from_this<logicalaccess::ReaderProvider>;
%template(ReaderUnitVector) std::vector<std::shared_ptr<ReaderUnit> >;

readerconfig.cs (extract)

[...]
ReaderUnitVector readerUnits = selectedReaderProvider.getReaderList();
foreach (ReaderUnit ru in readerUnits)
{
    string id = string.Empty;
    if (ru.getName() == "Example")
          id = ((Example)ru).getExampleId();
}

Output:

"Unable to cast object of type 'A' to type 'B'."

@wsfulton
Copy link
Member

wsfulton commented Jul 2, 2017

This is definitely not a standalone example of the problem. I started fixing numerous errors and don't have time to finish guessing what you are doing. Using %inline, you should be able to provide one SWIG interface file for SWIG to use. Then provide a snippet of C# code using the resulting output.

@schullq
Copy link
Contributor Author

schullq commented Jul 3, 2017

Ok, sorry, my bad, I will give you a full standalone example in one interface.

@schullq
Copy link
Contributor Author

schullq commented Jul 6, 2017

Here is a little standalone, I tried to simplify as much as I could, to obtain the same issue I had, in the exact same case.

sample.i:


%module sample

%include <windows.i>
%include <std_string.i>
%include <std_vector.i>
%include <std_shared_ptr.i>

%{
#include <memory>
#include <vector>
#include <iostream>

class HumanFeature;
%}

namespace std {
    template <class T> class enable_shared_from_this {
    public:
        shared_ptr<T> shared_from_this();
        //shared_ptr<const T> shared_from_this() const;
	protected:
		enable_shared_from_this();
        enable_shared_from_this(const enable_shared_from_this &);
		~enable_shared_from_this();
    };
}

%shared_ptr(std::enable_shared_from_this<HumanFeature>);
%shared_ptr(HumanFeature);
%shared_ptr(PilotFeature);
%shared_ptr(SportFeature);
%shared_ptr(std::enable_shared_from_this<Human>);
%shared_ptr(Human);
%shared_ptr(Pilot);

%template(HumanEnableShared) std::enable_shared_from_this<Human>;
%template(HumanFeatureEnableShared) std::enable_shared_from_this<HumanFeature>;
%template(HumanFeaturePtrVector) std::vector<std::shared_ptr<HumanFeature> >;

%typemap(csout, excode=SWIGEXCODE)
  HumanFeature*, std::shared_ptr<HumanFeature> {
    System.IntPtr cPtr = $imcall;
    HumanFeature ret = samplePINVOKE.createHumanFeature(cPtr, $owner);$excode
    return ret;
}

%inline %{
class HumanFeature : public std::enable_shared_from_this<HumanFeature>
{
public:
  HumanFeature(std::string name) 
  {
	d_name = name;
  }

  virtual ~HumanFeature() {}
	
  std::string getName() { return d_name; }

  virtual int importanceLevel() const { return 0; }

private:
  std::string d_name;
};

class PilotFeature : public HumanFeature
{
public:
  PilotFeature(int iq_) : HumanFeature("PilotFeature")
  {
    iq = iq_;
  }

  virtual int importanceLevel() { return 7; }

  int getIQ() { return iq; }

private:
  int iq;
};

class SportFeature : public HumanFeature
{
public:
  SportFeature(int sportSkill_) : HumanFeature("SportFeature")
  {
    sportSkill = sportSkill_;
  }

  virtual int importanceLevel() { return 5; }

  int getSportSkill() { return sportSkill; }

private:
  int sportSkill;
};

class Human : public std::enable_shared_from_this<Human>
{
public:
  virtual void speak() = 0;

  virtual const std::vector<std::shared_ptr<HumanFeature> >& getFeaturesList() = 0;

  virtual std::shared_ptr<HumanFeature> getSpecialSkill(std::string type) = 0;

  virtual std::string getJob() = 0;
};

class Pilot : public Human
{
public:
  Pilot(int iq, int sportskill = 0)
  {
	addFeature(std::shared_ptr<PilotFeature>(new PilotFeature(iq)));
	if (sportskill != 0)
		addFeature(std::shared_ptr<SportFeature>(new SportFeature(sportskill)));
  }

  ~Pilot() {};

  virtual void speak() {
    std::cout << "To infinity and beyond!" << std::endl;
  }

  virtual const std::vector<std::shared_ptr<HumanFeature> >& getFeaturesList()
  {
    return d_features;
  }

  virtual std::shared_ptr<HumanFeature> getSpecialSkill(std::string type)
  {
	for (int i = 0; i < d_features.size(); i++)
	{
		if (d_features[i]->getName() == type)
			return d_features[i];
	}
	return NULL;
  }

  virtual std::string getJob() { return "Pilot"; }

  void addFeature(std::shared_ptr<HumanFeature> ft)
  {
    d_features.push_back(ft);
  }

  std::string getPilotId() { return d_id; }

private:

  std::string d_id;

  std::vector<std::shared_ptr<HumanFeature> > d_features;
};
%}

%pragma(csharp) imclasscode=%{

	public static HumanFeature createHumanFeature(System.IntPtr cPtr, bool owner)
	{
		HumanFeature ret = null;
		if (cPtr == System.IntPtr.Zero) {
		  return ret;
		}
		string fname = ($imclassname.HumanFeature_getName(new System.Runtime.InteropServices.HandleRef(null, cPtr)));
		switch (fname)
		{
		case "PilotFeature":
			ret = new PilotFeature(cPtr, owner);
			break;
		case "SportFeature":
			ret = new SportFeature(cPtr, owner);
			break;
		default:
			break;
		}
		return ret;
	}
%}

I compiled the interface using the following line:

swig -csharp -c++ -outdir "youroutputpath" sample.i

And finally a little test case in C#:

using System;
public class Program
{
    public static void Main(string[] arg)
    {
        Human human1 = new Pilot(114, 3);

        human1.speak();

        HumanFeaturePtrVector list = human1.getFeaturesList();

        HumanFeature hf1 = human1.getSpecialSkill("PilotFeature");

        Console.WriteLine(((PilotFeature)hf1).getName()); // This should work

        Console.WriteLine(((PilotFeature)hf1).importanceLevel()); // This should work

        for (int i = 0; i < list.Count; i++)
        {
            Console.WriteLine(hf1.importanceLevel());
            if (list[i].getName() == "PilotFeature")
                ((PilotFeature)list[i]).getIQ(); // Here is the issue
            else
                ((SportFeature)list[i]).getSportSkill(); // Here is the issue (never reached in the example)
        }
    }
}

Hope this time you will understand my point.

@wsfulton
Copy link
Member

list[i] calls HumanFeaturePtrVector.getitem c# function which does not have the call to createHumanFeature from your csout typemap. The fix is:

--- example-original.i	2017-07-17 18:05:13.413154777 +0100
+++ example.i	2017-07-17 19:18:26.688651748 +0100
@@ -33,17 +33,18 @@
 %shared_ptr(Human);
 %shared_ptr(Pilot);
 
-%template(HumanEnableShared) std::enable_shared_from_this<Human>;
-%template(HumanFeatureEnableShared) std::enable_shared_from_this<HumanFeature>;
-%template(HumanFeaturePtrVector) std::vector<std::shared_ptr<HumanFeature> >;
-
 %typemap(csout, excode=SWIGEXCODE)
+  std::shared_ptr<HumanFeature> &,
   HumanFeature*, std::shared_ptr<HumanFeature> {
     System.IntPtr cPtr = $imcall;
-    HumanFeature ret = samplePINVOKE.createHumanFeature(cPtr, $owner);$excode
+    HumanFeature ret = $modulePINVOKE.createHumanFeature(cPtr, $owner);$excode
     return ret;
 }
 
+%template(HumanEnableShared) std::enable_shared_from_this<Human>;
+%template(HumanFeatureEnableShared) std::enable_shared_from_this<HumanFeature>;
+%template(HumanFeaturePtrVector) std::vector<std::shared_ptr<HumanFeature> >;
+
 %inline %{
 class HumanFeature : public std::enable_shared_from_this<HumanFeature>
 {

Why you might ask? Should be clear if you run swig with the -E option or just look inside csharp\std_vector.i to see it returns by reference:

  template<class T> class vector {
  ...
      T const& getitem(int index) throw (std::out_of_range) {

I would customise all the other typemaps you have overridden in csharp\boost_shared_ptr.i. Please look at the docs at http://swig.org/Doc3.0/Typemaps.html especially the debugging typemap section.

@schullq
Copy link
Contributor Author

schullq commented Jul 18, 2017

This is totally right. Thanks a lot @wsfulton !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants