Fog Creek Software
Discussion Board




Missing C feature?

1. I can
(a) Make a function whose parameters I figure out at compile type (normal function definition)
(b) Make a function whose parameters I figure out at compile type (variable arguments function)

2. I can
(a) Call a function whose address I know at compile time (normal call)
(b) Call a function whose address I know at run time (function pointer)

3. I can
(a) Call a function whose parameters I know at compile time (normal call)
BUT...unless I am mistaken
(b) I do NOT think you can call a function whose parameters I figure out at run time.

Am I mistaken?

If not mistaken, C is supposed to be (almost) a high-level assembler, so would you agree this is a missing feature?

If you do not 3b is ever conceivable, here is an example how it might be used:  I write a program which lets users interactively test some API by typing in function names and parameters. If it was say the Windows API I could use GetProcAddress to find the function to jump to, but there doesn't seem to be a C-way to pass the parameters in, if they are determined at runtime (consider if you wanted to write a scripting language with a way to call external DLL functions like VB does). Likewise if it was some other library, I would be able to make a table mapping strings of function names to function addresses, but still couldn't pass parameters in.   

S. Tanna
Friday, May 16, 2003

I propose 3b is already implemented widely as the lowly printf() function.  The parameter list is variable in length and all arguments following are determined at run-time.  n'est pas?

Nat Ersoz
Friday, May 16, 2003

The reason you cannot perform simialr operations with the Windows API is that the function calls are declared __stdapi (PASCAL calling) rather than __stddecl (C calling convention), and PASCAL doesn't support variable argument lists.

Variable argument lists would be a requirement, since different types would have different widths which would have to be deteremined by the "standardised" first argument (in the printf case, the format specifier - which though usually a string literal can itself be dynamic) .

MSFT saw PASCAL calling convention as being faster, and heck Apple was using it - so why not?  Given that the MSFT paradigm is highly optimized for speed and minimum size, they chose PASCAL calling convention giving up the ability to make C style calls in their API.

Nat Ersoz
Friday, May 16, 2003

You both misunderstand

printf is 1(b) not 3(b)...  a function which determines what its parameters are at run time.

As an illustration. Try making a program which
- Asks out how many integer to input (ok)
- Inputs them all into an array (ok)
- Generates the format string like "%d %d %d...etc" (ok)
- Make a **single** call to printf passing the format string and all the array arguments in this call, (I don't believe you can do this) i.e. at runtime it has to decide whether to do (and no including every possible variation is not allowed)
printf( "%d", x[0] ) ;
or printf( "%d %d", x[0], x[1] ) ;
or printf( "%d %d %d", x[0], x[1], x[2] ) ;
or etc.

As for PASCAL vs C calling convention.... under pascal calling convention it is not possible to do 1(b) [well in assembler I think you could do pascal-like which did], but it's not the issue.  Under both C and Pascal calling conventions, C language does not allow 3(b).

Off topic: I understand MS chose Pascal for Windows, because it fit on one less floppy (smaller code), which is adds up to big bucks when you're distributing millions of copies.

S. Tanna
Friday, May 16, 2003

They also, if dusty memory serves (and I'm sure someone will be along to embarrass me if it doesn't), that they also lost the ability to reference the stack using the BP.

Simon Lucy
Friday, May 16, 2003

Ha :-)

I would say

High Level Assembler which uses a different model than accessing registers directly = part of the machine-independent model, good, makes code more portable

High Level Assembler which excludes a whole class of problem types without additional low level assembler help = missing feature (?)

S. Tanna
Friday, May 16, 2003

No you can do it, you can pass the format in a string which you build on the fly and pass the array.  You might run out of stack and into data if the array was too big.  The format string defines how the data is addressed as a set of parameters.

Not to be recommended but it can be done.

Simon Lucy
Friday, May 16, 2003

I get the idea, could you give me an example or a link? What if the types of potential parameters are not all the same (e.g. some ints, floats, strings, etc.) - presumably you build a memory block with them in order?

S. Tanna
Friday, May 16, 2003

Yes, you can build a struct to handle them if its mixed content, or if you really want to be ugly you can use casts and pointer arithmetic into a byte aligned array.

C gives you a completely planar view of memory, you can mix types and whatever you like, but once you do that you have to manage all the details, such as byte alignments and the rest of it.

Simon Lucy
Friday, May 16, 2003


