我有这样的功能:
typedef long long myint64;
typedef enum {
INT32_FIELD,
CHARP_FIELD,
INT64_FIELD,
} InfoType;
int32_t ReadInfo(void *handle, InfoType info, ...)
{
va_list arg;
va_start(arg, info);
void *argPtr = va_arg(arg, void*);
va_end(arg);
int32_t ret = 0;
int32_t *paramInt = NULL;
char **paramCharp = NULL;
myint64 *paramInt64 = NULL;
switch (info) {
case INT32_FIELD:
paramInt = static_cast<int32_t*>(argPtr);
*paramInt = functionWhichReturnsInt32();
break;
case CHARP_FIELD:
paramCharp = static_cast<char**>(argPtr);
*paramCharp = functionWhichReturnsCharPtr();
break;
case INT64_FIELD:
paramInt64 = static_cast<myint64*>(argPtr);
*paramInt64 = functionWhichReturnsInt64();
break;
default:
ret = -1;
break;
}
return ret;
}
从分离的c文件中调用此函数。此文件不包含ReadInfo函数的定义:
extern "C" {int32_t CDECL ReadInfo(intptr_t, int32_t, int32_t*);}
int32_t readInt()
{
int32_t value = 0;
int32_t *ptr = &value;
ReadInfo(handle, INT32_FIELD, ptr);
return value;
}
此调用仅在iOS arm64下失败。 arm7s和win32可以正常使用此调用。 (是的,我们唯一的64位目标平台是iOS arm64。) 在调试器中,我发现readInt函数中的ptr地址与我所使用的不同: void argPtr = va_arg(arg,void );
我是否在使用arg_list工作错误?
P.S。它不是一个普通的Objective C应用程序。它是原生Unity插件的一部分。但是在iOS中,Unity代码只是从C#转换为Objective C / C ++。这就是为什么你可以看到第二个声明:
extern "C" {int32_t CDECL ReadInfo(intptr_t, int32_t, int32_t*);}
答案 0 :(得分:0)
这个问题的原因在于IL2CPP,它生成带有变量参数的函数调用。并且它不使用我的类型,如InfoType,myint64。它使用特定于平台的信息变量类型。我估计大小可能不同。
我只为Unity API添加了3个新功能:
int32_t ReadInfoInt(void *handle, InfoType info, int *ret);
int32_t ReadInfoInt64(void *handle, InfoType info, myint64 *ret);
int32_t ReadInfoStr(void *handle, InfoType info, char **ret);
在这个函数中我只调用ReadInfo。
它是100%的解决方法,但它比用IL2CPP更好。
答案 1 :(得分:0)
这不是IL2CPP的问题,而是iOS或编译器的问题。
即使在最新的Xcode(10.1)和iOS(12.1)上,以下代码也可以重现该问题
typedef int __cdecl (*PInvokeFunc) (const char*, int);
int test()
{
PInvokeFunc fp = (PInvokeFunc)printf;
fp("Hello World: %d", 10);
return 0;
}
预期输出为:Hello World: 10
,但在iOS上它将为Hello World: ??? (Random number)
。
我在macOS和Linux上尝试了相同的代码,并且它们都可以正常工作。
我不确定它是否与Apple文档有关:
可变函数 具有可变数量参数的iOS ABI与通用版本完全不同。
通用过程调用标准的阶段A和B照常执行-特别是,即使大于16字节的可变参数聚合也通过引用传递给调用方分配的临时内存。之后,固定参数会像通常在iOS中一样分配给寄存器和堆栈插槽。
然后将NSRN向上舍入为8个字节的下一个倍数,并将每个可变参数指定给适当数量的8字节堆栈插槽。
C语言要求在调用之前提升小于int的参数,但除此之外,此ABI不会指定堆栈上未使用的字节。
此更改的结果是,类型va_list是char *的别名,而不是通用PCS中指定的结构类型的别名。编译C ++代码时,它也不在std名称空间中。
更新:
Apple工程师的回信:
使用铸造函数指针添加其他调用约定不会更改被调用方的表示方式,只会更改调用方执行其调用的方式。 printf已经有一个调用约定,并且您所做的可能会在某些平台上适用于某些组合,而不适用于其他平台。您想改为声明一个包装函数,该包装函数具有所需的调用约定,并调用所需的函数。您需要手动封送参数。
也就是说,可变参数函数不能是直接p /调用,除非IL2CPP为其生成包装函数。仅函数指针是不够的。