4

My environment is Visual Studio 2008.

I have 3 different libraries. In a summarized manner the behaviors of them are equivalent as follows.

Library 1 - Offers functions to be registered

class FunctionRegistry
{
   typedef std::list<int> TListInt;
   TListInt m_Params;
   void * m_FPtr;
public:
   FunctionRegistry(void * fptr):m_FPtr(fptr){}
   FunctionRegistry& Insert(int value){m_Params.push_back(value); return *this;}
   void Call();
};

void FunctionRegistry::Call()
{
    /*this is the area I want to get rid of */
    TListInt::iterator ite = m_Params.begin();
    while (ite != m_Params.end());
    {
       __asm push *ite;//I want to remvoe this assmebly code somehow
       ++ite;//Edited after initial post
    }

    ((void(*)())m_FPtr)();

    ite = m_Params.begin();
    while (ite != m_Params.end());
    {
        int i = 0;
        __asm pop i;//I want to remvoe this assmebly code somehow
        ++ite;//Edited after initial post
    }
    /*end of area to get rid of*/
}

Library 2 - Defines a set of functions like

void foo(int i, int j)
{
    std::cout << "foo int int " << i << " " << j << std::endl;
}

void bar(int i)
{
    std::cout << "bar int " << i << std::endl;
}

void biz()
{
    std::cout << "biz" << std::endl;
}

FunctionRegistry fRegFoo((void*)&foo);
fRegFoo.Insert(10).Insert(20);

FunctionRegistry fRegBar((void*)&bar);
fRegBar.Insert(10);

FunctionRegistry fRegBiz((void*)&biz);

Library 3 - This is the user of above two libraries and a sample usage would be like,

fRegFoo.Call();
fRegBar.Call();
fRegBiz.Call();

This is a legacy code and I'm trying now to get rid of above assembly code. I can easily change (with some API changes) my library 1 to cope with my need. But my problem is, due to massive distribution of the libraries among a large number of users, asking them to change their use cases (here library 3) according API changes of library 1 should be my last option.

Now, I am seeking your help, is there any way to change my library 1 internals with whatever possible techniques without changing its API to eliminate this assembly code?

If you think, it can't be achieved without any API change, please suggest ways having minimal code changes to library 3.