I saw something like this once... but that's been a while ago. 

Once the parameters were determined at runtime, a routine was used to build a stack frame with the correct parameters (don't forget to allow room for a possible return value) and it then invoked the function.

I think it was in an old magazine... something like Windows Developer's Journal?  It could handle both C and Pascal calling conventions, you just needed to tell it which type of stack frame to build.

Sorry, that's where the memory goes bye-bye

Joe AA
Friday, May 16, 2003

I see what you mean now.  You need to use va_start, va_list, va_end macros to accomplish this.  It has been a __long__ time since I've used these, but they _might_ get teh job done.

Nat Ersoz
Friday, May 16, 2003

Well va_ macros would be the modern effete way of doing it, but us muscular, non dish washing guys would just contatenate them all in a memblock using casting and pointer arithmetic.

And I have done this kind of thing in memory managers and (oh god) games that have some kind of opcode multiparameter junk.

Its been too long, someone throw me a contract to write an asynchronous shared memory manager...

Simon Lucy
Friday, May 16, 2003

Here is a good example of va_ macro usage.  Its simple but non-trivial:

http://www.opengroup.org/onlinepubs/007904975/basedefs/stdarg.h.html

The refering page is the Single Unix Specification:

http://www.unix-systems.org/single_unix_specification/

search for stdarg.h

Feeling light in my loafers referencing va_ macros....

Nat Ersoz
Friday, May 16, 2003

So, yes - it can be done in an ANSI portable manner.

Nat Ersoz
Friday, May 16, 2003

opengroup example is 1(b), not 3(b)

1(b) The function definiton is variable arguments. i.e. function determines its arguments at runtime

3(b) would be when the CALLER determines the number of arguments at runtime.  The function definition may or may not take variable arguments at runtime.

S. Tanna
Friday, May 16, 2003

OK, you're right...  still trying...

Nat Ersoz
Friday, May 16, 2003

Just as a slightly more concrete example of what the original poster is asking for:

Imagine you're writing an interpreter for a scripting language of some sort. One of your requirements is that you be able to call some reasonable subset of C functions (in a DLL, for example) directly from the script.

So, at run time, you need to turn:

Call dll.function a, b, c

into a stack frame, and then execute the code in the dll in question.

*That's* a function call with parameters determined at runtime.

And yes, Windows Developer Journal did have an article some years back with a library that did exactly this. However, there were lots of low level stack munging stuff that wouldn't be even slightly portable.

Chris Tavares
Friday, May 16, 2003

You can't do 3(b) in C because C does not have run-time introspection and reflection capabilities. In languages such as Java and C# the compiled program contains, besides the code and data, the description its various constructs such as structures and function signatures as metadata. This allows you to perform run-time discovery of the "shape" of these constructs and perform operations on them, such as 3(b). In languages such as Java this is handled by a special API while with other languages such as JavaScript run-time introspection is integrated into the language itself.

C, being a low-level language, a high-level assembly if you wish, does not place such metadata in the compiler output. Interestingly C++ can provide some run-time type information using typeid operator and the type_info structure. However, this information and what you can do with it is rather limited. Also, many compilers, e.g. VC++, turn off this feature by default.

Dan Shappir
Friday, May 16, 2003

Chris' explanation is correct example of the type of problem.


Comments onDan's comments:

Seems to be addressing a different issue:

If I have to figure out  parameter types from their content, it's very tough in C/C++ [unless you make some convention for it which is not part of the language per-se].

This is one reason why variable arguments functions (1b) needs an extra hint/cue, like the formatting string.    In the case of 1(b) the hint/cue is usually the first paraemeter of the function.

In the case of 3(b) - if the function is a variable arguments then a hint/cue (like 1st parameter) would be needed. If however the function were not variable arguments, no hint/cue is need in the function itself, just in the caller - (like parsing the script to put it in Chris' example).  In fact in either case, the question is not about passing the hint/cue, but how to assembly the stack frame in the caller.

S. Tanna
Friday, May 16, 2003

I don't really see it as a missing feature since we can loop over arrays.

my_printf (formatString, intArray);

   
Friday, May 16, 2003

What if my_printf could be any one of 1,000 different functions, each with different parameters, and not within your control.

S. Tanna
Friday, May 16, 2003

then I'd write my_my_printf() to do the looping, or else I'd get a different library.

   
Friday, May 16, 2003

Read Chris Tavares post about 6 or 7 ago, beginning "Just as a slightly more concrete example of what the original poster is asking for:".... then you'll see why this is not a solution.

S. Tanna
Friday, May 16, 2003

but I don't know your exact situation so I'm not trying to tell you that that's best for you.

   
Friday, May 16, 2003

OK, so here is a starting point. We have filled out the GenericFunctionDescription structure, describing the number and type and value of all the arguments. Then we call this routine. The question is whether MIRACLE_STACK_MANGLER can be constructed. I'd say it can but would be implementation dependend on go off looking in std_arg.h to get some clues about how to manipulate the stack as needed.

#define STACK_BUFFER_SIZE 1024 // increase if needed
void GenericFunction(void *theFunction, GenericFunctionDescription *theDescription_p, void *ReturnValue_out)
{
  char StackBuffer[STACK_BUFFER_SIZE];

  MIRACLE_STACK_MANGLER(theDescription_p, STACK_BUFFER_SIZE); // work the magic

  *ReturnValue_out = theFunction(); // this function has invisible arguments

  return;
}

X. J. Scott
Friday, May 16, 2003

To answer the original question:

No I wouldn't agree this is a missing feature in C as it is way too processor and OS dependent and thus can't be properly standardized in a portable manner.

If you absolutely need to do something like this, either use inline assembly or a runtime assembler like SoftWire (http://softwire.sourceforge.net/).  Of course you'll then be x86 (or whatever processor you're coding for) specific, but you have to be in any situation, which is why it doesn't belong in standard C.

George McBay
Friday, May 16, 2003

I agree that it's not in C, but I disagree that it would never belong in C. Why shouldn't C get reflection down the line if it's a highly requested feature?

Brad Wilson (dotnetguy.techieswithcats.com)
Friday, May 16, 2003

Reflection requires that the runtime system keep track of type information at runtime. C isn't a strongly typed language, so you can't rely on the compiler for runtime type info.

C is all about not having a runtime. The guiding language design feature has always been "to the metal." Unless the underlying CPU features reflection natively, I don't think you'll find this in C.

Chris Tavares
Friday, May 16, 2003

By my estimation, reflection should require no more runtime than RTTI in C++ does. The specification doesn't set out how memory must be laid out, so there's nothing stopping adding meta-data information that could be read by the runtime library.

Now, I'm not saying it's a great idea, just not an impossible one.

Brad Wilson (dotnetguy.techieswithcats.com)
Friday, May 16, 2003

Yeah, but C++ has a built in place to stick extra data in a class (the vtable). C doesn't. Note that C++ RTTI only works for classes that have at least one virtual function.

And RTTI doesn't help when you're trying to figure out if an arbitrary void * points to an int, a float, or a string.

Chris Tavares
Friday, May 16, 2003

I think I must be missing the relevance of the question.  Why not use the suggestions that people have already given?

ie: If you are programming in C, and using it as "high-level assembly" then just drop in some real, in-line assembly?  Look up in your compiler's manual how parameters are passed (e.g.: first four params under word size in R1-4, then pushed onto stack from left to right) .

Or if you *must* do this in pure C, then you should probably pass two parameters; a description of args and then a void pointer to a memory block of them.

Adding special syntax for that kind of function call seems needless (and varargs is pretty ugly already).  It doesn't feel C-ish.  Are you sure you are approaching the problem in a C-ish way?

.
Friday, May 16, 2003

XJ Scott's answer is I think along the right lines, but I do not think it would work as I suspect the compiler would add code so that the stack will get cleaned up between MIRACLE_STACK_MANGLER and the  *ReturnValue_out = theFunction();

I think the way to do would be something like

..etc.
  stackframe_t x = MIRACLE_STACK_MANGLER(theDescription_p, STACK_BUFFER_SIZE); // work the magic

  *ReturnValue_out = theFunction(x);

...etc..

Now stackframe_t would need to be a new type which is variable size, including possibly 0 (function with no arguments)

I do not agree it does not belong in C. I can envisage that C could (but sadly does not include a set of functions or macros - which are sort of like reverse_va_start, reverse_va_arg, reverse_va_end (the opposite of va_start etc), so my final code, if C had this facility would be something like

...etc...

// x is final stack data to pass, might be 0 or more bytes - it's what to push on the stack
// ref is a handle or something to the particular stack frame I am preparing

stackframe_h ref = reverse_va_start() ;
while (...some condition I write meaning I have another argument...)
{
    ...etc...
      reverse_va_next( ref, arg, sizeof arg ) ; // arg might be any type
}
stackframe_t x = reverse_va_end(ref) ;

ReturnValue_out = theFunction(x);


As for reflection, I see this is an entirely unrelated issue, and personally I strongly believe it does not belong in C, whose goals including getting you close to the underlying machine.

S. Tanna
Friday, May 16, 2003

Oh by the way, I know this could be coded in assembler... I am just trying to figure out if there is a C way to do it. Consider it a puzzle, rather than a real programming exercise.

S. Tanna
Friday, May 16, 2003

... although at some point in future, I expect to need to write code which can call (almost any) DLL,  kind of like the example given in Chris Tavares' post.

S. Tanna
Friday, May 16, 2003

"I am just trying to figure out if there is a C way to do it."

Assuming you mean - Is there a way to do this using ISO Standard C then the answer is 'No'.

If you mean - Can I do it non-portably using my specific C implementation then the answer is 'Possibly'

But I'm interested in why you are asking this question here? Why not ask it in the comp.lang.c of comp.std.c Usenet newsgroups?

Neil Butterworth
Friday, May 16, 2003

You know, there is setjmp that saves the stack and register context, and longjump that restores it. They use a implementation-dependent data structure to store the info. I see that for the 86 here we have (Copyright (c) 1985-1993 Microsoft)

typedef struct __JUMP_BUFFER {
    unsigned long Ebp;
    unsigned long Ebx;
    unsigned long Edi;
    unsigned long Esi;
    unsigned long Esp;
    unsigned long Eip;
    unsigned long Registration;
    unsigned long TryLevel;
    unsigned long Cookie;
    unsigned long UnwindFunc;
    unsigned long UnwindData[6];
} _JUMP_BUFFER;

...so maybe you could use those to grab ahold of the stack state, mess with it with wild abandon, and then safely clean up afterwards...

X. J. Scott
Friday, May 16, 2003

Chances are it's not that there's been a missing C feature these past 35 years.

Chances are you have a poorly written dll, or you don't know how to design your code properly.

Either make your function take varargs, or have the library writer make their function take varargs. 

Then when you have a dynamic number of params to pass, there's no problem.

That's it.  That's all there is too it. 

Don't make things harder than they need to be
Friday, May 16, 2003

"Don't make things harder than they need to be": With all due respect, I don't think you understand the problem.  The puzzle is basically how to make an API calculator, one which allows any function, with any number and types of parameters, to be called. The names/addresses  of the functions (and therefore what parameters they take) are determined at runtime.  Redesigning the functions that you are calling, is not really an option if the API calculator is supposed to work against an existing C API, such as the Windows API, functions in a DLL, or anything else.

Changing topic,  I do spend spare brain power on thinking about "missing features" in programming tools, especially missing stuff in C/C++... I was just wondering whether this really was a missing feature in C [I have found a few over the years]. I have always thought it was.

For me real project, when I get round to it, I expect that I will just code the thing in assembler, so I am not that desparate for a C solution - just curious if there was one.

If this entire discussion is felt to be off-topic by this community, I don't mind ending it.

S. Tanna
Friday, May 16, 2003

In that case, you know the function signature already, so you should know what number of arguments to expect.

Don't make things harder than they need to be
Friday, May 16, 2003

Alright, I think the conversion is over done.

He wants reflection. There is no reflection in C. That's pretty much the end of the story, as far as "solving" this problem goes.

Brad Wilson (dotnetguy.techieswithcats.com)
Friday, May 16, 2003

"Don't make things harder than they need to be".  Sorry, no that is not the case

Suppose I write a scripting language that allows any DLL function to be called

Later, Person X writes a DLL contain some C APIs

Later, Person Y writes a script declaring the DLLs (including Person X's DLL) he wishes to call, what parameters he takes.

At the time I create my scripting language, I have no knowledge of what person X or Y will do in future. In other words, at runtime I would need to call the function with the correct parameters.


--
And to repeat, my previous comment, no this is NOT a reflection problem.  It would be a reflection problem if I needed to examine types and determine what they were (like writing say a printf type function which didn't take a format specifier).  However for this problem, even if I could examine types of variables at runtime, it would be no help resolving it.  AFAIK any language which requires the caller specify parameter types to a function at compile time, regardless of whether it had reflection or not, would still have this problem.

S. Tanna
Saturday, May 17, 2003

This is one of the more interesting topics generated here, period.  I appreiciate it.

One question, I'm sure S.T. has already considered, but I'lll mention it anyway...

So, you've got your hinter, first arg, and the rest are variable TBD args.  So a person pushes all TBD args into an array (on the stack, OK, but not required).  And then the called function parses them out of the array, as determined by the hinter.

ex:

void caller1( void )
{
  char *fmt = "%d%d%d";
  int args[] = { 1, 2, 3 };
  callee( fmt, args );
}

void caller2( void )
{
  char *fmt = "%c%f";
  void *args;
  args = malloc( sizeof(char) + sizeof(float) );
  callee( fmt, args );
  free( args );
}

void callee( const char *fmt, void *ptr )
{
  while( fmt )
  {
    fmt++;  // skip '%'
    switch( *fmt++ )
    {
      case 'c':
        parse_char( ptr );
        ((char*)ptr) ++;
        break;
      case 'd':
        parse_char( ptr );
        ((int*)ptr) ++;
      break;
      case 'f':
        parse_char( ptr );
        ((float*)ptr) ++;
      break; 
  }
}

Very ugly, but doesn't this do functionally the same thing as dynamic parameter lists?  Not that I'd want to....

Thanks,

Nat Ersoz
Saturday, May 17, 2003

I looked into this a quite few years ago and found that it couldn't be done without using inline assembly.

I was creating a component that would allow me to call arbitrary function pointers from VB given the function address and a list of parameters and parameter info. I couldn't find any way to do it without manually manipulating the stack and registers with assembly.

So, I just did it in VB using a buffer that I filled with the machine code and CallWindowProc to jump to my first instruction.

If anybody can think of a way to do it without the assembly I'd like to hear about it.

Stephen Martin
Saturday, May 17, 2003

But for you to call the dll, you have to know how to load it, and for you to load it, you have to know it's signature.

Don't make things harder than they need to be
Saturday, May 17, 2003

DLLs don't have signatures.

All you need to load a DLL is its name (and path, if necessary). All you need to get a function pointer to an exported function is the name of the function.

All of the above can be supplied at run-time along with the list of parameters and info needed by the function. But you can't call that arbitrary function without using assembly. That's the whole point of this thread.

Stephen Martin
Saturday, May 17, 2003

It's not even about calling it. It's about discovering the signature. Since the function signatures in C contain no hint about the parameters (unlike mangled names in C++), it's impossible for him to know what to pass, regardless of whether he can write the code to actually pass it or not.

Cart before the horse.

(And, sure, you say this "isn't a reflection problem". Okay, whatever makes you happy. In C# or Java, you could do it with reflection, but hey, this isn't a reflection problem, right?)

Brad Wilson (dotnetguy.techieswithcats.com)
Saturday, May 17, 2003

And, to clarify your misconception, .NET does not require compile time knowledge of the function AT ALL. VB.NET can call late bound methods on classes that it has no knowledge of at compile time. There's no syntactic support in C# for doing it (just means you have to build the infrastructure yourself).

It's quite trivial to take the name of a method as a string, and print out the precise signature using reflection. It's also quite trivial to call said method with reflection as. All without any compile time knowledge.

Brad Wilson (dotnetguy.techieswithcats.com)
Saturday, May 17, 2003

The original poster is talking about making a call to a function, the parameters of which are unknown until runtime. There is no requirement to _discover_ the parameters at runtime. We are all aware that that is impossible without some form of meta-data.

In my case after having written many C wrappers to enable VB programs to call function pointers returned by APIs, dynamically bind to DLLs based on OS/API version, call cdecl exported functions, etc. I decided to write a component that would be a wrapper for any such call, without the need to write a separate wrapper for each new call. So, when my component was compiled it had no knowledge of the functions it would call. When its clients were compiled they knew what functions needed to be called (by name or address), what their parameters were and what the calling convention was; this information was passed to my component which would then make the call using dynamically created machine code.

Another example might be a test harness for a C style API under development where the test harness could be written with no prior knowledge of the API. The tests could be written in some text format (maybe XML to be trendy) that the harness would then interpret to dynamically generate the test calls.

I'm well aware of what can be done with VB.NET, C#, reflection and late binding but we're not talking about .NET. And I'm not even sure who has the 'misconception' here since I can't find any reference to .NET in this thread.

Stephen Martin
Saturday, May 17, 2003

Misspoke.  To load the lib you just need the name.  But to call a function you need the sig.

The point is:  Somewhere along the chain, SOMEBODY knows the signature.  USE it.

Don't make things harder than they need to be
Saturday, May 17, 2003

Brad and don't make it harder have it back to front.  Like Stephen says (read his last post carefully), there is no requirement to discover the parameters at runtime, just to generate them.

To build on a previous example, I gave:

Suppose I write a scripting language that allows any DLL function to be called

Later, Person X writes a DLL contain some C APIs

Later, Person Y writes a script declaring the DLLs (including Person X's DLL) he wishes to call, what parameters he takes.

At the time I create my scripting language, I have no knowledge of what person X or Y will do in future. In other words, at runtime I would need to call the function with the correct parameters

In this example: I do NOT need to discover the parameters by looking at the function - because person Y's script specify them.  I simple parse person Y's script.  This is where the meta-data comes from

--

Stephen - I have a half solution in C, which could even be half-portable (does that make it a quarter solution :-)).

S. Tanna
Saturday, May 17, 2003

Assume a "classic" C compiler that passes parameters on the stack, and allocates automatic locals on the same stack
(this would not work if the compiler uses registering passing calling conventions)


PART 1

Think about a function, like say

// does haven't to be ints, just an example
void funcX( int i0, int i1, int i2 )
{
...etc..

At the start of func, the stack is going to look like
i0 i1 i2 (return-address)  <--- if cdecl or similar
or
i2 i1 i0 (return-address) <--- if pascal or similar



2. Think about this code

{
func_ptr = ...some method of getting a pointer to the function...
int x[1024] ; // must be last automatic before function call
*func_ptr() ;
}

Immediately before the call to func_ptr, the stack contains
x[0] x[1] x[2] x[3]... x[1022] x[1023]
or
x[1023] x[1022] x[1021] x[1020] ... x[1] x[0]
Depending on which way the stack goes


So inside the function we are going to have a stack which looks like

x[0] x[1] x[2] x[3]... x[1022] x[1023] (return address)
or
x[1023] x[1022] x[1021] x[1020] ... x[1] x[0] (return address)


Now suppose func_ptr actually pointed to funcX

When funcX is called it's going to get i0, i1, i2 from the x[]'s that are on the stack.  There are 4 possible variations, depending on which way the stack grows and whether the compiler does parameters left to right (cdecl) or right to left (pascal).  These are

1. i0 = x[0], i1 = x[1], i2 = x[2] ; // if cdecl-like and stack grows up
2. i2 = x[0], i1 = x[1], i0 = x[2] ; // if pascal-like and stack grows up
3. i0 = x[1023], i1 = x[1022], i2 = x[1021] ; // if cdecl-like and stack grows down
4. i2 = x[1023], i1 = x[1022], i0 = x[1021] ; // if pascal-like and stack grows down

Conclusion Part 1: if we were to fill out x[] appropriate before jumping to the function pointer, we'd create the stack frame with the correct parameters


PART 2

So, to sum up so far we need to know:
- whether it's cdecl or pascal-like functions
- we need to know whether the stack grows up or down

We can find this out by creating an x with 4 elements containing 1, 2, 3, 4, and calling an internal test function which takes 3 parameters.
Depending on whether we get
1, 2, 3
3, 2, 1
2,3,4
4,3,2
We can figure it out and a set a global or something

Conclusion Part 2: everything so far is portable [but you have to write code to investigate and do the setup at runtime], assuming no registering parameter passing


PART 3

What about types other than int?

This is actually fairly easily.  Instead of making x and array of ints make them chars (or in Visual C there is an _alloca runtime function)

Now use memset and sizeof to copy the parameters into the x array, in the right order

Now it is conceivable (but unlikely) that the byte-order (MSB, LSB etc) might be reversed in the stack, as compared to the internal memory layout.  We could solve this by using another runtime investigation function similar to part 2

PART 4

So it all works?

There is one big flaw

Think about the original fragment

{
func_ptr = ...some method of getting a pointer to the function...
int x[1024] ; // must be last automatic before function call
*func_ptr() ;
}

When we meet the }, x gets out of scope, and the compiler will generate code to clear 1024 ints of the stack

Now if the called function also clears its parameters off the stack, we end up clearing too much off the stack, and blow up the app

In Win32, If I remember correctly (could be make to front)

- I believe the cdecl makes the caller responsible for clearing the stack...  So there would be no problem in this case

- But in pascal the function the called function is responsible for clearing the stack... so in this case, the app is always blown up

I don't have a solution for this one...

S. Tanna
Saturday, May 17, 2003

It just realized it might be possible to do the called functions cleaned up the stack (pascal I think) in Visual C++

I don't think there is an opposite of _alloca

So you _alloca for in the caller for a function like this, and the called functions eats the allocated stack space.

S. Tanna
Saturday, May 17, 2003

Sorry dude, sounds like an unnecessary layer.  I'll leave you guys alone.

Don't make things harder than they need to be
Saturday, May 17, 2003

S. Tanna,

I thought about the called-function-clearing-the-stack problem and that's part of why I turned you on to setjmp and longjmp. They save the register state and the stack location. Use setjmp to save the stack and register context. When your return from your generic function call, use longjmp to restore the stack state perfectly.

Voila! 100% portable implementation of just what you wanted.

We're done here folks, nothing to see, move along.

Nat,

Agreed this is one of the most fun threads we've had.

X. J. Scott
Saturday, May 17, 2003

If anyone is curious, I gave these ideas a shot and it seems to work well. 

There's one minor annoyance with Visual C++.  When you make a debug build, Visual C++ inserts a stack check after a function call.  With the stdcall convention, the callee clears the stack.  The debug stack check catches that the stack hasn't been restored to what it expects (since this doesn't take place until the longjmp on the next line of code) and throws up an error dialog.  You can click "Ignore" to continue but I imagine this would get extremely annoying if you wanted to use this method extensively.

Somebody
Sunday, May 18, 2003

Wow, a zillion replies and nobody mentioned libffi!?

http://sources.redhat.com/libffi/

"The libffi library provides a portable, high level programming interface to various calling conventions. This allows a programmer to call any function specified by a call interface description at run-time. "

This does exactly what you need. It of course requires some assembly-code munging, but it's been ported to many platforms and adheres to a common interface. There is nothing magic to this; it's just a manual way of  setting up a call frame. (I'm not familiar with the implementation; I'd guess the x86 is trivial, but more complex arches like IA-64 are probably "interesting")

You might say that this was an omission in the standard C library - it already had stdarg.h, why not include something like libffi? Probably just an oversight on the part of the creators - IDLs and run-time binding weren't hot topics back in the early days of C.

Dan Maas
Sunday, May 18, 2003

This is mostly off topic except maybe as a response to Stephen Martin.  It doesn't answer the larger issue because it just uses embedded Intel '86 specific assembler for STDCALL (pascal type) functions, but:

Some years back, for some reason I can't remember, I was trying to call VB procs through function ptrs from VB.  VB5 & VB6 allowed you to get the address of a function but provided no way to dereferece that function to actually call it.  So, what I needed was an utterly generic .dll intermediary function that could be passed any ole bunch of args, so long as the first arg was the address of the target function - otherwise I'd have had to put a different 'intermediary agent' function in my .dll for every target function signature.  Anyhow while poking around somewhere I came across some asm for my generic callback helper:

__declspec(naked) void CallBkSml()
  {

  /* For procedures with return values of 8 bytes or less */

  /* Get address to be called and fix-up return address */

      _asm  pop  eax;        // save return address
      _asm  pop  ecx;        // get address to JMP to
      _asm  push  eax;        // restore return address
      _asm  jmp  ecx;        // Jump to callback function....
  }

  __declspec(naked) void CallBkBig()
  {

  /* For procedures with return values of more than 8 bytes */

  /* Get address to be called and fix-up return address */

      _asm  pop  eax;        // save return address
      _asm  pop  edx;        // save parameter 0
      _asm  pop  ecx;        // get address to JMP to
      _asm  push  edx;        // restore parameter 0
      _asm  push  eax;        // restore return address
      _asm  jmp  ecx;        // Jump to callback function....
  }

It's been a while now, so I don't remember all the issues, but I just compiled this with a .def file containing:

LIBRARY  VBCallback

  EXPORTS
      CallBkSml
      CallBkBig

And I managed to do something marvelous that I can't remember.

John Aitken
Monday, May 19, 2003

*  Recent Topics

*  Fog Creek Home