Is it possible to compile C headers with the Rad Studio XE C++ compiler and link them with Delphi code?
Thus eliminating the need to convert header files to pascal ?
The reason for the question is..
C - Header definition:
DLLEXPORT int url_engine_version(char *version, size_t length);
Attempt at Delphi Definition
function url_engine_version(version: PByte; var length: cardinal): integer;
cdecl; external 'corplib.dll';
Main app tried to call it using:
engVer: Pointer;
engLen: cardinal;
engLen := 64;
GetMem(engVer,engLen);
url_engine_version(engVer,engLen);
But Delphi AV's when it tries to call the routine.
Working C# definition - Chick works if I pass a StringBuilder predefinded as length 64
[DllImport("corplib.dll", CharSet = CharSet.Ansi,
CallingConvention = CallingConvention.Cdecl)]
public static extern int url_engine_version(StringBuilder version, [Out] int length);
The answer to your actual question is: No. Sorry.
However, I think in this case the problem is quite simple...
Either the C Header you have reproduced here is wrong or the C# declaration is or you have just been very lucky to not have the C# code crash and burn as badly as the Delphi code.
The problem I think is that the C Header declares the length parameter as a SIZE_T, NOT a pointer to a SIZE_T. i.e. it is an input parameter, not an output or in/out parameter.
You presumably use length to specify the size of buffer allocated for the pointer you pass in version. I further presume that the function returns the number of actual bytes used for the data placed in the version buffer.
The Delphi version crashes, I believe, because by specifying length as var, you are passing length by reference, i.e. the function receives not "64" but a pointer to the value "64", but it is using this pointer value, not the 64 value.
The C# code may be dodging the bullet by (also incorrectly, if the C Header itself is correct) declaring the parameter as out. This may translate into something that, if not correct, is at least not as "damagingly incorrect" at runtime.
I think simply removing the "var" from the length parameter declaration should solve your problem:
function url_engine_version(aVersion: PByte; aLength: cardinal): integer; cdecl; external 'corplib.dll';
The main problem with converting are differences in the type system. A good delphi header can't be derived from c.
For example c doesn't distinguish pointers to one element and pointers to arrays. It doesn't distinguish bools and ints. A char* can mean a zero terminated string, a pointer to bytes, a pointer to a single char, a char passed by reference,...
And in your example the delphi code passes the last parameter by reference(i.e. as pointer to UInt32) and the c code doesn't. But I don't understand why the C# code works.
Project JEDI have done a lot of C header conversions for Delphi. They have an excellent set of resources, tutorials etc. on their website.
They also have a tool that can automate this which was actually derived from original code by Bob Swart.
Related
I have been working on an Ada project and need to interface with a C library (fftw3). I used the command
gcc -c -fdump-ada-spec -C /usr/local/include/fftw3.h
to generate a preliminary binding (some tweaking required). I was able to get my code and the fftw3_h.ads to compile in gnat. However, the program crashes with
raised STORAGE_ERROR : fftw3_h.ads:733 object too large
When I run it through gdb, the code is crashing on a line that defines a version string,
fftw_version : aliased char_array (size_t); -- /usr/local/include/fftw3.h:457
pragma Import (C, fftw_version, "fftw_version");
My understanding of this is that Ada is trying to allocate space for the entire string all at once and is basing the storage space on the range of size_t. However, size_t in this case comes from interfaces.c.size_t which is defined as
type size_t is mod 2 ** System.Parameters.ptr_bits;
in i-c.ads while for C, size_t is defined as unsigned long in stddef.h. I am not sure how big 2**ptr_bits is, but I don't see any reason why the definition of size_t in i-c.ads should be expected to be limited to the size of C's unsigned long. If it is longer than C's unsigned long, then I suspect that the code is trying to create an array that uses more memory than I have. I have tried to just use interfaces.c.unsigned_long instead of size_t, but Ada does not like the type mismatch (which I should have know).
At this point, I have two questions. Firstly, is my understanding of the problem close (this is my first experience with interfacing to between Ada and C).
Secondly, assuming my understanding is correct, is there a way around the problem or do I need to take a completely different approach?
Thank you everybody. Simon's answer really helped. One problem is that fftw_version[] is set in the C code for fftw, not by me, and I cannot guarantee that the procedure will be called before something in the fftw library needs it (fftw3_h.ads is elaborated before any of my code). I also found an old FFTW_Ada binding online from 2004 by Stephen J. Sangwine. While I could not get his code to work, I combined how he dealt with the version string with Simon's suggestion and created a function that returns the string when needed by something else in the fftw library.
function FFTW_Version return String is
tmp_version : aliased char_array(size_t) ;
pragma Import(C, tmp_version, "fftw_version");
begin
return To_Ada(tmp_version, Trim_Nul => True) ;
end FFTW_Version;
This gives me something that produces the string when needed, but does not make assumptions about the size of the string (which the FFTW_Ada code did). This compiles and works nicely.
I do not think you understand the size of the array you are trying to create with
fftw_version : aliased char_array (size_t); -- /usr/local/include/fftw3.h:457
This line does not simply state that the array fftw_version is indexed by the type size_t, it also specifies that the array indices span the range of 0..2**32, which apparently is too large for your hardware to handle.
What array size do you really want here? For instance, if you want an array of 100 characters you should specify
fftw_version : aliased char_array(size_t range 0..99);
To quote the LRM:
The types int, short, long, unsigned, ptrdiff_t, size_t, double, char, wchar_t, char16_t, and char32_t correspond respectively to the C types having the same names.
Regardless of how the actual definition looks like, you can trust the compiler to comply to the LRM, so Ada's size_t will, in fact, have the exact same size as C's.
That being said, your error stems from the fact that you get a stack overflow when allocating size_t'Last bytes for the array. I searched the fftw3.h header for a definition of fftw_version which you're trying to wrap, but I couldn't find any, so for a more detailed answer, you need to show the code you're wrapping. Most probably you want to use chars_ptr instead from Interfaces.C.Strings to wrap strings from C – its conversion methods take care of getting the string length by searching for the null terminator.
Seems to me it’s likely to be what you’re doing with it. This isn’t a Minimal, Complete and Verifiable Example, because it works for me :-)
bartels.adb:
with Ada.Text_IO; use Ada.Text_IO;
with Interfaces.C; use Interfaces.C;
procedure Bartels is
fftw_version : aliased char_array (size_t); -- /usr/local/include/fftw3.h:457
pragma Import (C, fftw_version, "fftw_version");
V : constant String := To_Ada (fftw_version, Trim_Nul => True);
begin
Put_Line (V);
end Bartels;
bartels_c.c:
const char fftw_version[] = "the version";
Compile the C file, build the Ada with
$ gnatmake bartels.adb -largs bartels_c.o
and run
$ ./bartels
the version
Summarizing several helpful answers, #Jim Rogers points out here that the elaboration of an object of size_t might exceed available memory. #Simon Wright observes here and illustrates here that no space need be allocated for an imported object. Indeed, the Import aspect, implied by the corresponding pragma, specifies the following dynamic semantics:
Notwithstanding what this International Standard says elsewhere, the elaboration of a declaration with a True Import aspect does not create the entity. Such an elaboration has no other effect than to allow the defining name to denote the external entity.
The following complete example, illustrates the aspect corresponding to the pragma shown here in the function by #C. Bartels:
console:
$ gprclean -q ; gprbuild -q && ./bartels
Versioni 1.2.3
bartels.gpr:
project Bartels is
for Languages use ("Ada", "C");
for Main use ("bartels.adb");
end Bartels;
bartels.adb:
with Ada.Text_IO;
with Interfaces.C;
procedure Bartels is
function Get_Version return String is
version : Interfaces.C.char_array(Interfaces.C.size_t)
with Import => True, Convention => C;
begin
return Interfaces.C.To_Ada(version, Trim_Nul => True) ;
end Get_Version;
begin
Ada.Text_IO.Put_Line (Get_Version);
end Bartels;
version.c:
const char version[] = "Versioni 1.2.3";
As an aside, some confusion may have arisen as this example works with GNAT Community 2018 but fails with GNAT GPL 2017:
$ gprclean -q ; gprbuild -q && ./bartels
raised STORAGE_ERROR : bartels.adb:7 object too large
I'm having trouble translating from some C declarations to Delphi XE2 for calling functions in a DLL. I translated all the function declarations from a Visual Basic source file, but in testing them I ran in to problems. Some functions returned Long values, but debugging my code I observed that the functions involved returned values that weren't right. Then I turned to the original code in C, and there I found the root of my troubles: At some point in the original C code there is this declaration:
typedef struct { } __RSI_CHANNEL;
typedef __RSI_CHANNEL FAR* RSI_CHANNEL;
Now, some functions return RSI_CHANNEL; those functions return values like this:
return (RSI_CHANNEL)ws;
And ws is declared as:
rsiChannel FAR* ws = new FAR rsiChannel;
rsiChannel is a typedef struct. So far, so good...by now I guess some of you may have recognized this as PIMPL idiom. Ok, according to the source code comments, I should have to save that return value (RSI_CHANNEL) and test against NULL, and pass it through function calls untouched...nothing more...so my take is it should be implemented in Delphi as Pointer. But it doesn't work. Something like this:
Type
RSI_CHANNEL = Pointer;
...{ later in implementation block }...
Function rsiInitWsock(HostName : PAnsiChar; port : Long) : RSI_CHANNEL; stdcall; external 'rsidll32';
No compile errors, no runtime errors. If I call this function, I get Nil.
¿Any idea how this could be implemented in Delphi XE2? and, ¿what I'm doing wrong? thanks in advance.
Additional details:
Delphi XE2 (target: Win32)
Windows 7 x64
I found the problem; it didn't had to do with my code, it was right since the beginning; it has to do with a ping function in the DLL, it worked on a laptop but it doesn't want to work with a desktop PC (both Win7), and when it doesn't work it breaks subsequent function calls to the DLL (why, I don't know...yet). Anyway, it wasn't a full solution, but #DavidHeffernan was the first to come up with the idea that the problem was someplace else, so I'm accepting his answer mainly because it pointed me in the right direction. Thanks to everyone!
As described, your handling of RSI_CHANNEL is correct. Declaring it as Pointer is the appropriate action. To make type safety stronger you could define a distinct type rather than an alias:
Type
RSI_CHANNEL = type Pointer;
If the port parameter is really WORD then that maps to Word in Delphi.
As to your problem, it lies elsewhere. The translation of RSI_CHANNEL is accurate.
Since RSI_CHANNEL is a typed pointer in the C code, I would declare a similar typed pointer in Delphi to match, instead of using an untyped Pointer (this is also inline with modern Delphi versions using STRICT to avoid untyped pointers in Win32 API handle types like HWND, etc):
type
RSI_CHANNEL = ^__RSI_CHANNEL;
__RSI_CHANNEL = record
end;
Function rsiInitWsock(HostName : PAnsiChar; port : WORD) : RSI_CHANNEL; stdcall; external 'rsidll32';
I am trying to use a C macro from Vala. It seems to me that this should be possible with the CCode directive but I fail to find any meaningful documentation about how to use it.
There is a brief section about CCode arguments in "The Hacker's Guide to Vala"
and a mailing list thread about calling a C macro from Vala with CCode.
But neither resource really helps me to understand what CCode really does. It obviously affects how Vala generates C code, from the Hackers' Guide to Vala I can deduce that the CCode directive probably gives me direct influence into how the CCode tree is created when traversing Valas AST.
Could anybody explain a little more what CCode does?
Unfortunately, there isn't a great deal of documentation about CCode that makes sense alone. What you need to do is use it in conjunction with the VAPI files that comes with Vala. At its most basic, you will probably use your macro something like this:
[CCode(cname = "FOO", cheader_filename = "blah.h")]
public extern void foo();
Here we are setting the cname (i.e., the name that will be emitted into the C code), and the cheader_filename (i.e., the header file that should be #included). Most of the other CCode attributes control how arrays are handled. array_length = false indicates that an array is of unknown length. This can be applied to a parameter or to the method, indicating that is applies to the return type. For instance:
[CCode(array_length = false)] public int[] x();
[CCode(array_null_terminated = true)] public FileStream[] y();
public int[] z();
In this example, x will have unknown array length and have an expected C prototype of int *x(void), while y is assumed to have a null-terminated array with the expected C prototype of FILE **y(void). Finally, z is assumed to have an array length out parameter (i.e., a prototype of int *z(int *length), where length is a pointer to where to store the length of the returned array.
All of these can be applied to parameters too. It's also useful to specify array_length_pos if there is an array length but it is not the argument immediately after the array. If a parameter is a delegate, target_pos specifies where the user data is passed (i.e., the void* that goes with the function pointer).
There's also a variety of CCode attributes for use with delegates, classes, and structs. instance_pos specifies where the class/struct instance or delegate user data goes. All of the position arguments are specified with a floating point number. This allows multiple positions to be encoded. For instance, suppose we had a C prototype:
void foo(void* userdata, int length, double *dbl_array, void(*handler)(double,void*));
then we might write this:
[CCode(cname = "foo")]
public void foo([CCode(array_length_pos = 0.2)] double[] array, [CCode(target_pos = 0.1)] Handler func);
Given Handler is defined as a delegate elsewhere, you can see that the pos values put the arguments after argument 0 (i.e., the start) and then in a particular order.
Classes and structs have functions to handle initialisation, destruction, and reference counting, but those are fairly straight forward. Handling generics is also a bit complicated. Again, the VAPIs are the best source of insight. However, that's enough to get you started on your basic C functions and macros.
I'm using Delphi to make an XLL add-in for Excel, which involves making a lot of calls to the Excel4v function of xlcall32.dll. However, as I'm guessing very few Delphi experts here have worked with that specific API, I'm hoping that the problem might have been observed in other APIs too.
In C, specifically in the xlcall.h file that comes with the Microsoft Excel 2007 XLL SDK, Excel4v is defined as:
int pascal Excel4v(int xlfn, LPXLOPER operRes, int count, LPXLOPER opers[]);
In Delphi I'm using:
function Excel4v(xlfn: Integer; operRes: LPXLOPER; count: Integer;
opers: array of LPXLOPER): Integer; stdcall; external 'xlcall32.dll';
LPXLOPER is a pointer to a struct (in C) or record (in Delphi).
I've been doing my homework on declaring C functions in Delphi (this excellent article was a great help), and I think I'm declaring Excel4v properly. However, calls from Delphi code into that function cause exceptions ("access violation..." is what I keep seeing) unless they are followed by the following line:
asm pop sink; end;
Where "sink" is defined somewhere as an integer.
I have no clue about assembly... So there's no way would I have thought to try fixing the exceptions with "asm pop sink; end;". But "asm pop sink; end;" does indeed fix the exceptions. I first saw it used in this useful article on making XLLs using Delphi. Here's the most relevant quote:
"From Delphi the big stumbling block
with add-ins is the extra parameter
after the return address on the stack.
This comes free with every call to
Excel. I’ve never found out what it
holds, but so long as you throw it
away, your add-in will work fine. Add
the line asm pop variable, end; after
every call where variable can be any
global, local or object variable that
is at least 4 bytes long- integer is
fine. To repeat- THIS MUST BE INCLUDED
after every Excel4v call. Otherwise
you are constructing a time-bomb."
Basically I want to understand what's actually happening, and why. What could be causing a Win32 function to return an "extra parameter after the return address on the stack", and what does that actually mean?
Might there be another way to fix this, e.g. with a different compiler option or a different way of declaring the function?
And is there anything risky about calling "asm pop sink; end;" after every call to Excel4v...? It seems to work fine, but, as I don't understand what's going on, it feels a little dangerous...
I don't believe it's pascal vs stdcall - they are very similar calling conventions and should not result in a mismatched stack on function exit.
From the referenced article,
This would indeed be a very nice
syntax, but it is not the same as the
above array definition. Array-of
parameters are open array parameters.
They may look like any array, and they
do accept any array, but they get an
extra (hidden) parameter, which holds
the highest index in the array (the
High value). Since this is only so in
Delphi, and not in C or C++, you'd
have a real problem. (See also my
article on open arrays), since the
real number of parameters wouldn't
match.
You're getting the extra "highest array index" parameter being passed to the function. This is an int and has to be cleaned up when the function exits so that you don't wind up with a corrupted stack and crash. The article indicates how to pass arrays to C functions.
Something like:
type
PLPXLOPER = ^LPXLOPER;
And pass PLPXLOPER as the last parameter.
Your calling convention is wrong, specifically the "stdcall". The C declaration is specified as "pascal"
Stdcall passes parameters in right to left order, expects the routine to clean up, and does not use registers. Pascal, OTOH passes parameters in left to right order. Therefore, things are not happening the way the other half of the code expects in either case.
Change your Delphi declaration to also be "pascal" instead of "stdcall".
Most Windows functions use __stdcall for their calling conventions.
To allow access to the Win32 API from a scripting language (written in C), I would like to write a function such as the following:
void Call(LPCSTR DllName, LPCSTR FunctionName,
LPSTR ReturnValue, USHORT ArgumentCount, LPSTR Arguments[])
which will call, generically, any Win32 API function.
(the LPSTR parameters are essentially being used as byte arrays - assume that they have been correctly sized to take the correct data type external to the function. Also I believe that some additional complexity is required to distinguish between pointer and non-pointer arguments but I'm ignoring that for the purposes of this question).
The problem I have is passing the arguments into the Win32 API functions. Because these are stdcall I can't use varargs so the implementation of 'Call' must know about the number of arguments in advance and hence it cannot be generic...
I think I can do this with assembly code (by looping over the arguments, pushing each to the stack) but is this possible in pure C?
Update: I've marked the 'No it is not possible' answer as accepted for now. I will of course change this if a C-based solution comes to light.
Update: ruby/dl looks like it may be implemented using a suitable mechanism. Any details on this would be appreciated.
First things first: You cannot pass a type as a parameter in C. The only option you are left with is macros.
This scheme works with a little modification (array of void * for arguments), provided you are doing a LoadLibrary/GetProcAddress to call Win32 functions. Having a function name string otherwise will be of no use. In C, the only way you call a function is via its name (an identifier) which in most cases decays to a pointer to the function. You also have to take care of casting the return value.
My best bet:
// define a function type to be passed on to the next macro
#define Declare(ret, cc, fn_t, ...) typedef ret (cc *fn_t)(__VA_ARGS__)
// for the time being doesn't work with UNICODE turned on
#define Call(dll, fn, fn_t, ...) do {\
HMODULE lib = LoadLibraryA(dll); \
if (lib) { \
fn_t pfn = (fn_t)GetProcAddress(lib, fn); \
if (pfn) { \
(pfn)(__VA_ARGS__); \
} \
FreeLibrary(lib); \
} \
} while(0)
int main() {
Declare(int, __stdcall, MessageBoxProc, HWND, LPCSTR, LPCSTR, UINT);
Call("user32.dll", "MessageBoxA", MessageBoxProc,
NULL, ((LPCSTR)"?"), ((LPCSTR)"Details"),
(MB_ICONWARNING | MB_CANCELTRYCONTINUE | MB_DEFBUTTON2));
return 0;
}
No, I don't think its possible to do with without writing some assembly. The reason is you need precise control over what is on the stack before you call the target function, and there's no real way to do that in pure C. It is, of course, simple to do in Assembly though.
Also, you're using PCSTR for all of these arguments, which is really just const char *. But since all of these args aren't strings, what you actually want to use for return value and for Arguments[] is void * or LPVOID. This is the type you should use when you don't know the true type of the arguments, rather than casting them to char *.
The other posts are right about the almost certain need for assembly or other non-standard tricks to actually make the call, not to mention all of the details of the actual calling conventions.
Windows DLLs use at least two distinct calling conventions for functions: stdcall and cdecl. You would need to handle both, and might even need to figure out which to use.
One way to deal with this is to use an existing library to encapsulate many of the details. Amazingly, there is one: libffi. An example of its use in a scripting environment is the implementation of Lua Alien, a Lua module that allows interfaces to arbitrary DLLs to be created in pure Lua aside from Alien itself.
A lot of Win32 APIs take pointers to structs with specific layouts. Of these, a large subset follow a common pattern where the first DWORD has to be initialized to have the size of the struct before it is called. Sometimes they require a block of memory to be passed, into which they will write a struct, and the memory block must be of a size that is determined by first calling the same API with a NULL pointer and reading the return value to discover the correct size. Some APIs allocate a struct and return a pointer to it, such that the pointer must be deallocated with a second call.
I wouldn't be that surprised if the set of APIs that can be usefully called in one shot, with individual arguments convertable from a simple string representation, is quite small.
To make this idea generally applicable, we would have to go to quite an extreme:
typedef void DynamicFunction(size_t argumentCount, const wchar_t *arguments[],
size_t maxReturnValueSize, wchar_t *returnValue);
DynamicFunction *GenerateDynamicFunction(const wchar_t *code);
You would pass a simple snippet of code to GenerateDynamicFunction, and it would wrap that code in some standard boilerplate and then invoke a C compiler/linker to make a DLL from it (there are quite a few free options available), containing the function. It would then LoadLibrary that DLL and use GetProcAddress to find the function, and then return it. This would be expensive, but you would do it once and cache the resulting DynamicFunctionPtr for repeated use. You could do this dynamically by keeping pointers in a hashtable, keyed by the code snippets themselves.
The boilerplate might be:
#include <windows.h>
// and anything else that might be handy
void DynamicFunctionWrapper(size_t argumentCount, const wchar_t *arguments[],
size_t maxReturnValueSize, wchar_t *returnValue)
{
// --- insert code snipped here
}
So an example usage of this system would be:
DynamicFunction *getUserName = GenerateDynamicFunction(
"GetUserNameW(returnValue, (LPDWORD)(&maxReturnValueSize))");
wchar_t userName[100];
getUserName(0, NULL, sizeof(userName) / sizeof(wchar_t), userName);
You could enhance this by making GenerateDynamicFunction accept the argument count, so it could generate a check at the start of the wrapper that the correct number of arguments has been passed. And if you put a hashtable in there to cache the functions for each encountered codesnippet, you could get close to your original example. The Call function would take a code snippet instead of just an API name, but would otherwise be the same. It would look up the code snippet in the hashtable, and if not present, it would call GenerateDynamicFunction and store the result in the hashtable for next time. It would then perform the call on the function. Example usage:
wchar_t userName[100];
Call("GetUserNameW(returnValue, (LPDWORD)(&maxReturnValueSize))",
0, NULL, sizeof(userName) / sizeof(wchar_t), userName);
Of course there wouldn't be much point doing any of this unless the idea was to open up some kind of general security hole. e.g. to expose Call as a webservice. The security implications exist for your original idea, but are less apparent simply because the original approach you suggested wouldn't be that effective. The more generally powerful we make it, the more of a security problem it would be.
Update based on comments:
The .NET framework has a feature called p/invoke, which exists precisely to solve your problem. So if you are doing this as a project to learn about stuff, you could look at p/invoke to get an idea of how complex it is. You could possibly target the .NET framework with your scripting language - instead of interpreting scripts in real time, or compiling them to your own bytecode, you could compile them to IL. Or you could host an existing scripting language from the many already available on .NET.
You could try something like this - it works well for win32 API functions:
int CallFunction(int functionPtr, int* stack, int size)
{
if(!stack && size > 0)
return 0;
for(int i = 0; i < size; i++) {
int v = *stack;
__asm {
push v
}
stack++;
}
int r;
FARPROC fp = (FARPROC) functionPtr;
__asm {
call fp
mov dword ptr[r], eax
}
return r;
}
The parameters in the "stack" argument should be in reverse order (as this is the order they are pushed onto the stack).
Having a function like that sounds like a bad idea, but you can try this:
int Call(LPCSTR DllName, LPCSTR FunctionName,
USHORT ArgumentCount, int args[])
{
void STDCALL (*foobar)()=lookupDLL(...);
switch(ArgumentCount) {
/* Note: If these give some compiler errors, you need to cast
each one to a func ptr type with suitable number of arguments. */
case 0: return foobar();
case 1: return foobar(args[0]);
...
}
}
On a 32-bit system, nearly all values fit into a 32-bit word and shorter values are pushed onto stack as 32-bit words for function call arguments, so you should be able to call virtually all Win32 API functions this way, just cast the arguments to int and the return value from int to the appropriate types.
I'm not sure if it will be of interest to you, but an option would be to shell out to RunDll32.exe and have it execute the function call for you. RunDll32 has some limitations and I don't believe you can access the return value whatsoever but if you form the command line arguments properly it will call the function.
Here's a link
First, you should add the size of each argument as an extra parameter. Otherwise, you need to divine the size of each parameter for each function to push onto the stack, which is possible for WinXX functions since they have to be compatible with the parameters they are documented, but tedious.
Secondly, there isn't a "pure C" way to call a function without knowing the arguments except for a varargs function, and there is no constraint on the calling convention used by a function in a .DLL.
Actually, the second part is more important than the first.
In theory, you could set up a preprocessor macro/#include structure to generate all combinations of parameter types up to, say, 11 parameters, but that implies that you know ahead of time which types will be passed through you function Call. Which is kind of crazy if you ask me.
Although, if you really wanted to do this unsafely, you could pass down the C++ mangled name and use UnDecorateSymbolName to extract the types of the parameters. However, that won't work for functions exported with C linkage.