Doonyx
  • 546
  • 2
  • 11
  • 4
    If you know `m_Params.size()` will always be fairly small (say <= 10) - you can just switch on it to a number of distinct calls that cast the function pointer to an appropriate type to accept the parameters. If that gets really painful, you can automate the iteration with the boost preprocessor library. If you really need to support arbitrary numbers of parameters, then you're stuck with the assembly or a bit of a rewrite that will affect library 2 code (e.g. have functions accept a `vector`). – Tony Delroy Jul 05 '14 at 11:25
  • Echoing what @david.pfx said: What exactly is the published API that you must preserve? If it's just Library 3, then that's trivial. – Kerrek SB Jul 05 '14 at 11:50
  • @KerrekSB API is almost like library 1 with a minor functions. But the non-trivial case is changes the library usage everywhere. In in hundreds spanning among tons of users. That's why I initially mentioned that, asking all of my users to change their code according my change, would be 'my last option'. I am seeking options (if any) before my last option. – Doonyx Jul 05 '14 at 11:53
  • It may just be me, but I'm still completely unclear about your requirements and your constraints. Your question suggests that users only see Library 3, but your comments (and the fact that you posted this question at all) suggest that the inner workings of Library 1 are somehow public, too. I can't make sense of this, nor do I have any idea of what a "correct" answer to this question should look like. – Kerrek SB Jul 05 '14 at 12:12
  • If you register a function and then call it with a different number of parameters than it expects, bad things can happen; especially if the function modifies its parameters. To me, that's enough to justify an API change. – Ferruccio Jul 05 '14 at 13:02
  • 1
    Let me borrow [Raymond](http://blogs.msdn.com/b/oldnewthing/) psychic powers, it seems your lib is trying to do a [Dispatcher](http://dispatcher.sourceforge.net/documentation-0.1.10/index.html) for mimic [CDECL](http://www.nynaeve.net/?p=41) call. The most straight forward solution was to use intrinsics, but there is no intrinsic for push and pop. Unfortunately it is not a trivial task, you can move the asm code to an external file and compile with [MASM](http://msdn.microsoft.com/pt-br/library/afzk3475.aspx). IMO too much for such small code. – CodeSettler Jul 05 '14 at 13:51
  • @CodeSettler That's a good answer. Moving the code to asm file and compile with a MASM seems a good solution. Why don't you post this as an answer. – Doonyx Jul 06 '14 at 04:18
  • Are you aware of the fact that your code exposes undefined behavior by using `void*` to store function pointers? – SomeWittyUsername Jul 06 '14 at 07:04
  • @icepack: are you aware you're wrong ;-. "The type of a pointer to `void` or a pointer to an object type is called an *object pointer type*." - "Converting a function pointer to an object pointer type or vice versa is conditionally-supported. The meaning of such a conversion is implementation-defined, except that if an implementation supports conversions in both directions, converting a prvalue of one type to the other type and back, possibly with different cv-qualification, shall yield the original pointer value." – Tony Delroy Jul 07 '14 at 02:01
  • @TonyD First of all, the conversion is implementation-defined, nothing implies what kind of behavior it must expose, except that the behavior shall be well-defined. Second, in the code snippet, the pointer is stored as a member, thus it's not prvalue. Third, and most important here, your quote is relevant to C++11 only, while OP uses VS2008, which has no support whatsoever of C++11. – SomeWittyUsername Jul 07 '14 at 02:15
  • @icepack: 1: *you're* telling *me* what implementation-defined means? Point is it's *not* undefined. O_o 2: "Evaluation of an expression...in general includes both [X] and ***fetching a value previously assigned to an object for prvalue evaluation***". Third, C++ has supported storing function pointers in `void*` for a long time, see "major extensions to C provided by C++" / "C.1.1 C++ features available in 1985" / "13 A pointer to function can be assigned to a `void*`". VS2008: "A pointer to a function can be converted to type `void*`, if type `void*` is large enough to hold that pointer." – Tony Delroy Jul 07 '14 at 03:01
  • @TonyD 1. Implementation-defined doesn't necessary mean that it will be converted to a proper function pointer. It can be also *well-defined* to cut off the highest 16 bits, for example. And surely, all of this is irrelevant for standard pre-C++11, where the conversion is undefined. 2. The pointer is saved to a member, no conversion to prvalue there. Again, C++11 only. 3. Allowing assignment doesn't mean the result is well-defined; Regarding VS2008 - surely, you noted the "if" part of the quote. Also: http://www.parashift.com/c++-faq/cant-cvt-fnptr-to-voidptr.html – SomeWittyUsername Jul 07 '14 at 05:47
  • @icepack: I didn't say implementation defined promised anything - I merely said that the behaviour was not undefined like you'd said it was. You've pointed out that despite the lack of C++03 tags the OP's use of VS2008 implies pre-C++11 - good insight - but as I'd commented on current C++ and you replied I've replied again about the current Standard; with regard to that, the stipulation about "shall yield the original pointer value" precludes chopping of bits for C++11 - if it compiles it must work. With VS2008 a static assert of size suffices, that's implementation defined to work. – Tony Delroy Jul 07 '14 at 06:02
  • @TonyD "shall yield the original pointer value" is conditioned by several prerequisites, some of which do not hold for this question (prvalue) and others are compiler-proprietary-internals-dependent of which neither of us has knowledge. Static assert *might* suffice, but: a. it's not present in this code. b. sizeof operator isn't applicable to function pointer in standard C++. – SomeWittyUsername Jul 07 '14 at 07:21
  • @icepack: "prerequisites"? All the Standard says is that implementation support is optional.. if provided it must work as expected. The prvalue is all that is *required*, but an lvalue may be used as explained by my quote "...fetching a value..." above. Re static assert - that means a recommendation to the OP to use static assert is warranted, not your claim that this has undefined behaviour. C++03 & C++11 "sizeof operator can be applied to a pointer to a function" - why don't you check this stuff before making assertions? – Tony Delroy Jul 07 '14 at 07:42
  • @TonyD You're right regarding the sizeof, I confused function pointer with function for some reason - my bad. Regarding the rest - "work as expected" is defined as (and I quote you): "The **meaning** of such a conversion is implementation-defined". That's a pretty clear statement. Opposed to that, in the "**except**" part, the behavior is defined to allow the desired conversion for prvalues only. Your claim regarding lvalue isn't valid here - the pointer is stored to void* **before** it had a chance to participate in evaluation (that's another expression in this code). – SomeWittyUsername Jul 07 '14 at 08:09
  • @icepack: re "except" part - yes - I pointed out half the picture so far, the rest being "Whenever a glvalue appears in a context where a prvalue is expected, the glvalue is converted to a prvalue". Assignment to the member does that, given " the built-in assignment operators expect that the left operand is an lvalue and that the right operand is a prvalue and yield an lvalue as the result." – Tony Delroy Jul 07 '14 at 08:39
  • @TonyD I'm talking about the `m_FPtr(fptr)`. There is no assignment operator usage here. – SomeWittyUsername Jul 07 '14 at 09:11
  • @icepack: assignment was just an example that I thought might help you think this through... ignore it if it's confusing you: the important message was "Whenever a glvalue...". More generally, you seem to be missing a simple concept - that things like assignment can state a requirement in terms of prvalue (for rhs expression) and be satisfied by glvalues. – Tony Delroy Jul 07 '14 at 09:18
  • @TonyD Ok, that's a good point. One thing remains unclear, though. "*if an implementation supports conversions in both directions*" - C-style cast (like in this code) can convert pretty much anything to anything without any correctness guarantee. – SomeWittyUsername Jul 07 '14 at 09:34
  • @icepack: you're right: C-style cast is famously dangerous. I think my comment on slashmais's answer's a fair illustration of this - it's dangerous to cast to a different type of function pointer, even though it compiles and happens to work for certain implementations/types. For example, if the parameters were a mix of `float` and `int` then `...` wouldn't work (with VS2008) as the `float`s get loaded into registers rather than pushed to the stack. – Tony Delroy Jul 07 '14 at 09:42

