Thread: Implementing type safety in a C-style callback scenario

  1. #1
    Registered User
    Join Date
    Jul 2015
    Posts
    64

    Implementing type safety in a C-style callback scenario

    Hey all,

    I'm currently writing a function that I intend to compile into a personal-use library, so, naturally, I'd like to get things right first time. At the moment, I'm using default (or not) arguments passed to the function to selectively reinterpret_cast a function pointer to a callback routine. I've knocked together some demo code to show you what I mean in case it's unclear:

    Code:
    #include <iostream>
    #include <string>
    #include <Windows.h>
    
    void Caller(void* gen_fpCallback = 0, void* lpCallbackData = 0)
    {
        std::cout << "Caller function executing.\n\n";
        
        // Do stuff here
    
        if (gen_fpCallback)
        {
            if (lpCallbackData)
            {
                auto fpCallback = reinterpret_cast<void(*)(void*)>(gen_fpCallback);
                fpCallback(lpCallbackData);
            }
    
            else
            {
                auto fpCallback = reinterpret_cast<void(*)()>(gen_fpCallback);
                fpCallback();
            }    
        }
    }
    
    void A()
    {
        std::cout << "Function A is running.\nNo parameters specified.\n\n";
    }
    
    struct Data
    {
        std::string arbitraryparam_string1;
        std::string arbitraryparam_string2;
    };
    
    void B(void* param)
    {
        std::cout << ((Data*) param)->arbitraryparam_string1 << ((Data*) param)->arbitraryparam_string2;
        MessageBox(NULL, L"MessageBox to confirm that function B actually executes on final call", L"Note", MB_OK);
    }
    
    int main()
    {
        // Launch caller with no callback
        Caller();
    
        // Launch caller with a callback function that takes no parameters
        void(*ptr_to_A)() = &A;
        Caller(ptr_to_A);
    
        // Launch caller with a callback that accepts a void* parameter
        Data data = { "Function B is running.\n", "These strings were passed via a pointer to a structure.\n\n" };
        void* ptr_to_data = &data;
    
        void(*ptr_to_B)(void*) = &B;
        Caller(ptr_to_B, ptr_to_data);
        
        Caller(ptr_to_B);        // What is happening here?
    
        std::cin.get();
        return 0;
    }
    As it stands, I'm very wary of the reinterpret_cast; I've never come across a situation where I've had to use it before. The first three calls to Caller() work as expected. However, the final call casts the generic function pointer to a void (*)() and, through it, executes function B, even though B is prototyped to accept a void pointer parameter.

    What is happening during this call? Is the compiler implicitly (and nicely) passing '0' as the second parameter or does, perhaps, whatever happened to be in that memory location at the time act as the second parameter passed to B? I realise the latter may be dangerous. As clever as compilers are these days (and please correct me if I'm wrong here), I don't believe that function B is being implicitly overloaded by calling it through a mismatched function pointer. I also note that this behaviour may be undefined by the standard; I am using MSVC if it matters.

    My question: is there a more type safe way of achieving this? Whilst I could leave it up to the calling application to ensure that a valid function pointer is passed to the calling function, there is no reason to leave this "type unsafe" if it can be rectified simply and efficiently. In order to avoid a large library footprint (and I realise MSVC isn't necessarily the way to go for this), I'd like to avoid overloading the Caller function twice over. Is it perhaps possible to perform a runtime check on the callback function in order to enforce correct function pointer casting?

    Also, how does this technique look in terms of security? I'm aware that a malicious attacker could take over execution if they are able to overwrite the function pointer itself. If I am careful to avoid this scenario, is this a relatively 'safe' technique to work with?

    Many thanks for your time; I much appreciate it!
    Abyssion

    P.S. I see that these boards now support TLS; on behalf of the security-conscious users here, "Thank you very much!"
    P.P.S. Many apologies if the example code doesn't compile; my IDE is currently out of action.
    Last edited by Abyssion; 03-12-2017 at 04:33 PM.

  2. #2
    Registered User
    Join Date
    Jun 2015
    Posts
    1,643
    I get a bunch of errors trying to compile your code with gcc.
    Why don't you just do something like this:
    Code:
    void stuff() {
        std::cout << "stuff in common\n";
    }
    
    void Caller() {
        stuff();
    }
    
    void Caller(void (*fp)()) {
        stuff();
        fp();
    }
    
    void Caller(void (*fp)(void*), void *data) {
        stuff();
        fp(data);
    }
    BTW, we don't all use Windows, so it's best not to use a gratuitous MessageBox call.

  3. #3
    Registered User
    Join Date
    Jul 2015
    Posts
    64

    Thumbs up

    Thank you very much algorism! I'm literally sat here laughing out loud at how simple the solution is; I was overthinking this massively.

    And yes, I didn't really think the MessageBox call through; it just became my go-to for demonstrating a block of code was executing because I'm so used to posting on the Windows board. My apologies

    Thanks again!

  4. #4
    Registered User
    Join Date
    Jun 2015
    Posts
    1,643
    I should have mentioned that you are sneaking in a C-style cast with (Data*). That should be a static_cast instead.
    Code:
    #include <iostream>
    #include <string>
    
    struct Data { std::string s1, s2; };
    
    void A() {
        std::cout << "Function A\n";
    }
      
    void B(void* param) {
        std::cout << "Function B\n";
        std::cout << static_cast<Data*>(param)->s1
                  << static_cast<Data*>(param)->s2;
    }
    
    void stuff() {
        std::cout << "Function Caller: common stuff\n";
    }
    
    void Caller() {
        stuff();
    }
    
    void Caller(void (*fp)()) {
        stuff();
        fp();
    }
    
    void Caller(void (*fp)(void*), void *data) {
        stuff();
        fp(data);
    }
     
    int main() {
        Caller();
        Caller(A);
        Data data = { "Line 1\n", "Line 2\n" };
        Caller(B, &data);
        return 0;
    }

  5. #5
    Registered User
    Join Date
    Jul 2015
    Posts
    64
    To be honest, I did realise that; it was just laziness on my part.
    Should have known it would never sneak by you guys

    Cheers for pointing it out anyway!
    I'll refactor my code tonight; thanks for the quick responses, it's most appreciated.

  6. #6
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    38,658
    It seems like you're trying to roll your own bind.
    std::bind - cppreference.com

    or this, if you want the hyped up version.
    Chapter 1. Boost.Bind - 1.63.0
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  7. #7
    Registered User
    Join Date
    Jul 2015
    Posts
    64
    Hi Salem, thanks very much for the reply.
    std::bind is a component of the standard library that I'd never heard of prior to your comment; cheers for the links.
    I don't think that's quite what I was going for though, because (please correct me if I'm mistaken here,) the bind interface does not allow one to call the bound function with no parameters at all, but simply substitutes in arguments that are not specified during the call with arguments that are pre-specified during the binding.

    I suppose a more concise way to explain my intention would be that I wished to check, before making the call, that I would be passing the correct number (and type) of parameters to a pointed-to function, using nothing but the function pointer itself. Sort of like runtime confirmation of the callback function's prototype. The technique that algorism kindly suggested will work, although I am still curious if it's possible.

    Cheers!

  8. #8
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    38,658
    > the bind interface does not allow one to call the bound function with no parameters at all
    A simple experiment would have shown you otherwise.
    Code:
    #include <random>
    #include <iostream>
    #include <memory>
    #include <functional>
    
    void f() {
      std::cout << "f called" << std::endl;
    }
    void g(int a) {
      std::cout << "g=" << a << " called" << std::endl;
     
    }
    
    int main()
    {
      using namespace std::placeholders;
      auto p = std::bind(f);
      p();
      auto q = std::bind(g,_1);
      q(2);
      q(3.4); // implicit float-to-int conversion
      // this gets lots of error messages
      //q("hello");
    }
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  9. #9
    Registered User
    Join Date
    Jul 2015
    Posts
    64
    Ahh righto, thank you for the clarification then.
    I apologise, I must have misunderstood the documentation.

    As I say, my IDE is currently unavailable at the moment, but yes, I could and should have perhaps used one of those online compiler tools.
    My bad.

    Thanks very much for the replies guys; in the interest of learning something new, I think I'll go with the std::bind solution.
    When I get time, I'll also take a look at the relevant Boost source code; I'd like to see how it's done at a lower level.

    Cheers again!

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Macro type safety
    By Aslaville in forum C Programming
    Replies: 7
    Last Post: 06-27-2016, 01:57 PM
  2. How can I enforce type safety?
    By Absurd in forum C Programming
    Replies: 4
    Last Post: 03-26-2016, 11:44 AM
  3. Callback style
    By nvoigt in forum C++ Programming
    Replies: 7
    Last Post: 08-19-2011, 08:46 PM
  4. C/C++ Type Safety
    By forumuser in forum C Programming
    Replies: 8
    Last Post: 09-23-2009, 12:27 AM
  5. Callback type things
    By Rune Hunter in forum C++ Programming
    Replies: 3
    Last Post: 02-04-2006, 08:39 PM

Tags for this Thread