我只是想做一些托管/非托管互操作。为了获得扩展的错误信息,我决定注册dll提供的日志回调:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public unsafe delegate void LogCallback(void* arg1,int level,byte* fmt);
这个定义有效,但我得到的字符串如“格式%s探测大小=%d和得分=%d”。 我尝试添加__arglist关键字,但代表不允许这样做。
嗯,这对我来说并不那么引人注目,但我只是很好奇,有人可以在C#中获得varargs参数。 我知道我可以使用c ++进行互操作。 所以:有没有办法纯粹用C#做这个,有合理的efford?
编辑:对于那些仍然没有得到它的人:我不进口一个varargs函数但是将其作为回调导出,然后称为我的本机代码。我一次只能指定一个 - >只有一个重载可能,__ arglist不起作用。
答案 0 :(得分:4)
没有可能的方法。它是不可能的原因是因为变量参数列表在C中的工作方式。
在C中,变量参数只是作为额外参数被推送到堆栈(在我们的例子中是非托管堆栈)。 C不会记录堆栈上的参数数量,被调用的函数采用其最后的形式参数(varargs之前的最后一个参数)获取其位置并开始从堆栈中弹出参数。
它知道从堆栈弹出多少变量的方式是完全基于约定的 - 一些其他参数指示堆栈上有多少变量参数。对于printf,它通过解析格式字符串并在每次看到格式代码时弹出堆栈来实现。看起来您的回调类似。
对于CLR来处理它,它必须能够知道正确的约定来确定拾取所需的参数数量。您无法编写自己的处理程序,因为它需要访问您无权访问的非托管堆栈。所以你无法用C#做到这一点。
有关这方面的更多信息,您需要阅读C调用约定。
答案 1 :(得分:2)
以下是处理它的方法。它可能适用于您的情况,也可能不适用,具体取决于您的回调参数是否与printf
函数族一起使用。
首先,从vsprintf
导入_vscprintf
和msvcrt
:
[DllImport("msvcrt.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern int vsprintf(
StringBuilder buffer,
string format,
IntPtr args);
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int _vscprintf(
string format,
IntPtr ptr);
接下来,使用IntPtr
args指针声明您的委托:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public unsafe delegate void LogCallback(
void* arg1,
int level,
[In][MarshalAs(UnmanagedType.LPStr)] string fmt,
IntPtr args);
现在,当通过本机代码调用您的委托时,只需使用vsprintf
正确格式化消息:
private void LogCallback(void* data, int level, string fmt, IntPtr args)
{
var sb = new StringBuilder(_vscprintf(fmt, args) + 1);
vsprintf(sb, fmt, args);
//here formattedMessage has the value your are looking for
var formattedMessage = sb.ToString();
...
}
答案 2 :(得分:1)
实际上可以在CIL中使用:
.class public auto ansi sealed MSIL.TestDelegate
extends [mscorlib]System.MulticastDelegate
{
.method public hidebysig specialname rtspecialname
instance void .ctor(object 'object',
native int 'method') runtime managed
{
}
.method public hidebysig newslot virtual
instance vararg void Invoke() runtime managed
{
}
}
答案 3 :(得分:0)
以下文章介绍了略有不同的情况,可能会有所帮助:
答案 4 :(得分:0)
你需要P / invoke marshaller的支持才能做到这一点。编组人员不提供此类支持。因此无法完成。
答案 5 :(得分:0)
我不同意@ shf301,有可能。
对于PInvoke,您可以使用__arglist
,如下所示:
[DllImport("msvcrt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "printf")]
public static extern int PrintFormat([MarshalAs(UnmanagedType.LPStr)] string format, __arglist);
呼叫:PrintFormat("Hello %d", __arglist(2019));
对于委托和回调:
定义以下结构:
public unsafe struct VariableArgumentBuffer
{
public const int BufferLength = 64; // you can increase it if needed
public fixed byte Buffer[BufferLength];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static VariableArgumentBuffer Create(params object[] args)
{
VariableArgumentBuffer buffer = new VariableArgumentBuffer();
Write(ref buffer, args);
return buffer;
}
public static void Write(ref VariableArgumentBuffer buffer, params object[] args)
{
if (args == null)
return;
fixed (byte* ptr = buffer.Buffer)
{
int offset = 0;
for (int i = 0; i < args.Length; i++)
{
var arg = args[i];
if (offset + Marshal.SizeOf(arg) > BufferLength)
throw new ArgumentOutOfRangeException();
switch (arg)
{
case byte value:
*(ptr + offset++) = value;
break;
case short value:
*(short*)(ptr + offset) = value;
offset += sizeof(short);
break;
case int value:
*(int*)(ptr + offset) = value;
offset += sizeof(int);
break;
case long value:
*(long*)(ptr + offset) = value;
offset += sizeof(long);
break;
case IntPtr value:
*(IntPtr*)(ptr + offset) = value;
offset += IntPtr.Size;
break;
default: // TODO: Add more types
throw new NotImplementedException();
}
}
}
}
}
定义您的代表
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int PrintFormatDelegate([MarshalAs(UnmanagedType.LPStr)] string format, VariableArgumentBuffer arglist);
通话
callback("Hello %d %s", VariableArgumentBuffer.Create(2019, Marshal.StringToHGlobalAnsi("Merry christmas")));
用于实施
public static int MyPrintFormat(string format, VariableArgumentBuffer arglist)
{
var stream = new UnmanagedMemoryStream(arglist.Buffer, VariableArgumentBuffer.BufferLength);
var binary = new BinaryReader(stream);
....
}
format
才能知道被压入堆栈的内容,然后使用binary
读取参数。例如,如果您知道要推送一个int32,则可以使用binary.ReadInt32()
来读取它。如果您不了解此部分,请在评论中告诉我,以便我为您提供更多信息。