0

I'm working with a different team on a project. The other team is constructing a GUI, which, like most GUI frameworks is very inheritance driven. On the other hand, the code on this side ('bottom end', I guess one could say) is essentially C (though I believe it's all technically C++ via the MSVC2010 toolchain w/o the "treat as C" flag.

Both modules (UI and this) must be compiled separately and then linked together.

Problem: A need has popped up for the bottom end to call a redraw function on the GUI side with some data given to it. Now here is where things go bad. How can you call INTO a set of member functions, especially one w/ complex dependencies? If I try to include the window header, there's an inheritance list for the GUI stuff a mile long, the bottom end obviously isn't build against the complex GUI libs...I can't forward declare my way out because I need to call a function on the window?

Now obviously this is a major communication design flaw, though we're in a bad position right now where major restructuring isn't really an option.

Questions:

  1. How SHOULD have this been organized for the bottom end to contact the top for a redraw, going from a ball of C like code to a ball of C++ node.

  2. What can I do now to circumvent this issue?

The only good way I can think of is with some sort of communication class...but I don't see how that won't run into the same issue as it will need to be built against both the GUI and the bottom end?

Rakib
  • 7,435
  • 7
  • 29
  • 45
Syndacate
  • 637
  • 1
  • 7
  • 15
  • how about the bottom level setting a dirty flag or calling a callback or something, then let the higher level code do the stuff that requires access to the higher level stuff – Cheers and hth. - Alf Jun 09 '14 at 01:42
  • 1
    On windoze, the usual method of triggering a redraw is to post a message. The "low" code can post a message, that the UI pick up in the message queue. Beyond that, it's hard to tell from your description of the problem. – user3344003 Jun 09 '14 at 01:44
  • As Alf says, a callback sounds good - e.g. you can have the GUI code set a function pointer (or even `std::function` if you're feeling adventurous), when the "C" code reaches a point where it knows a redraw might be needed, it calls via the function pointer (if not `nullptr`). The GUI side can always provide a function pointer to some intermediate function that stores context (your "with some data given to it") then call the member function(s). – Tony Delroy Jun 09 '14 at 01:59
  • As for using a function pointer - won't I run into the same problem? A non-member function pointer is different than a member function pointer. In declaring the member function pointer type, the class must be specified. The "lower level" would still need to know about the GUI stack classes, no? Not sure off-hand if the class for the member function ptr can be extern'd...if not it'd be the same problem. @user3344003 : It's using QT, not Win32 GUI stack. To post any messages it'll need to know about the GUI framework - even in Win32. – Syndacate Jun 10 '14 at 08:21

1 Answers1

0

If you only need to call a single function, or even a small subset of functions, a callback is probably your best bet. If you're dealing with a member function, you can still call it with a pointer to the member function and a pointer to the object in question. See this answer for details on doing that. However, this could mean requiring that you include the entire mile-long list of dependencies for the GUI code.

Edit: After some thought, you could do a callback for a few functions without needing to include the dependencies for the GUI code. For example:

In the GUI code somewhere...

int DoFooInBar(int arg1, const char *arg2){
    return MyForm.ChildContainer.ChildBox.ChildButton.Bar.DoFoo( arg1, arg2 );
}

Now in GUICallbacks.hpp...

int DoFooInBar(int arg1, const char *arg2);

You could then include GUICallbacks.hpp and call DoFooInBar() from anywhere in your C code. The only issue with this method is that you would need to make a new function for every callback you want to use.

A more general method of accomplishing such a task in bulk is via passing messages. A very cross-platform method for doing this involves a communication object, as you have mentioned. You wouldn't necessarily encounter any build issues if you provide a mechanism for obtaining a pointer to a shared communication object by a naming mechanism. A small example would be:

class CommObj{
public:
    struct Message{
        uint32_t type;
        uint32_t flags;
        std::string title;
        std::string contents;
        ... //maybe a union here or something instead
    };
private:
    static map<std::string, CommObj*> InternalObjects;
    std::deque<Message> Messages;
    std::string MyName;
public:
    CommObj(const char *name); //Registers the object in the map
    ~CommObj(); //Unregisters the object in the map
    void PushMessage( uint32_t type, uint32_t flags, const char *title, const char *contents, ...);
    Message GetMessage();
    bool HasMessages();
    static CommObj *GetObjByName(const char *name);
    static bool ObjWithNameExists();
};

Obviously you can make a more C-like version, however this is in C++ for clarity. The implementation details are an exercise for the reader.

With this code, you may then simply build both the backend and frontend against this object, and you can run a check on both sides of the code to see if a CommObj with the name "Backend->GUI" has been made yet. If not, make it. You would then be able to start communicating with this object by grabbing a pointer to it with GetObjByName("Backend->GUI"); You would then continuously poll the object to see if there are any new messages. You can have another object for the GUI to post messages to the backend too, perhaps named "GUI->Backend", or you could build bi-directionality into the object itself.

An alternative method would be to use socket communication / shared file descriptors. You could then read and write data to the socket for the other side to pick up. For basic signalling, this may be a simple way to accomplish what you need, especially if you don't really need anything complex. A simple send() call to a socket descriptor would be all you need to signal the other side of the code.

Do be aware that using sockets could cause slowdowns if used incredibly heavily. It depends on the underlying implementation, but sockets on localhost are often slower than raw function calls. You probably aren't going to need interlocked signalling in a tight loop though, so you should be fine with either method. When I say slower, I mean it's maybe 50 microseconds vs 5 microseconds. It's not really anything to worry too much about for most situations, but something to be aware of. On the flipside, if the GUI code is running in a different thread from the backend code, you would likely want to mutex the communications object before posting/reading messages, which wouldn't be needed with a shared file descriptor. Mutexes/semaphors bring their own baggage along to deal with.

Using a communications object like the one I gave an outline for would allow for some automatic marshaling of types, which you might be interested in. Granted, you could also write an object to do that marshaling with a socket too, however at that point you might as well use a shared object.

I hope your project ends up going smoothly.

Community
  • 1
  • 1
Kaslai
  • 2,445
  • 17
  • 17
  • Yeah, I'd rather stay away from sockets. It's actually a bit of a resource intensive app, so the overhead might matter. The communications object is good, but I don't like that it would have to continuously poll, I feel that's bad style for something like this. I don't mind doing that callback, but "MyForm" would be a bit tricky to get a hold of seeing as DoFooInBar is a non-member function. I may be able to save "MyForm" in a static context and get it from there...not my preferred method, but it may be manageable... Even if it involves callback I'd like the action to be direct function call – Syndacate Jun 10 '14 at 08:32
  • Right, for the callback mechanism you would have to do some gymnastics in order to obtain the root form element. You could look into binding the function into an std::function from the GUI code that you could then pass back to the backend code. – Kaslai Jun 10 '14 at 10:59