Our company is rewriting most of the legacy C code in C++11. (Which also means I am a C programmer learning C++). I need advice on message handlers.
We have distributed system - Server process sends a packed message over TCP to client process.
In C code this was being done:
- parse message based on type and subtype, which are always the first 2 fields
- call a handler as handler[type](Message *msg)
- handler creates temporary struct say, tmp_struct to hold the parsed values and ..
- calls subhandler[type][subtype](tmp_struct)
There is only one handler per type/subtype.
Moving to C++11 and mutli-threaded environment. The basic idea I had was to -
1) Register a processor object for each type/subtype combination. This is
actually a vector of vectors -
vector< vector >
class MsgProcessor {
// Factory function
virtual Message *create();
virtual Handler(Message *msg)
}
This will be inherited by different message processors
class AMsgProcessor : public MsgProcessor {
Message *create() override();
handler(Message *msg);
}
2) Get the processor using a lookup into the vector of vectors.
Get the message using the overloaded create() factory function.
So that we can keep the actual message and the parsed values inside the message.
3) Now a bit of hack, This message should be send to other threads for the heavy processing. To avoid having to lookup in the vector again, added a pointer to proc inside the message.
class Message {
const MsgProcessor *proc; // set to processor,
// which we got from the first lookup
// to get factory function.
};
So other threads, will just do
Message->proc->Handler(Message *);
This looks bad, but hope, is that this will help to separate message handler from the factory. This is for the case, when multiple type/subtype wants to create same Message, but handle it differently.
I was searching about this and came across :
http://www.drdobbs.com/cpp/message-handling-without-dependencies/184429055?pgno=1
It provides a way to completely separate the message from the handler. But I was wondering if my simple scheme above will be considered an acceptable design or not. Also is this a wrong way of achieving what I want?
Efficiency, as in speed, is the most important requirement from this application. Already we are doing couple of memory Jumbs => 2 vectors + virtual function call the create the message. There are 2 deference to get to the handler, which is not good from caching point of view I guess.
Though your requirement is unclear, I think I have a design that might be what you are looking for.
Check out http://coliru.stacked-crooked.com/a/f7f9d5e7d57e6261 for the fully fledged example.
It has following components:
An interface class for Message processors IMessageProcessor.
A base class representing a Message. Message
A registration class which is essentially a singleton for storing the message processors corresponding to (Type, Subtype) pair. Registrator. It stores the mapping in a unordered_map. You can also tweak it a bit for better performance. All the exposed API's of Registrator are protected by a std::mutex.
Concrete implementations of MessageProcessor. AMsgProcessor and BMsgProcessor in this case.
simulate function to show how it all fits together.
Pasting the code here as well:
/*
* http://stackoverflow.com/questions/40230555/efficient-message-factory-and-handler-in-c
*/
#include <iostream>
#include <vector>
#include <tuple>
#include <mutex>
#include <memory>
#include <cassert>
#include <unordered_map>
class Message;
class IMessageProcessor
{
public:
virtual Message* create() = 0;
virtual void handle_message(Message*) = 0;
virtual ~IMessageProcessor() {};
};
/*
* Base message class
*/
class Message
{
public:
virtual void populate() = 0;
virtual ~Message() {};
};
using Type = int;
using SubType = int;
using TypeCombo = std::pair<Type, SubType>;
using IMsgProcUptr = std::unique_ptr<IMessageProcessor>;
/*
* Registrator class maintains all the registrations in an
* unordered_map.
* This class owns the MessageProcessor instance inside the
* unordered_map.
*/
class Registrator
{
public:
static Registrator* instance();
// Diable other types of construction
Registrator(const Registrator&) = delete;
void operator=(const Registrator&) = delete;
public:
// TypeCombo assumed to be cheap to copy
template <typename ProcT, typename... Args>
std::pair<bool, IMsgProcUptr> register_proc(TypeCombo typ, Args&&... args)
{
auto proc = std::make_unique<ProcT>(std::forward<Args>(args)...);
bool ok;
{
std::lock_guard<std::mutex> _(lock_);
std::tie(std::ignore, ok) = registrations_.insert(std::make_pair(typ, std::move(proc)));
}
return (ok == true) ? std::make_pair(true, nullptr) :
// Return the heap allocated instance back
// to the caller if the insert failed.
// The caller now owns the Processor
std::make_pair(false, std::move(proc));
}
// Get the processor corresponding to TypeCombo
// IMessageProcessor passed is non-owning pointer
// i.e the caller SHOULD not delete it or own it
std::pair<bool, IMessageProcessor*> processor(TypeCombo typ)
{
std::lock_guard<std::mutex> _(lock_);
auto fitr = registrations_.find(typ);
if (fitr == registrations_.end()) {
return std::make_pair(false, nullptr);
}
return std::make_pair(true, fitr->second.get());
}
// TypeCombo assumed to be cheap to copy
bool is_type_used(TypeCombo typ)
{
std::lock_guard<std::mutex> _(lock_);
return registrations_.find(typ) != registrations_.end();
}
bool deregister_proc(TypeCombo typ)
{
std::lock_guard<std::mutex> _(lock_);
return registrations_.erase(typ) == 1;
}
private:
Registrator() = default;
private:
std::mutex lock_;
/*
* Should be replaced with a concurrent map if at all this
* data structure is the main contention point (which I find
* very unlikely).
*/
struct HashTypeCombo
{
public:
std::size_t operator()(const TypeCombo& typ) const noexcept
{
return std::hash<decltype(typ.first)>()(typ.first) ^
std::hash<decltype(typ.second)>()(typ.second);
}
};
std::unordered_map<TypeCombo, IMsgProcUptr, HashTypeCombo> registrations_;
};
Registrator* Registrator::instance()
{
static Registrator inst;
return &inst;
/*
* OR some other DCLP based instance creation
* if lifetime or creation of static is an issue
*/
}
// Define some message processors
class AMsgProcessor final : public IMessageProcessor
{
public:
class AMsg final : public Message
{
public:
void populate() override {
std::cout << "Working on AMsg\n";
}
AMsg() = default;
~AMsg() = default;
};
Message* create() override
{
std::unique_ptr<AMsg> ptr(new AMsg);
return ptr.release();
}
void handle_message(Message* msg) override
{
assert (msg);
auto my_msg = static_cast<AMsg*>(msg);
//.... process my_msg ?
//.. probably being called in some other thread
// Who owns the msg ??
(void)my_msg; // only for suppressing warning
delete my_msg;
return;
}
~AMsgProcessor();
};
AMsgProcessor::~AMsgProcessor()
{
}
class BMsgProcessor final : public IMessageProcessor
{
public:
class BMsg final : public Message
{
public:
void populate() override {
std::cout << "Working on BMsg\n";
}
BMsg() = default;
~BMsg() = default;
};
Message* create() override
{
std::unique_ptr<BMsg> ptr(new BMsg);
return ptr.release();
}
void handle_message(Message* msg) override
{
assert (msg);
auto my_msg = static_cast<BMsg*>(msg);
//.... process my_msg ?
//.. probably being called in some other thread
//Who owns the msg ??
(void)my_msg; // only for suppressing warning
delete my_msg;
return;
}
~BMsgProcessor();
};
BMsgProcessor::~BMsgProcessor()
{
}
TypeCombo read_from_network()
{
return {1, 2};
}
struct ParsedData {
};
Message* populate_message(Message* msg, ParsedData& pdata)
{
// Do something with the message
// Calling a dummy populate method now
msg->populate();
(void)pdata;
return msg;
}
void simulate()
{
TypeCombo typ = read_from_network();
bool ok;
IMessageProcessor* proc = nullptr;
std::tie(ok, proc) = Registrator::instance()->processor(typ);
if (!ok) {
std::cerr << "FATAL!!!" << std::endl;
return;
}
ParsedData parsed_data;
//..... populate parsed_data here ....
proc->handle_message(populate_message(proc->create(), parsed_data));
return;
}
int main() {
/*
* TODO: Not making use or checking the return types after calling register
* its a must in production code!!
*/
// Register AMsgProcessor
Registrator::instance()->register_proc<AMsgProcessor>(std::make_pair(1, 1));
Registrator::instance()->register_proc<BMsgProcessor>(std::make_pair(1, 2));
simulate();
return 0;
}
UPDATE 1
The major source of confusion here seems to be because the architecture of the even system is unknown.
Any self respecting event system architecture would look something like below:
A pool of threads polling on the socket descriptors.
A pool of threads for handling timer related events.
Comparatively small number (depends on application) of threads to do long blocking jobs.
So, in your case:
You will get network event on the thread doing epoll_wait or select or poll.
Read the packet completely and get the processor using Registrator::get_processor call.
NOTE: get_processor call can be made without any locking if one can guarantee that the underlying unordered_map does not get modified i.e no new inserts would be made once we start receiving events.
Using the obtained processor we can get the Message and populate it.
Now, this is the part that I am not that sure of how you want it to be. At this point, we have the processor on which you can call handle_message either from the current thread i.e the thread which is doing epoll_wait or dispatch it to another thread by posting the job (Processor and Message) to that threads receiving queue.
Related
In summary, I have a class inherited from std::enabled_shared_from_this, and there is a factory method return an std::unique_ptr of it. In another class, I convert the std::unique_ptr of the previous class object to std::shared_ptr, and then I call shared_from_this(), which then throws std::bad_weak_ptr. The code is shown below:
#include <memory>
#include <iostream>
struct Executor;
struct Executor1 {
Executor1(const std::shared_ptr<Executor>& executor,
int x): parent(executor) {
std::cout << x << std::endl;
}
std::shared_ptr<Executor> parent;
};
struct Backend {
virtual ~Backend() {}
virtual void run() = 0;
};
struct Executor: public Backend, public std::enable_shared_from_this<Executor> {
const int data = 10;
virtual void run() override {
Executor1 x(shared_from_this(), data);
}
};
// std::shared_ptr<Backend> createBackend() {
std::unique_ptr<Backend> createBackend() {
return std::make_unique<Executor>();
}
class MainInstance {
private:
std::shared_ptr<Backend> backend;
public:
MainInstance(): backend(createBackend()) {
backend->run();
}
};
int main() {
MainInstance m;
return 0;
}
Indeed changing std::unique_ptr<Backend> createBackend() to std::shared_ptr<Backend> createBackend() can solve the problem, but as I understand, in general, the factory pattern should prefer return a unique_ptr. Considering a good pratice of software engineering, is there a better solution?
[util.smartptr.shared.const]/1 In the constructor definitions below, enables shared_from_this with p, for a pointer p of type Y*, means that if Y has an unambiguous and accessible base class that is a specialization of enable_shared_from_this (23.11.2.5), then [magic happens that makes shared_from_this() work for *p - IT]
template <class Y, class D> shared_ptr(unique_ptr<Y, D>&& r);
[util.smartptr.shared.const]/29 Effects: ... equivalent to shared_ptr(r.release(), r.get_deleter())...
template<class Y, class D> shared_ptr(Y* p, D d);
[util.smartptr.shared.const]/10 Effects: ... enable shared_from_this with p
Your example executes std::shared_ptr<Backend>(uptr) where uptr is std::unique_ptr<Backend>, which is equivalent to std::shared_ptr<Backend>(p, d) where p is of type Backend*. This constructor enables shared_from_this with p - but that's a no-op, as Backend doesn't have an unambiguous and accessible base class that is a specialization of enable_shared_from_this
In order for Executor::enable_from_this to work, you need to pass to a shared_ptr constructor a pointer whose static type is Executor* (or some type derived therefrom).
Ok, I find a simple solution, that is, using auto as the return type of the factory function, instead of std::unique_ptr or std::shared_ptr, and keeping std::make_unique inside the factory function. The factory function createBackend should be:
auto createBackend() {
return std::make_unique<Executor>();
}
In this case, the return type can be automatically determined, although I don't know how it works exactly. This code can return either unique_ptr or shared_ptr, which should be better than just using shared_ptr. I tested clang and gcc, and both of them worked, but I am still not sure if this is gauranteed by the type deduction and the implicit conversion.
Update:
Actually, I have found that auto deduces the return type above as std::unique_ptr<Executor> instead of std::unique_ptr<Backend>, which might be the reason why the code works. But using auto has an issue: if you return the smart pointer in an if-else block, where the return type varies depending on some parameters, then auto cannot determine the type. For example:
std::unique_ptr<Backend> createBackend(int k = 0) {
if (k == 0) {
return std::make_unique<Executor>();
}
else {
return std::make_unique<Intepreter>();
}
}
Here, both Executor and Intepreter derive from Backend. I think a correct solution includes:
Inherit Backend instead of its derived classes from std::enable_shared_from_this;
Use dynamic_pointer_cast<Derived class> to cast the shared_ptr to derived class after shared_from_this.
The full code is listed in:
https://gist.github.com/HanatoK/8d91a8ed71271e526d9becac0b20f758
Historically, I've been using trait classes to hold information and apply that into a "generic" function that runs the same "algorithm." Only differed by the trait class. For example: https://onlinegdb.com/ryUo7WRmN
enum selector { SELECTOR1, SELECTOR2, SELECTOR3, };
// declaration
template < selector T> struct example_trait;
template<> struct example_trait<SELECTOR1> {
static constexpr size_t member_var = 3;
static size_t do_something() { return 0; }
};
template<> struct example_trait<SELECTOR2> {
static constexpr size_t member_var = 5;
static size_t do_something() { return 0; }
};
// pretend this is doing something useful but common
template < selector T, typename TT = example_trait<T> >
void function() {
std::cout << TT::member_var << std::endl;
std::cout << TT::do_something() << std::endl;
}
int main()
{
function<SELECTOR1>();
function<SELECTOR2>();
return 0;
}
I'm not sure how to create "generic" algorithms this when dealing with polymorphic classes.
For example: https://onlinegdb.com/S1hFLGC7V
Below I have created an inherited class hierarchy. In this example I have a base catch-all example that defaults all the parameters to something (0 in this case). And then each derived class sets overrides specific methods.
#include <iostream>
#include <memory>
#include <type_traits>
#include <assert.h>
using namespace std;
struct Base {
virtual int get_thing_one() {
return 0;
}
virtual int get_thing_two() {
return 0;
}
virtual int get_thing_three() {
return 0;
}
virtual int get_thing_four() {
return 0;
}
};
struct A : public Base {
virtual int get_thing_one() override {
return 1;
}
virtual int get_thing_three() override {
return 3;
}
};
struct B : public Base {
virtual int get_thing_one() override {
return 2;
}
virtual int get_thing_four() override{
return 4;
}
};
Here I created a simple factory, not elegant but for illustrative purposes
// example simple factory
std::shared_ptr<Base> get_class(const int input) {
switch(input)
{
case 0:
return std::shared_ptr<Base>(std::make_shared<A>());
break;
case 1:
return std::shared_ptr<Base>(std::make_shared<B>());
break;
default:
assert(false);
break;
}
}
So this is the class of interest. It is a class does "something" with the data from the classes above. The methods below are a simple addition example but imagine a more complicated algorithm that is very similar for every method.
// class that uses the shared_ptr
class setter {
private:
std::shared_ptr<Base> l_ptr;
public:
setter(const std::shared_ptr<Base>& input):l_ptr(input)
{}
int get_thing_a()
{
return l_ptr->get_thing_one() + l_ptr->get_thing_two();
}
int get_thing_b()
{
return l_ptr->get_thing_three() + l_ptr->get_thing_four();
}
};
int main()
{
constexpr int select = 0;
std::shared_ptr<Base> example = get_class(select);
setter l_setter(example);
std::cout << l_setter.get_thing_a() << std::endl;
std::cout << l_setter.get_thing_b() << std::endl;
return 0;
}
How can I make the "boilerplate" inside the setter class more generic? I can't use traits as I did in the example above because I can't tie static functions with an object. So is there a way to make the boilerplate example more common?
Somewhere along the lines of having a selector, say
enum thing_select { THINGA, THINGB, };
template < thing_select T >
struct thing_traits;
template <>
struct thing_traits<THINGA>
{
static int first_function() --> somehow tied to shared_ptr<Base> 'thing_one' method
static int second_function() --> somehow tied to shared_ptr<Base> 'thing_two' method
}
template <>
struct thing_traits<THINGB>
{
static int first_function() --> somehow tied to shared_ptr<Base> 'thing_three' method
static int second_function() --> somehow tied to shared_ptr<Base> 'thing_four' method
}
// generic function I'd like to create
template < thing_select T, typename TT = thing_traits<T> >
int perform_action(...)
{
return TT::first_function(..) + TT::second_function(..);
}
I ideally would like to modify the class above to something along the lines of
// Inside setter class further above
int get_thing_a()
{
return perform_action<THINGA>(...);
}
int get_thing_b()
{
return perform_action<THINGB>(...);
}
The answer is, maybe I can't, and I need to pass int the shared_ptr as a parameter and call the specific methods I need instead of trying to tie a shared_ptr method to a static function (in hindsight, that doesn't sound like a good idea...but I wanted to bounce my idea)
Whoever makes the actual call will need a reference of the object, one way or the other. Therefore, assuming you want perform_action to perform the actual call, you will have to pass the parameter.
Now, if you really want to store which function of Base to call as a static in thing_traits without passing a parameter, you can leverage pointer to member functions:
template <>
struct thing_traits<THINGA>
{
static constexpr int (Base::*first_function)() = &Base::get_thing_one;
...
}
template < thing_select T, typename TT = thing_traits<T>>
int perform_action(Base & b)
{
return (b.*TT::first_function)() + ...;
}
You can also play instead with returning a function object that does the call for you (and the inner function takes the parameter).
It all depends on who you need to make the call and what information/dependencies you assume you have available in each class/template.
I have a use case where one thread reads message into a large buffer and the distributes the processing to a bunch of threads. The buffer is shared by multiple threads after that. Its read-only and when the last thread finishes, the buffer has to be freed. The buffer is allocated from a lock-free slab allocator.
My initial design was to use shared_ptr for the buffer. But the buffer can be of different size. My way of getting around it was do something like this.
struct SharedBuffer {
SharedBuffer (uint16_t len, std::shared_ptr<void> ptr)
: _length(len), _buf(std::move(ptr))
{
}
uint8_t data () { return (uint8_t *)_buf.get(); }
uint16_t length
std::shared_ptr<void> _buf; // type-erase the shared_ptr as the SharedBuffer
// need to stored in some other structs
};
Now the allocator will allocate the shared_ptr like this:
SharedBuffer allocate (size_t size)
{
auto buf = std::allocate_shared<std::array<uint8_t, 16_K>>(myallocator);
return SharedBuffer{16_K, buf}; // type erase the std::array
}
And the SharedBuffer is enqueued to each thread who wants it.
Now I think, I am doing lot of stuff unnecessarily, I can sort of make do with boost::intrusive_ptr with the below scheme. Things are bit C'ish- as I am using variable size array. Here I have changed the slab allocator with a operator new() for the sake of simplicity. I wanted to run it by to see if this implementation is okay.
template <typename T>
inline int atomicIncrement (T* t)
{
return __atomic_add_fetch(&t->_ref, 1, __ATOMIC_ACQUIRE);
}
template <typename T>
inline int atomicDecrement (T* t)
{
return __atomic_sub_fetch(&t->_ref, 1, __ATOMIC_RELEASE);
}
class SharedBuffer {
public:
friend int atomicIncrement<SharedBuffer>(SharedBuffer*);
friend int atomicDecrement<SharedBuffer>(SharedBuffer*);
SharedBuffer(uint16_t len) : _length(len) {}
uint8_t *data ()
{
return &_data[0];
}
uint16_t length () const
{
return _length;
}
private:
int _ref{0};
const uint16_t _length;
uint8_t _data[];
};
using SharedBufferPtr = boost::intrusive_ptr<SharedBuffer>;
SharedBufferPtr allocate (size_t size)
{
// dummy implementation
void *p = ::operator new (size + sizeof(SharedBuffer));
// I am not explicitly constructing the array of uint8_t
return new (p) SharedBuffer(size);
}
void deallocate (SharedBuffer* sbuf)
{
sbuf->~SharedBuffer();
// dummy implementation
::operator delete ((void *)sbuf);
}
void intrusive_ptr_add_ref(SharedBuffer* sbuf)
{
atomicIncrement(sbuf);
}
void intrusive_ptr_release (SharedBuffer* sbuf)
{
if (atomicDecrement(sbuf) == 0) {
deallocate(sbuf);
}
}
I'd use the simpler implementation (using shared_ptr) unless you are avoiding specific problems (i.e. profile first).
Side Note: you can use boost::shared_pointer<> with boost::make_shared<T[]>(N), which is being [added to the standard library in c++20.
Note that allocate_shared already embeds the control block into the same allocation like you do with the intrusive approach.
Finally, I'd use std::atomic_int so you have a clear contract that cannot (accidentally) be used wrong. At the same time, it'll remove the remaining bit of complexity.
What I'm trying to archive is to get an update if the number of connections to a boost::signal2::signal object is changeing.
To give you the whole picture: I'm writing a GUI application which displays data from a remote server. Each "window" in the application should get it's data for a specific dataset. If a dataset is to be displayed it needs to be remotely subscribed from the server. Multiple windows can display the same dataset (with different ordering or filtering). My goal is to subscribe to a specific dataset only ONCE and disconnect ones its not longer needed.
Background: HFT software, displaying marketdata (orderbooks, trades, ...)
My code so far: I got stuck once I tried to implement the "operator()".
enum UpdateCountMethod {
UP = 1,
DOWN = -1
};
/**
* \brief Connection class which holds a Slot as long as an instance of this class "survives".
*/
class Connection {
public:
Connection(const boost::function<void (int)> updateFunc, const boost::signals2::connection conn) : update(updateFunc), connection(conn) {
update(UP); //Increase counter only. Connection was already made.
}
~Connection() {
update(DOWN); //Decrease counter before disconnecting the slot.
connection.disconnect();
}
private:
const boost::function<void(int)> update; // Functor for updating the connection count.
const boost::signals2::connection connection; // Actual boost connection this object belongs to.
};
/**
* \brief This is a Signal/Slot "container" which number of connections can be tracked.
*/
template<typename Signature>
class ObservableSignal{
typedef typename boost::signals2::slot<Signature> slot_type;
public:
ObservableSignal() : count(0) {}
boost::shared_ptr<Connection> connect(const slot_type &t) {
// Create the boost signal connection and return our shared Connection object.
boost::signals2::connection conn = signal.connect(t);
return boost::shared_ptr<Connection>(new Connection(boost::bind(&ObservableSignal::updateCount, this, _1), conn));
}
// This is where I don't know anymore.
void operator() (/* Parameter depend on "Signature" */) {
signal(/* Parameter depend on "Signature" */); //Call the actual boost signal
}
private:
void updateCount(int updown) {
// TODO: Handle subscription if count is leaving or approaching 0.
count += updown;
std::cout << "Count: " << count << std::endl;
}
int count; // current count of connections to this signal
boost::signals2::signal<Signature> signal; // Actual boost signal
};
I make a interface class in C++ for voice recognition, i´m using the Julius API. http://julius.sourceforge.jp/en_index.php?q=index-en.html.
Well, my class has some events, these events will be triggered by the Julius API.
The Julius API has the function call callback_add with this signature:
int callback_add (Recog *recog, int code, void(*func)(Recog *recog, void *data), void data)
I using some 'proxy' functions to Invoke the events and passing this functions to callback_add.
If the property event is static, it works fine, but if is a non static, inside the proxy function the property not be recognized.
The difficult is because I have to use the callback_add function and can't modify this.
Here is a summary of the class with 2 events (static and non-static)
Header
#ifndef FALAENGINE_H_
#define FALAENGINE_H_
#pragma once
extern "C"{
#include <julius/julius.h>
}
namespace FalaAPI {
public ref class FalaEngine
{
public:
FalaEngine();
~FalaEngine();
// Events
delegate void OnRecognizedDele(FalaAPI::RecoResult^ result);
static property OnRecognizedDele^ OnRecognized;
delegate void OnEngineStartDele();
property OnEngineStartDele^ OnEngineStart;
private:
Recog *recog;
Jconf *jconf;
};
}
#endif /* FALAENGINE_H_*/
Source
#include "stdafx.h"
using System::String;
using System::Console;
#include "FalaEngine.h"
#include <windows.h>
namespace FalaAPI{
void StartOnEngineStart()(Recog *recog, void * dummy){
if(FalaEngine::OnEngineStart->GetInvocationList()->Length > 0)
FalaEngine::OnEngineStart->Invoke();
}
void StartOnRecognized()(Recog *recog, void * dummy){
if(FalaEngine::OnRecognized->GetInvocationList()->Length > 0)
FalaEngine::OnRecognized->Invoke();
}
FalaEngine::FalaEngine(){
recog = j_recog_new();
jconf = j_jconf_new();
//Julius callback Functions
callback_add(recog, CALLBACK_EVENT_PROCESS_ONLINE, StartOnEngineStart, NULL);
callback_add(recog, CALLBACK_RESULT, StartOnRecognized, NULL);
}
}
The problem occurs inside StartOnEngineStart function:
error C2227: left of '->GetInvocationList' must point to class/struct/union/generic type
A non-static member exists separately in each instance. You haven't specified which instance contains the delegate you want to inspect, you've only specified a class (and there may be many instances).
Try using the dummy parameter to pass your instance. But be careful, because the garbage collector will move objects around unless you have pinned them, so simply passing the address will not work. You need to create and pass a GCHandle instead. (Be careful not to leak the GCHandle, or your object will never be released)
Something like this should be effective:
ref class FalaEngine;
struct EngineHandle
{
gcroot<FalaEngine^> handle;
EngineHandle(FalaEngine^ engine) : handle(engine) {}
};
public ref class FalaEngine
{
clr_scoped_ptr<EngineHandle> callback_ptr;
public:
FalaEngine();
~FalaEngine();
// Events
delegate void OnRecognizedDele(FalaAPI::RecoResult^ result);
property OnRecognizedDele^ OnRecognized;
delegate void OnEngineStartDele();
property OnEngineStartDele^ OnEngineStart;
private:
Recog *recog;
Jconf *jconf;
};
void StartOnEngineStart(Recog *recog, void * dummy)
{
FalaEngine^ that = static_cast<EngineHandle*>(dummy)->handle;
that->OnEngineStart(); // C++/CLI already checks if the invocation list is empty
}
void StartOnRecognized(Recog *recog, void * dummy)
{
FalaEngine^ that = static_cast<EngineHandle*>(dummy)->handle;
that->OnRecognized(recog->get_result());
}
FalaEngine::FalaEngine()
: callback_ptr(new EngineHandle(this))
{
recog = j_recog_new();
jconf = j_jconf_new();
//Julius callback Functions
callback_add(recog, CALLBACK_EVENT_PROCESS_ONLINE, StartOnEngineStart, callback_ptr.get());
callback_add(recog, CALLBACK_RESULT, StartOnRecognized, callback_ptr.get());
}
The clr_scoped_ptr class is here. There are not many license requirements, make sure you follow them though if you use it.