4 Answers4

3

If your end users can only call the API as defined in your "Library 3" then the answer is obviously yes. The surface area of this API is tiny and it would be trivial to re-implement it without any assembly language. This would require changes in Lib 2 as well as Lib 1.

The naive implementation would be a set of classes with static Call functions. There are many other choices.

But the problem is: we haven't see all you have to show. If the API provided in Library 3 is rich, and/or if it is leaky, then the answer could be anywhere from 'easy-peasy' to 'give up and find another job'. Your call.


Note: this answer is based on what you said: that the API Lib 3 must not change, but everything else can. If instead you require a solution in which both Lib 2 and Lib 3 are unchanged and only the assembly code in Lib 1 can change, please edit your question to explain this.

If this is so, there are two choices: either standards compliant or relying on implementation-defined behaviour. [If it's Windows/VS only life may be easier, but I did this exact thing 20 years ago just so I could make it portable to lots of kinds of Unix too. It can definitely be done.]

david.pfx
  • 9,271
  • 2
  • 19
  • 54
  • I guess, giving up seems the options for me and come with a new API which doesn't use any nasty assembly code. – Doonyx Jul 05 '14 at 11:34
  • The API doesn't use assembly. The implementation does. You can change that, depending on the API. – david.pfx Jul 05 '14 at 11:37
  • Exactly, that is my question. How to do it. Can you please tell me a concrete solution? Still I can't figure it out. – Doonyx Jul 05 '14 at 11:42
1

This gets rid of the asm push & pop but you may end up with a big switch-statement, up to a max of 256 case-statements. (Possible to use a macro to generate the case-statements?)

void FunctionRegistry::Call()
{
    typedef void (*_F)(...);
    _F f = (_F)m_FPtr;

    auto p=*m_Params.begin();
    switch(m_Params.size())
    {
        case 0: f();break;
        case 1: f(p); break;
        case 2: f(p, p+1); break;
        //...
        //case 256: f( ...please get therapy... ); break
    }
}
Community
  • 1
  • 1
slashmais
  • 6,783
  • 9
  • 50
  • 74
  • In the link you provided, in the selected answer it is compile time function binding. I need run-time function binding mechanism. Another answer, uses variable arguments, which changes my API. I have mentioned in above post and in my comment, why it's relatively hard to change my API. – Doonyx Jul 05 '14 at 12:02
  • 1
    @SargiEran: Yeah. Replaced with a code sample; don't know how helpful that will be ... may end-up with a big switch – slashmais Jul 05 '14 at 12:29
  • `typedef void (*_F)(...); _F f = (_F)m_FPtr;` - just an observation - this can be expected to work for Microsoft's "stdcall" calling convention and `int` parameters - as per the assembly it's replacing, but is significantly less portable - and less tedious - than casting `m_FPtr` to `void(*)(int)`, `void(*)(int, int)` etc.. From the Standard "The effect of calling a function through a pointer to a function type (8.3.5) that is not the same as the type used in the definition of the function is undefined". – Tony Delroy Jul 07 '14 at 01:58
  • @TonyD: why less portable? (incidentally it was written & tested on Linux) – slashmais Jul 07 '14 at 16:29
  • @slashmais: because an ABI might pass parameters to function specified as accepting "`...`" parameters differently from other functions - for example, if an ABI normally passes the first few parameters in registers, the compiler might still decide to generate code that passes all parameters to a `...` function via the stack so that the stdargs/varargs implementation can be kept simple (otherwise it needs to count how many parameters its used and follow the ABI model to know which registers to retrieve values from). On VS for example, if one of the parameters is a `float` it fails. – Tony Delroy Jul 08 '14 at 02:30
0

Not 100% sure, but it looks like you're pushing the value ite points to to the stack and then poping it into i later. If that is all you are doing, consider replacing the asm push/pop with a std::stack. Something like:

#include <stack>
//...
void FunctionRegistry::Call()
{
    std::stack<TListInt::value_type> astack;
    TListInt::iterator ite = m_Params.begin();
    while (ite != m_Params.end());
    {
       astack.push(*ite);
       ++ite;//Edited after initial post
    }

    ((void(*)())m_FPtr)();

    ite = m_Params.begin();
    while (ite != m_Params.end());
    {
        int i = astack.pop();
        ++ite;//Edited after initial post
    }
}
KitsuneYMG
  • 12,247
  • 3
  • 35
  • 57
0

Did you have a look at boost::bind ? Something like this :

#include <boost/bind/bind.hpp>
class FunctionRegistry
{
  typedef void tProcedure (void);

  private:
    const tProcedure proc;

  public:
    explicit FunctionRegistry( tProcedure proc ) : proc(proc) {}
    void Call() const { proc(); }

};

const FunctionRegistry fRegFoo( boost::bind( foo, 10, 20 ) );
Nielk
  • 551
  • 1
  • 4
  • 18