Reputation:
I have some C code and I want to port it to c++, the problem is that in C++ I can't use an assembly function due to it's dynamic use
C version
extern asmFunc(); // C function prototype version
//actual use example
asmFunc(var1,ptr2,HANDLE);
asmFunc(ptr4,var2,NULL,eg ...); //everything works
C++ version
extern "C" VOID asmFunc(); // C++ function prototype version
//actual use example
asmFunc(var1,ptr2,HANDLE); // E0140 too many arguments in function call
asmFunc(ptr4,var2,NULL,eg ...); // E0140 too many arguments in function call
The Assembly function is declared in a separate asm file and it uses direct syscalls from ntdll.dll's functions, that's why it requires dynamic arguments
How to make it work?
Upvotes: 1
Views: 339
Reputation: 364458
You can have multiple labels at the same address. You could have multiple prototypes with different names that all happen to have the same address (and thus implemented by the same machine code). In your .asm
file, you put:
global foo_int, foo_long, foo_float ; NASM syntax for putting these in the symbol table
foo_int:
foo_long:
foo_float:
your code goes here
ret
You could also do this with assembler directives to create aliases for a symbol, like GAS .set foo_long, foo
.
You can think of this as multiple names for the same function, or as other functions whose implementation is to fall through into the real function, as an optimized tailcall where you've even optimized away the jmp
by making them contiguous in the asm.
Or using a GNU extension, declare multiple C or C++ names that all use the same asm symbol name. The compiler-generated .obj
will have every call-site referencing the same symbol name. (Which you're setting fully manually, so even without extern "C"
there's no name mangling and no leading underscore unless you choose one.)
// GNU C or C++; compatible with the mainstream compilers other than MSVC
int foo_int(...) asm ("foo");
long foo_long(...) asm ("foo");
float foo_float(...) asm ("foo");
See it in action on Godbolt with clang and GCC, noting that return foo_float('a', 1);
in the C++ source compiles to a call foo
in the compiler-generated asm. (Godbolt compiles for Linux normally, but I used -mabi=ms
to get GCC to use the Windows x64 calling convention.)
If this was in a separate DLL, each name would get resolved separately, wasting a bit of space for multiple DLL import entries (whatever Windows calls the functions pointers that are equivalent to the GOT in Linux dynamic linking.) The GNU C asm("name")
way doesn't have this downside, as there's only one asm symbol name.
But if you're just linking this .asm
file into the same executable or library as the C++ callers, separate symbol-table entries get resolved and go away at build time, not every time you run.
You could have a full prototype (and name) for every different function signature.
Or you could just have a name for every different return type,
using extern "C" int foo_int(...);
as Remy's answer shows. Note that (...)
will trigger the default argument promotions, e.g. float
will promote to double
, so it's impossible to pass an actual float
. (This is why printf
's "%f" conversion takes a double.)
The promotions are harmless and very cheap for integer, though, just sign- or zero-extending to int
. The mainstream x86 and x86-64 calling conventions were designed such that args are passed the same to variadic functions as they would be for a prototyped function with the same arg types (after promotion of float to double). x86-64 System V requires callers of variadic functions to set AL = # of XMM args, a number from 0 to 8; without any FP or vector args, this means each call-site will get an extra xor eax,eax
. That's about as cheap as a 2-byte NOP, especially on Intel.
Assuming the return type is statically known for any given call-site, this should solve your whole problem. If not, it's trickier!
It wouldn't necessarily work to to return a union
and decide at run-time which return value to use. Some calling conventions would return a pure float
in a different register (e.g. xmm0) than they'd pick for a union with a float
member. Even for pure integer, a union of an int
and a larger struct would get returned in memory, differently from a plain int
.
Upvotes: 0
Reputation: 596592
Use ...
in the argument list to specify the function is a variadic function taking unspecified arguments, eg:
extern "C" VOID asmFunc(...); // C++ function prototype version
//actual use example
asmFUNC(var1,ptr2,HANDLE);
asmFUNC(ptr4,var2,NULL,eg);
Upvotes: 2