I am creating a C# library with some reusable code and was trying to create a method inside a method. I have a method like this:
public static void Method1()
{
// Code
}
What I would like to do is this:
public static void Method1()
{
public static void Method2()
{
}
public static void Method3()
{
}
}
Then I could choose either Method1.Method2 or Method1.Method3. Obviously the compiler isn't happy about this, any help is much appreciated. Thanks.
If by nested method, you mean a method that is only callable within that method (like in Delphi) you could use delegates.
public static void Method1()
{
var method2 = new Action(() => { /* action body */ } );
var method3 = new Action(() => { /* action body */ } );
//call them like normal methods
method2();
method3();
//if you want an argument
var actionWithArgument = new Action<int>(i => { Console.WriteLine(i); });
actionWithArgument(5);
//if you want to return something
var function = new Func<int, int>(i => { return i++; });
int test = function(6);
}
Yes, when C# 7.0 is released, Local Functions will allow you to do that. You will be able to have a method, inside a method as:
public int GetName(int userId)
{
int GetFamilyName(int id)
{
return User.FamilyName;
}
string firstName = User.FirstName;
var fullName = firstName + GetFamilyName(userId);
return fullName;
}
Note that public (and similar modifiers) are not supported C# programming guide:
Because all local functions are private, including an access modifier, such as the private keyword, generates compiler error CS0106, "
This answer was written before C# 7 came out. With C# 7 you can write local methods.
No, you can't do that. You could create a nested class:
public class ContainingClass
{
public static class NestedClass
{
public static void Method2()
{
}
public static void Method3()
{
}
}
}
You'd then call:
ContainingClass.NestedClass.Method2();
or
ContainingClass.NestedClass.Method3();
I wouldn't recommend this though. Usually it's a bad idea to have public nested types.
Can you tell us more about what you're trying to achieve? There may well be a better approach.
You can define delegates within your method with complete code and call them if you want.
public class MyMethods
{
public void Method1()
{
// defining your methods
Action method1 = new Action( () =>
{
Console.WriteLine("I am method 1");
Thread.Sleep(100);
var b = 3.14;
Console.WriteLine(b);
}
);
Action<int> method2 = new Action<int>( a =>
{
Console.WriteLine("I am method 2");
Console.WriteLine(a);
}
);
Func<int, bool> method3 = new Func<int, bool>( a =>
{
Console.WriteLine("I am a function");
return a > 10;
}
);
// calling your methods
method1.Invoke();
method2.Invoke(10);
method3.Invoke(5);
}
}
There is always an alternative of using a nested class within a class that will not be visible from outside and calling its methods, like:
public class SuperClass
{
internal static class HelperClass
{
internal static void Method2() {}
}
public void Method1 ()
{
HelperClass.Method2();
}
}
As of C# 7.0 you can do that:
public static void SlimShady()
{
void Hi([CallerMemberName] string name = null)
{
Console.WriteLine($"Hi! My name is {name}");
}
Hi();
}
This is called local functions, that is just what you were looking for.
I took the example from here, but further informatin can be found here and here.
Why you don't use classes?
public static class Helper
{
public static string MethodA()
{
return "A";
}
public static string MethodA()
{
return "A";
}
}
Now you can acces MethodA via
Helper.MethodA();
Older thread, but C# does have the concept of nested functions
Func<int> getCalcFunction(int total, bool useAddition)
{
int overallValue = 0;
if (useAddition)
{
Func<int> incrementer = new Func<int>(() =>
{
overallValue += total;
return overallValue;
});
return incrementer;
}
else
{
Func<int> decrementer = new Func<int>(() =>
{
overallValue -= total;
return overallValue;
});
return decrementer;
}
}
private void CalcTotals()
{
Func<int> decrem = getCalcFunction(30, false);
int a = decrem(); //result = -30
a = decrem(); //result = -60
Func<int> increm = getCalcFunction(30, true);
int b = increm(); //result = 30
b = increm(); //result = 60
}
Your nearly there
public static void Method1()
should be
public static class Method1{}
Don't you want to use nested class instead?
That's said, you seem to not respect the Single Responsibility Principle because you want a single method do more than one thing at a time.
Why don't you just Run a method within another
public void M1()
{
DO STUFF
}
public void M1()
{
DO STUFF
M1();
}
Related
As the title suggests, I'm trying to create a bunch of attributes but the code is getting repetitive and messy. I want to use the closure argument to make the code more compact.
According to the C API reference, the closure is a function pointer that provides additional information for getters/setters. I have not been able to find an example of it in use.
This is how I am currently using it:
static void closure_1() {};
static void closure_2() {};
...
static PyObject *
FOO_getter(FOO* self, void *closure) {
if (closure == &closure_1) {
return self->bar_1;
} else if (closure == &closure_2) {
return self->bar_2;
}
}
static int
FOO_setter(FOO* self, PyObject *value, void *closure) {
if (closure == &closure_1) {
if (somehow value is invalid) {
PyErr_SetString(PyExc_ValueError, "invalid value for bar_1.");
return -1;
}
} else if (closure == closure_2) {
if (somehow value is invalid) {
PyErr_SetString(PyExc_ValueError, "invalid value for bar_2.");
return -1;
}
}
return 0;
}
static PyGetSetDef FOO_getsetters[] = {
{"bar_1", (getter) FOO_getter, (setter) FOO_setter, "bar_1 attribute", closure_1},
{"bar_2", (getter) FOO_getter, (setter) FOO_setter, "bar_2 attribute", closure_2},
{NULL} /* Sentinel */
};
...
It works the way I want it to, but it looks more like a hack than something "pythonic". Is there a better way to handle this? e.g. call the closure in some way.
I guess this "closure" is used to pass an extra context to Foo_getter. It should be something that simplifies accessing members of Foo. Documentation it likely wrong. It should be "optional pointer", not "optional function pointer".
Consider passing offsets of the members. Offsets to struct members can be easily obtained with standard offsetof macro defined in stddef.h. It is a small unsigned integer that will fit to void* type.
static PyGetSetDef FOO_getsetters[] = {
{"bar_1", (getter) FOO_getter, (setter) FOO_setter, "bar_1 attribute", (void*)offsetof(FOO, bar_1)},
{"bar_2", (getter) FOO_getter, (setter) FOO_setter, "bar_2 attribute", (void*)offsetof(FOO, bar_2)},
{NULL} /* Sentinel */
};
Now the getter could be:
static PyObject *
FOO_getter(FOO* self, void *closure) {
// pointer to location where the FOO's member is stored
char *memb_ptr = (char*)self + (size_t)closure;
// cast to `PyObject**` because `mem_ptr` points to location where a pointer to `PyObject` is stored
return *(PyObject**)mem_ptr;
}
Use similar schema for the setter.
Despite the documentation, I'm assuming the closure can be any pointer you want. So how about passing an "object", seeing as C doesn't support closures (short of literally generating functions at run-time).
In an object, we can store the offset of the member in FOO, and a pointer to an attribute-specific validator.
typedef int (*Validator)(FOO *, const struct Attribute *, void *);
typedef struct Attribute {
const char *name;
size_t offset;
Validator validator;
} Attribute;
static PyObject **resolve_offset(FOO *self, const Attribute *attr) {
return (PyObject **)( ( (char *)self ) + attr->offset );
}
static PyObject *FOO_getter(FOO *self, void *_attr) {
const Attribute *attr = (const Attribute *)_attr;
return *resolve_offset(self, attr);
}
static int FOO_setter(FOO *self, PyObject *val, void *_attr) {
const Attribute *attr = (const Attribute *)_attr;
if (attr->validator(self, attr, val)) {
*resolve_offset(self, attr) = val;
return 0;
} else {
// Building the string to include attr->name is left to you.
PyErr_SetString(PyExc_ValueError, "invalid value.");
return -1;
}
}
static int FOO_bar_1_validator(FOO *self, const Attribute *attr, void *val) { ... }
static int FOO_bar_2_validator(FOO *self, const Attribute *attr, void *val) { ... }
#define ATTRIBUTE(name) \
static Attribute FOO_ ## name ## attr = { \
#name, \
offsetof(FOO, name), \
FOO_ ## name ## _validator \
};
ATTRIBUTE(bar_1);
ATTRIBUTE(bar_2);
#define PY_ATTR_DEF(name) { \
#name, \
(getter)FOO_getter, \
(setter)FOO_setter, \
#name " attribute", \
&(FOO_ ## name ## attr) \
}
static PyGetSetDef FOO_getsetters[] = {
PY_ATTR_DEF(bar_1),
PY_ATTR_DEF(bar_2),
{ NULL }
};
I originally wrote:
resolve_offset surely relies on undefined behaviour, but it should work fine. The alternative would be to have three functions in our attribute object (get, validate, set) instead of one, but that defies the point of the question.
But #tstanisl points out that it looks like it isn't UB. Awesome!
I'm doing project with beacon.
smartphone(android client) collect rssi nearby beacons and send to python(server)
< current application's function >
-> when new beacon signal captured, display beacon's rssi value, Mac address
-> connect with tcp and send to python server in real time (Problem!!)
when using method(send text message to python server) with buttonclickevent,
connecting and sending text message works well.
but when i use method with listview adapter(using ArrayMap),
connecting works well, but can't send rssi.
more detail, infinite loop for waiting data
I think there is a problem between using runOnUiThread and data sending but i'm not sure about it.
Mainactivity
package midascon.example.scanlist;
import com.hanvitsi.midascon.Beacon;
import com.hanvitsi.midascon.BeaconCallback;
import com.hanvitsi.midascon.MidasApplication;
import com.hanvitsi.midascon.manager.ContextManager;
import android.Manifest;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.StrictMode;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.widget.ListView;
public class MainActivity extends Activity implements BeaconCallback, Runnable
{
private static final int MY_PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 100;
private ContextManager contextManager;
private BeaconListAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (android.os.Build.VERSION.SDK_INT > 9) { StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy); }
checkPermission();
contextManager = getMidasApplication().getContextManager();
contextManager.getBeaconSettings().setMidasScanMode(false);
adapter = new BeaconListAdapter(getBaseContext()); // Beacon list adapter
ListView listView = (ListView) findViewById(R.id.listView);
listView.setAdapter(adapter);
}
#Override
public void onBeaconCallback(int status, Beacon beacon) // add beacon to list when beacon signal captured
{
switch (status) {
case STATUS_CODE_ENTER:
case STATUS_CODE_UPDATE:
if (adapter != null)
adapter.addBeacon(beacon);
break;
case STATUS_CODE_EXIT:
if (adapter != null)
adapter.removeBeacon(beacon);
break;
default:
break;
}
runOnUiThread(this);
}
public void checkPermission() // allow permission
{
if(checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
}
}
#Override
public void run() {
if (adapter != null)
adapter.notifyDataSetChanged();
}
// call name class setting by AndroidManifest.xml
public MidasApplication getMidasApplication() {
return (MidasApplication) getApplication();
}
#Override
protected void onResume() {
super.onResume();
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION)) {
ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.ACCESS_FINE_LOCATION }, MY_PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
}
else
{
ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.ACCESS_FINE_LOCATION }, MY_PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
}
}
else {
if (BluetoothAdapter.getDefaultAdapter().isEnabled()) {
// register callback
contextManager.setBeaconCallback(this);
contextManager.startLeScan();
} else {
contextManager.stopLeScan();
Intent settingsIntent = new Intent(android.provider.Settings.ACTION_BLUETOOTH_SETTINGS);
startActivity(settingsIntent);
}
}
}
#Override
protected void onPause() {
super.onPause();
contextManager.stopLeScan();
}
}
midas is company that made beacon
BeaconListAdapter
public class BeaconListAdapter extends BaseAdapter
{
private final LayoutInflater inflater;
private int count; //counting beacon
private final ArrayMap<String, Beacon> itemMap = new ArrayMap<String, Beacon>();
private final int padding;
public int CurBeaconHave = 2;
private Handler mHandler;
public int port = 9999;
public int initcnt = 0;
public int stackcnt = 0;
public int serveractivate = 0;
public int praccnt = 0;
public static String Sendrssi = "";
public Socket socket = null;
public BeaconListAdapter(Context context) {
super();
padding = (int) context.getResources().getDimension(R.dimen.activity_vertical_margin);
this.inflater = LayoutInflater.from(context);
}
public int addBeacon(Beacon beacon) {
synchronized (itemMap) {
itemMap.put(beacon.getMac(), beacon);
count = itemMap.size();
return count;
}
}
public int removeBeacon(Beacon beacon) {
synchronized (itemMap) {
itemMap.remove(beacon.getMac());
count = itemMap.size();
return count;
}
}
#Override
public int getCount() {
return count;
}
#Override
public Beacon getItem(int position) {
synchronized (itemMap) {
return itemMap.valueAt(position);
}
}
void ToPython() {
try {
String tmp = Sendrssi;
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())));
out.println(tmp);
out.flush();
Log.d("sendrssi ",tmp);
}
catch (Exception e) {
e.printStackTrace();
}
}
/*public void ToPython() {
try {
String tmp = Sendrssi;
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
out.write(tmp);
out.newLine();
out.flush();
Log.d("Sending. Rssi : ", tmp);
} catch (Exception e) {
e.printStackTrace();
}
}*/
void connect() {
Log.w("state", "connecting..");
Thread checkUpdate = new Thread() {
public void run() {
String newip = "192.168.0.4";
try {
socket = new Socket(newip, port);
Log.w("state ", "server connected");
serveractivate = 1;
} catch (IOException e1) {
Log.w("state ", "failed");
e1.printStackTrace();
System.out.println("error :" + e1.getMessage());
}
}
};
checkUpdate.start();
}
#Override
public long getItemId(int position) {
return position;
}
public void stopsocket()
{
try
{
socket.close();
} catch (IOException e){
e.printStackTrace();
}
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView textView = null;
if (convertView == null)
{
convertView = inflater.inflate(android.R.layout.simple_list_item_1, parent, false);
textView = (TextView) convertView.findViewById(android.R.id.text1);
textView.setBackgroundColor(Color.WHITE);
textView.setTextColor(Color.BLACK);
textView.setPadding(padding, padding, padding, padding);
convertView.setTag(textView);
}
else
{
textView = (TextView) convertView.getTag();
}
Beacon item = getItem(position);
int temprssi = item.getRssi();
while(serveractivate == 0)
{
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d("Connect State : ","wait for Server Connect...");
connect();
}
if(serveractivate == 1)
{
try {
socket.setTcpNoDelay(true);
} catch (SocketException e) {
e.printStackTrace();
}
}
int[] values = BeaconUtils.getAccelerometer(item);
textView.setText(String.format("[%s]\nMAC : %s\nRssi : %d\n", item.getType() == Beacon.TYPE_MIDAS ? "Midascon" : "Beacon", item.getMac(),temprssi));
if(count == CurBeaconHave)
{
if(stackcnt == CurBeaconHave)
{
ToPython();
stackcnt = 0;
Sendrssi = "";
}
else if(stackcnt != CurBeaconHave)
{
Sendrssi = Sendrssi + temprssi + " " ;//+ (temprssi - 3) + " " + (temprssi + 5) + " " + (temprssi - 7) + " ";
stackcnt++;
}
}
return convertView;
}
}
total code line is about 400 line, so i upload code that have problem i think.
thank you for reading and try to solve my problem
if you need another code i'll upload it
Note that i'm not good at english, so you may have difficult with reading and understand. sorry for that :(
problem solved.
It's not a normal method, so please be patient.
BeaconListadapter in connect method, i use thread.
but i don't use thread in Topython method.
because of this, there's problem with socket information when send data
so i delete thread in connect method and run and method
in main thread(i know this is idiot thing)
if you use like me, probably use connect and send data method in one thread
in java, FlatBuffers will generate name() method for enums, which is very helpful.
public static final String[] names = { "SUCCESS", "TIME_OUT", "UNKNOWN_ERROR", };
public static String name(int e) { return names[e]; }
so does it in c++
inline const char **EnumNamesResultCode() {
static const char *names[] = { "SUCCESS", "TIME_OUT", "UNKNOWN_ERROR", nullptr };
return names;
}
inline const char *EnumNameResultCode(ResultCode e) { return EnumNamesResultCode()[static_cast<int>(e)]; }
but in python, there is no such method. it just generate a pure class with some constants.
class ResultCode(object):
SUCCESS = 0
TIME_OUT = 1
UNKNOWN_ERROR = 2
how to generate name() method in python?
in other words, how can I get enum names in python?
If the class has a bunch of members, you can dir() the class and get the members. You probably want to filter out things that start with __. You can then create a mapping of the class[member_name] where member_name is what you want.
I'm having an issue where a python class, which is derived from a c++ base class using pybind11, is being immediately destructed (garbage collected). I would like C++ to take ownership of the dynamically allocated object, but I can't seem to make that happen. I've tried keep_alive, passing shared_ptr<> as py::class_ template argument, and py::return_value_policy... nothing is working. I suspect this is just user error.
This is a simplification of the real issue I'm having with a much larger code base that is architected similarly. Changing the architecture is not an option, so making this example work is critical for me.
I have two c++ classes that I have created python interfaces for using pybind11. Class A and B both have virtual methods, so they have corresponding trampoline classes to support inheritance. The user calls the B::Run() function which results in a dynamically allocated (via new) A object to be created. When I create specializations of these two classes in python, as shown below.... Segmentation fault because the B::aBase is destroyed immediately after B::Run being called.
Any Ideas how to fix this? Thanks in advance!
class A
{
public:
A(){};
virtual ~A()
{
std::cout << "In A::~A()\n";
};
virtual char* SayHello()
{
char* x = "\n\nHello from Class A\n\n";
return x;
}
};
class ATramploline : public A
{
public:
using A::A;
char* SayHello() override
{
PYBIND11_OVERLOAD( char*,A,SayHello,);
}
};
class B
{
public:
B()
{
std::cout << "In Class B Constructor\n";
}
void Run()
{
aBase = AllocateAnAClass();
std::cout << aBase->SayHello();
}
virtual ~B()
{
fprintf(stderr,"About to delete aBase");
delete aBase;
}
A* aBase;
virtual A* AllocateAnAClass()
{
return new A;
}
};
class BTramploline : public B
{
public:
using B::B;
A* AllocateAnAClass() override
{
PYBIND11_OVERLOAD( A*,B,AllocateAnAClass,);
}
};
PYBIND11_MODULE(TestModule,m)
{
py::class_<A,ATramploline>(m,"A")
.def(py::init<>(),py::return_value_policy::reference_internal)
.def("SayHello",&A::SayHello);
py::class_<B,BTramploline>(m,"B")
.def(py::init<>())
.def("Run",&B::Run)
.def("AllocateAnAClass",&B::AllocateAnAClass,py::return_value_policy::reference_internal);
}
#!/usr/bin/python3
from TestModule import A,B
class MyA(A):
def __init__(self):
super().__init__()
print("Done with MyA Constructor")
def SayHello(self):
return '\n\nHello from Class MyA\n\n'
class MyB(B):
def __init__(self):
super().__init__()
print("Done With MyB Constructor")
def AllocateAnAClass(self):
print("In MyB::AllocateAnAClass!!!")
return MyA()
#x = B()
#x.Run()
y = MyB()
y.Run()
print("done with test script\n")
The correct (I think) way to use std::shared_ptr<A> as the A holder is to add it to class_<A...> arguments.
You also want to replace every instance of A* with std::shared_ptr<A>, and new with std::make_shared. I think non-default return policies are not needed in this case, so I have removed them; YMMV.
Working module below (with minor errors corrected).
#include <pybind11/pybind11.h>
#include <memory>
#include <iostream>
namespace py = pybind11;
class A
{
public:
A(){};
A(const A&) { std::cout << "Copying A\n"; }
virtual ~A()
{
std::cout << "In A::~A()\n";
};
virtual const char* SayHello()
{
const char* x = "\n\nHello from Class A\n\n";
return x;
}
};
class ATrampoline : public A
{
public:
using A::A;
const char* SayHello() override
{
PYBIND11_OVERLOAD( const char*,A,SayHello,);
}
};
class B
{
public:
B()
{
std::cout << "In Class B Constructor\n";
}
B(const B&) { std::cout << "Copying B\n"; }
void Run()
{
aBase = AllocateAnAClass();
std::cout << aBase->SayHello();
}
virtual ~B()
{
}
std::shared_ptr<A> aBase;
virtual std::shared_ptr<A> AllocateAnAClass()
{
return std::make_shared<A>();
}
};
class BTrampoline : public B
{
public:
using B::B;
std::shared_ptr<A> AllocateAnAClass() override
{
PYBIND11_OVERLOAD(std::shared_ptr<A>,B,AllocateAnAClass,);
}
};
PYBIND11_MODULE(TestModule,m)
{
py::class_<A,std::shared_ptr<A>, ATrampoline>(m,"A")
.def(py::init<>())
.def("SayHello",&A::SayHello);
py::class_<B, BTrampoline>(m,"B")
.def(py::init<>())
.def("Run",&B::Run)
.def("AllocateAnAClass",&B::AllocateAnAClass);
}
py::nodelete was the solution. While n.m's answer DOES work, it would require going back and chaning all of the pointer in an existing libary to smart pointers, which isn't a viable option for me. Using py::nodelete allows me to do everything on the pybind11 side.
py::class_<A,ATramploline,std::unique_ptr<A,py::nodelete> >(m,"A")
.def(py::init<>())
.def("SayHello",&A::SayHello);
namespace test_py
{
class Event
{
public:
enum Type { BEGIN = 0, RESULT, END };
Type get_type( ) const { return m_type; }
protected:
Event( ) { }
~Event( ) { }
Type m_type;
};
class EventBegin : public Event
{
public:
EventBegin( ) { m_type = Event::BEGIN; }
~EventBegin( ) {}
};
class EventResult : public Event
{
public:
EventResult( int result ) { m_type = Event::RESULT; m_result = result; }
~EventResult( ) {}
int get_result( ) { return m_result; }
protected:
int m_result;
};
class EventEnd : public Event
{
public:
EventEnd( ) { m_type = Event::END; }
~EventEnd( ) {}
};
class EventListener
{
public:
virtual void on_event( const Event& event ) = 0;
};
struct EventListenerWrap: EventListener, py::wrapper< EventListener >
{
void
on_event( const Event& event )
{
this->get_override( "on_event" )( event );
}
};
BOOST_PYTHON_MODULE( test_py )
{
{
py::scope outer = py::class_< Event, boost::noncopyable >( "Event", py::no_init )
.add_property( "event_type", &Event::get_type );
py::enum_< Event::Type >( "EventType" )
.value( "BEGIN", Event::BEGIN )
.value( "RESULT", Event::RESULT )
.value( "END", Event::END )
.export_values( );
}
{
py::class_< EventBegin, py::bases< Event > >( "EventBegin" );
}
{
py::class_< EventResult, py::bases< Event > >( "EventResult", py::no_init )
.def( py::init< int >( ( py::arg( "result" ) ) ) )
.add_property( "result", &EventResult::get_result );
}
{
py::class_< EventEnd, py::bases< Event > >( "EventEnd" );
}
{
py::class_< EventListenerWrap, boost::noncopyable >( "EventListener", py::no_init )
.def( "on_event", py::pure_virtual( &EventListener::on_event ) );
}
}
}
I have a protected constructor and destructor in Event base class and cannot change that.
In Python 2.7 I need to derive from EventListener class and send pointer back to C++ code.
During compilation I got error like that:
/boost/python/detail/destroy.hpp: In instantiation of ‘static void boost::python::detail::value_destroyer<false>::execute(const volatile T*) [with T = test_py::Event]’:
/boost/python/detail/destroy.hpp:95:36: required from ‘void boost::python::detail::destroy_referent_impl(void*, T& (*)()) [with T = const test_py::Event]’
/boost/python/detail/destroy.hpp:101:39: required from ‘void boost::python::detail::destroy_referent(void*, T (*)()) [with T = const test_py::Event&]’
/boost/python/converter/rvalue_from_python_data.hpp:135:71: required from ‘boost::python::converter::rvalue_from_python_data<T>::~rvalue_from_python_data() [with T = const test_py::Event&]’
/boost/python/converter/arg_from_python.hpp:107:8: required from ‘PyObject* boost::python::detail::caller_arity<2u>::impl<F, Policies, Sig>::operator()(PyObject*, PyObject*) [with F = void (test_py::EventListener::*)(const test_py::Event&); Policies = boost::python::default_call_policies; Sig = boost::mpl::vector3<void, test_py::EventListener&, const test_py::Event&>; PyObject = _object]’
/boost/python/object/py_function.hpp:38:33: required from ‘PyObject* boost::python::objects::caller_py_function_impl<Caller>::operator()(PyObject*, PyObject*) [with Caller = boost::python::detail::caller<void (test_py::EventListener::*)(const test_py::Event&), boost::python::default_call_policies, boost::mpl::vector3<void, test_py::EventListener&, const test_py::Event&> >; PyObject = _object]’
EventListener.cpp:193:1: required from here
EventListener.cpp:18:5: error: ‘test_py::Event::~Event()’ is protected
~Event( ) { }
^
In file included from /boost/python/converter/rvalue_from_python_data.hpp:10:0,
from /boost/python/converter/registry.hpp:9,
from /boost/python/converter/registered.hpp:8,
from /boost/python/object/make_instance.hpp:10,
from /boost/python/object/make_ptr_instance.hpp:8,
from /boost/python/to_python_indirect.hpp:11,
from /boost/python/converter/arg_to_python.hpp:10,
from /boost/python/call.hpp:15,
from /boost/python/object_core.hpp:14,
from /boost/python/object/class.hpp:9,
from /boost/python/class.hpp:13,
from ../../defs.hpp:6,
from ../defs.hpp:3,
from defs.hpp:3,
from EventListener.cpp:1:
/boost/python/detail/destroy.hpp:33:9: error: within this context
p->~T();
^
py::scope outer = py::class_< Event, boost::noncopyable >( "Event", py::no_init )
.add_property( "event_type", &Event::get_type );
First glance tells me you have a problem here. py::class_<Event, ...> only knows about binding to the Event, which has the protected destructor.
You're going to have to wrap Event in a class that exposes the destructor publically.
If that's not possible (because you cant change the definition of EventBegin, EventEnd etc for example) then you're going to have to write a polymorphic container that holds on to the derived classes through its own internal interface, internally treating the events as non-polymorphic objects.
This is not as difficult as it sounds:
#include <memory>
namespace test_py
{
class Event
{
public:
enum Type { BEGIN = 0, RESULT, END };
Type get_type( ) const { return m_type; }
protected:
Event( ) { }
~Event( ) { }
Type m_type;
};
class EventBegin : public Event
{
public:
EventBegin( ) { m_type = Event::BEGIN; }
~EventBegin( ) {}
};
class EventResult : public Event
{
public:
EventResult( int result ) { m_type = Event::RESULT; m_result = result; }
~EventResult( ) {}
int get_result( ) { return m_result; }
protected:
int m_result;
};
class EventEnd : public Event
{
public:
EventEnd( ) { m_type = Event::END; }
~EventEnd( ) {}
};
class EventProxy
{
// define an interface for turning a non-polymorphic event
// into a polymorphic one
struct concept
{
virtual const Event* as_event() const = 0;
virtual ~concept() = default;
};
// define a model to support the polymorphic interface for a
// non-polymorphic concrete object
template<class T> struct model : concept
{
template<class...Args> model(Args&&... args)
: _event(std::forward<Args>(args)...)
{}
const Event* as_event() const override {
return &_event;
}
T _event;
};
// construct the model that contains any Event
template<class T>
EventProxy(std::shared_ptr<T> ptr)
: _impl(std::move(ptr))
{}
public:
// T should be derived from Event...
template<class T, class...Args>
static EventProxy create(Args&&... args)
{
return EventProxy(std::make_shared<model<T>>(std::forward<Args>(args)...));
}
// simply return the address of the internal non-polymorphic event
const Event* as_event() const {
return _impl->as_event();
}
// return a shared pointer that points to the internal Event BUT
// defers lifetime ownership to our internal shared_ptr to
// our model. This means we never invoke the polymorphic
// destructor of Event through the protected interface.
std::shared_ptr<const Event> as_shared_event() const {
return std::shared_ptr<const Event>(_impl, _impl->as_event());
}
private:
// lifetime of the proxy is owned by this shared_ptr.
std::shared_ptr<concept> _impl;
};
}
// a quick test.
auto main() -> int
{
auto ep = test_py::EventProxy::create<test_py::EventBegin>();
const test_py::Event* p = ep.as_event();
std::shared_ptr<const test_py::Event> sp = ep.as_shared_event();
}
When exposing a function, Boost.Python will generate converters for each of the arguments. For arguments with types T and T&, the resulting Python converter will hold a copy of the object, and hence needs access to the copy-constructor and destructor. The rationale for this behavior is to prevent accidentally exposing dangling references. The same holds true when passing C++ arguments to Python.
This behavior presents a problem when:
exposing EventListener::on_event(const Event&), as Boost.Python is attempting to create an object that will hold a copy of the Event. To resolve this, consider exposing an auxiliary function that accepts a Event*, and then delegates to the original function.
passing an Event object to the Python in EventListenerWrap::on_event. To resolve this, consider wrapping the argument in boost::ref() or boost::python::ptr().
Be aware that by not creating copies, it creates the chance for dangling references. If the actual Event object is owned by Python, then its lifetime needs to be at least as long as any reference to it in C++. Likewise. If the actual Event object is owned by C++, then its lifetime needs to be at least as long as any reference to it in Python.
struct EventListenerWrap
: EventListener,
boost::python::wrapper<EventListener>
{
void on_event(const Event& event)
{
this->get_override("on_event")(boost::ref(event));
}
};
/// #brief Auxiliary function that will delegate to EventListener::on_event and
/// avoid by-value conversions at the language boundary. This prevents
/// prevents Boost.Python from creating instance holders that would hold
/// the value as an rvalue.
void event_listener_on_event_aux(EventListener& listener, Event* event)
{
return listener.on_event(*event);
}
BOOST_PYTHON_MODULE(...)
{
namespace python = boost::python;
python::class_<EventListenerWrap, boost::noncopyable>("EventListener")
.def("on_event", python::pure_virtual(&event_listener_on_event_aux))
;
}
An interesting detail is that boost::python::pure_virtual() will duplicate the signature of the function it wraps, but it will never actual invoke the wrapped function. Hence, the wrapped function could have a no-op/empty implementation, but providing an implementation is a good idea incase the pure_virtual designator is removed or if the auxiliary function is invoked directly.
Also, note that to allow a Python class to derive from a Boost.Python class, the Boost.Python must expose an __init__() method. Providing no methods, such as by using boost::python::no_init(), will result in a runtime error.
Here is a minimal complete example based on the original code that demonstrates exposing a class with protected constructor and destructor, two derived classes, and virtual dispatch of the derived classes through Boost.Python:
#include <iostream>
#include <string>
#include <boost/python.hpp>
// Legacy API.
class event
{
public:
std::string name;
protected:
event(std::string name) : name(name) {}
~event() = default;
};
struct event_a: event { event_a(): event("event_a") {} };
struct event_b: event { event_b(): event("event_b") {} };
class event_listener
{
public:
virtual void on_event(const event& event) = 0;
};
// Boost.Python helper types and functions.
struct event_listener_wrap
: event_listener,
boost::python::wrapper<event_listener>
{
void on_event(const event& event)
{
std::cout << "event_listener_wrap::on_event()" << std::endl;
this->get_override("on_event")(boost::ref(event));
}
};
/// #brief Auxiliary function that will delegate to EventListener::on_event and
/// avoid by-value conversions at the language boundary. This prevents
/// prevents Boost.Python from creating instance holders that would hold
/// the value as an rvalue.
void event_listener_on_event_wrap(event_listener& listener, event* event)
{
return listener.on_event(*event);
}
BOOST_PYTHON_MODULE(example)
{
namespace python = boost::python;
// Expose event and suppress default by-value converters and initailizers.
// This will prevent Boost.Python from trying to access constructors and
// destructors.
python::class_<event, boost::noncopyable>("Event", python::no_init)
.def_readonly("name", &event::name)
;
// Expose event_a and event_b as derived from event.
python::class_<event_a, python::bases<event>>("EventA");
python::class_<event_b, python::bases<event>>("EventB");
// Expose event_listener_wrap.
python::class_<event_listener_wrap, boost::noncopyable>("EventListener")
.def("on_event", python::pure_virtual(&event_listener_on_event_wrap))
;
// Expose a function that will perform virtual resolution.
python::def("do_on_event", &event_listener_on_event_wrap);
}
Interactive usage:
>>> import example
>>> class Listener(example.EventListener):
... def on_event(self, event):
... assert(isinstance(event, example.Event))
... print "Listener::on_event: ", event, event.name
...
>>> listener = Listener()
>>> listener.on_event(example.EventA())
Listener::on_event: <example.EventA object at 0x7f3bc1176368> event_a
>>> example.do_on_event(listener, example.EventB())
event_listener_wrap::on_event()
Listener::on_event: <example.Event object at 0x7f3bc1246fa0> event_b
When Python is directly aware of method, it will invoke it without passing through Boost.Python. Notice how listener.on_event() did not get dispatched through C++, and event object maintains its example.EventA type. On the other hand, when dispatching is forced into C++, upcasting will not occur. When Listener.on_event() is invoked through example.do_on_event(), the event object's type is example.Event and not example.EventB.