描述我试图解决的问题的最佳方式是用代码进行交谈。我在这个论坛上看到了很多__arglist问题,但没有很多有用的答案。我知道应该避免使用_arglist,所以我可以选择其他方法
在一个C ++模块中,我有类似下面的内容
void SomeFunction(LPCWSTR pszFormat, va_args args)
{
// this function is not exported...
// it is designed to take a format specifier and a list of variable
// arguments and "printf" it into a buffer. This code
// allocates buffer and uses _vsnwprintf_s to format the
// string.
// I really do not have much flexibility to rewrite this function
// so please steer away from scrutinizing this. it is what is
// and I need to call it from C#.
::_vsnwprintf_s(somebuff, buffsize, _TRUNCATE, pszFormat, args)
}
__declspec(dllexport) void __cdecl ExportedSomeFunction(LPCWSTR pszFormat, ...)
{
// the purpose of this method is to export SomeFunction to C# code.
// it handles any marshaling. I can change this however it makes sense
va_list args ;
va_start(args, pszFormat) ;
SomeFunction(pszFormat, args) ;
va_end(args) ;
}
在另一个C#模块中,我有代码处理所有对C ++ DLL的编组。 目的是隐藏Native API的所有复杂性并从用户代码中编组。 最终目标是C ++开发人员或C#开发人员进行SAME API调用,但代码只编写一次并导出到两者
[DllImport("mymodule.dll", CallingConvention=CallingConvention.Cdecl)]
private static extern void ExportedSomeFunction(
[MarshalAs(UnmanagedType.LPWStr)] out string strPath,
/* ? what goes here ? */);
void SomeFunction(string strFormat, /*? what goes here ?*/ )
{
// handles marshalling incoming data to what ever is needed by exported C function
ExportedSomeFunction(strFormat, /*? something ?*/ ) ;
}
然后其他模块中的用户代码应如下所示......
SomeFunction("my format: %ld, %s", 5, "Some Useless string") ;
那将是理想的,但我准备和
一起生活SomeFunction("my format: %ld, %s", __arglist(5, "Some Useless string")) ;
我并不关心数据是如何编组的。如果我使用__arglist或某些数组,只要我最终得到一个va_args
我就不在乎__ arglist看起来像解决方案,我可以成功调用
ExportedSomeFunction(strFormat, __arglist(5, "Some Useless string")) ;
但我无法弄清楚如何使用变量参数调用C#SomeFunction并将__arglist传递给导出的函数。
SomeFunction("my format: %ld, %s", __arglist(5, "Some Useless string")) ;
我无法让这个工作......
[DllImport("mymodule.dll", CallingConvention=CallingConvention.Cdecl)]
private static extern void ExportedSomeFunction(
[MarshalAs(UnmanagedType.LPWStr)] out string strPath,
__arglist);
void SomeFunction(string strFormat, __arglist )
{
ExportedSomeFunction(strFormat, __arglist) ; // error cannot convert from RuntimeArgumentHandle to __arglist
}
这会编译,但不会产生预期的结果。在C ++中收到的参数列表是错误的。
private static extern void ExportedSomeFunction(
[MarshalAs(UnmanagedType.LPWStr)] out string strPath,
RuntimeArgumentHandle args);
答案 0 :(得分:5)
这是我的建议如何解决这个问题。看看varargs.h
是VisualStudio的一部分。这可以让您了解va_list
的含义。您可以看到:typedef char * va_list;
。它只是一个指针。
__arglist
不仅没有记录,I don't think it works correctly on 64-bit processes.
您需要在C#端动态构建va_list
。我相信这是比未记录的__arglist
更好的解决方案,而且似乎工作得很好。对于C#,您希望使用params[]
,并在C ++接收端使用va_list
。每个可变函数都应该具有以v...
开头的函数,例如vsprintf
,它接收va_list
,而不是摆弄堆栈中的参数。
将此美容复制/粘贴到您的解决方案中:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
// Author: Chris Eelmaa
namespace ConsoleApplication1
{
#region VariableCombiner
class CombinedVariables : IDisposable
{
readonly IntPtr _ptr;
readonly IList<IDisposable> _disposables;
bool _disposed;
public CombinedVariables(VariableArgument[] args)
{
_disposables = new List<IDisposable>();
_ptr = Marshal.AllocHGlobal(args.Sum(arg => arg.GetSize()));
var curPtr = _ptr;
foreach (var arg in args)
{
_disposables.Add(arg.Write(curPtr));
curPtr += arg.GetSize();
}
}
public IntPtr GetPtr()
{
if(_disposed)
throw new InvalidOperationException("Disposed already.");
return _ptr;
}
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
foreach (var disposable in _disposables)
disposable.Dispose();
Marshal.FreeHGlobal(_ptr);
}
}
}
#endregion
#region VariableArgument
abstract class VariableArgument
{
#region SentinelDispose
protected static readonly IDisposable SentinelDisposable =
new SentinelDispose();
class SentinelDispose : IDisposable
{
public void Dispose()
{
}
}
#endregion
public abstract IDisposable Write(IntPtr buffer);
public virtual int GetSize()
{
return IntPtr.Size;
}
public static implicit operator VariableArgument(int input)
{
return new VariableIntegerArgument(input);
}
public static implicit operator VariableArgument(string input)
{
return new VariableStringArgument(input);
}
public static implicit operator VariableArgument(double input)
{
return new VariableDoubleArgument(input);
}
}
#endregion
#region VariableIntegerArgument
sealed class VariableIntegerArgument : VariableArgument
{
readonly int _value;
public VariableIntegerArgument(int value)
{
_value = value;
}
public override IDisposable Write(IntPtr buffer)
{
Marshal.Copy(new[] { _value }, 0, buffer, 1);
return SentinelDisposable;
}
}
#endregion
#region VariableDoubleArgument
sealed class VariableDoubleArgument : VariableArgument
{
readonly double _value;
public VariableDoubleArgument(double value)
{
_value = value;
}
public override int GetSize()
{
return 8;
}
public override IDisposable Write(IntPtr buffer)
{
Marshal.Copy(new[] { _value }, 0, buffer, 1);
return SentinelDisposable;
}
}
#endregion
#region VariableStringArgument
sealed class VariableStringArgument : VariableArgument
{
readonly string _value;
public VariableStringArgument(string value)
{
_value = value;
}
public override IDisposable Write(IntPtr buffer)
{
var ptr = Marshal.StringToHGlobalAnsi(_value);
Marshal.Copy(new[] {ptr}, 0, buffer, 1);
return new StringArgumentDisposable(ptr);
}
#region StringArgumentDisposable
class StringArgumentDisposable : IDisposable
{
IntPtr _ptr;
public StringArgumentDisposable(IntPtr ptr)
{
_ptr = ptr;
}
public void Dispose()
{
if (_ptr != IntPtr.Zero)
{
Marshal.FreeHGlobal(_ptr);
_ptr = IntPtr.Zero;
}
}
}
#endregion
}
#endregion
}
以及使用示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(
AmazingSPrintf("I am %s, %d years old, %f meters tall!",
"Chris",
24,
1.94));
}
static string AmazingSPrintf(string format, params VariableArgument[] args)
{
if (!args.Any())
return format;
using (var combinedVariables = new CombinedVariables(args))
{
var bufferCapacity = _vscprintf(format, combinedVariables.GetPtr());
var stringBuilder = new StringBuilder(bufferCapacity + 1);
vsprintf(stringBuilder, format, combinedVariables.GetPtr());
return stringBuilder.ToString();
}
}
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
static extern int vsprintf(
StringBuilder buffer,
string format,
IntPtr ptr);
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
static extern int _vscprintf(
string format,
IntPtr ptr);
}
}
CombinedVariables
类用于构建va_list
,然后您可以将其传递给C ++方法void SomeFunction(LPCWSTR pszFormat, va_list args)
。
您需要处理VariableStringArgument
,因为它目前适用于ANSI。您可能正在寻找Marshal.StringToHGlobalUni
。
答案 1 :(得分:0)
请注意,va_list
和...
之间存在细微差别。 printf
使用...
,而vprintf
使用va_list
。 va_list
通常是指向...
的第一个元素的指针。 __arglist
适用于...
。
对于va_list
,您可以使用@Erti代码或我的代码:
public class VaList : IDisposable
{
protected readonly List<GCHandle> handles = new List<GCHandle>();
public VaList(bool unicode, params object[] args)
{
if (args == null)
{
throw new ArgumentNullException("args");
}
// The first handle is for the bytes array
handles.Add(default(GCHandle));
int total = 0;
var bytes = new PrimitiveToBytes[args.Length];
for (int i = 0; i < args.Length; i++)
{
int size = Convert(unicode, args[i], ref bytes[i]);
bytes[i].Size = size;
total += size;
}
// Instead of a byte[] we use a IntPtr[], so copying elements
// inside is faster (perhaps :-) )
var buffer = new IntPtr[total / IntPtr.Size];
handles[0] = GCHandle.Alloc(buffer, GCHandleType.Pinned);
for (int i = 0, j = 0; i < args.Length; i++)
{
buffer[j++] = bytes[i].IntPtr;
// long or double with IntPtr == 4
if (bytes[i].Size > IntPtr.Size)
{
buffer[j++] = (IntPtr)bytes[i].Int32High;
}
}
}
// Overload this to handle other types
protected virtual int Convert(bool unicode, object arg, ref PrimitiveToBytes primitiveToBytes)
{
int size;
if (arg == null)
{
primitiveToBytes.IntPtr = IntPtr.Zero;
size = IntPtr.Size;
}
else
{
Type type = arg.GetType();
TypeCode typeHandle = Type.GetTypeCode(type);
switch (typeHandle)
{
case TypeCode.Boolean:
// Boolean converted to Int32
primitiveToBytes.Int32 = (bool)arg ? 1 : 0;
size = IntPtr.Size;
break;
case TypeCode.SByte:
primitiveToBytes.SByte = (sbyte)arg;
size = IntPtr.Size;
break;
case TypeCode.Byte:
primitiveToBytes.Byte = (byte)arg;
size = IntPtr.Size;
break;
case TypeCode.Int16:
primitiveToBytes.Int16 = (short)arg;
size = IntPtr.Size;
break;
case TypeCode.UInt16:
primitiveToBytes.UInt16 = (ushort)arg;
size = IntPtr.Size;
break;
case TypeCode.Int32:
primitiveToBytes.Int32 = (int)arg;
size = IntPtr.Size;
break;
case TypeCode.UInt32:
primitiveToBytes.UInt32 = (uint)arg;
size = IntPtr.Size;
break;
case TypeCode.Int64:
primitiveToBytes.Int64 = (long)arg;
size = sizeof(long);
break;
case TypeCode.UInt64:
primitiveToBytes.UInt64 = (ulong)arg;
size = sizeof(ulong);
break;
case TypeCode.Single:
// Single converted to Double
primitiveToBytes.Double = (double)(float)arg;
size = sizeof(double);
break;
case TypeCode.Double:
primitiveToBytes.Double = (double)arg;
size = sizeof(double);
break;
case TypeCode.Char:
if (unicode)
{
primitiveToBytes.UInt16 = (char)arg;
}
else
{
byte[] bytes = Encoding.Default.GetBytes(new[] { (char)arg });
if (bytes.Length > 0)
{
primitiveToBytes.B0 = bytes[0];
if (bytes.Length > 1)
{
primitiveToBytes.B1 = bytes[1];
if (bytes.Length > 2)
{
primitiveToBytes.B2 = bytes[2];
if (bytes.Length > 3)
{
primitiveToBytes.B3 = bytes[3];
}
}
}
}
}
size = IntPtr.Size;
break;
case TypeCode.String:
{
string str = (string)arg;
GCHandle handle;
if (unicode)
{
handle = GCHandle.Alloc(str, GCHandleType.Pinned);
}
else
{
byte[] bytes = new byte[Encoding.Default.GetByteCount(str) + 1];
Encoding.Default.GetBytes(str, 0, str.Length, bytes, 0);
handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
}
handles.Add(handle);
primitiveToBytes.IntPtr = handle.AddrOfPinnedObject();
size = IntPtr.Size;
}
break;
case TypeCode.Object:
if (type == typeof(IntPtr))
{
primitiveToBytes.IntPtr = (IntPtr)arg;
size = IntPtr.Size;
}
else if (type == typeof(UIntPtr))
{
primitiveToBytes.UIntPtr = (UIntPtr)arg;
size = UIntPtr.Size;
}
else if (!type.IsValueType)
{
GCHandle handle = GCHandle.Alloc(arg, GCHandleType.Pinned);
primitiveToBytes.IntPtr = handle.AddrOfPinnedObject();
size = IntPtr.Size;
}
else
{
throw new NotSupportedException();
}
break;
default:
throw new NotSupportedException();
}
}
return size;
}
~VaList()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
for (int i = 0; i < handles.Count; i++)
{
if (handles[i].IsAllocated)
{
handles[i].Free();
}
}
handles.Clear();
}
public IntPtr AddrOfPinnedObject()
{
if (handles.Count == 0)
{
throw new ObjectDisposedException(GetType().Name);
}
return handles[0].AddrOfPinnedObject();
}
[StructLayout(LayoutKind.Explicit)]
protected struct PrimitiveToBytes
{
[FieldOffset(0)]
public byte B0;
[FieldOffset(1)]
public byte B1;
[FieldOffset(2)]
public byte B2;
[FieldOffset(3)]
public byte B3;
[FieldOffset(4)]
public byte B4;
[FieldOffset(5)]
public byte B5;
[FieldOffset(6)]
public byte B6;
[FieldOffset(7)]
public byte B7;
[FieldOffset(4)]
public int Int32High;
[FieldOffset(0)]
public byte Byte;
[FieldOffset(0)]
public sbyte SByte;
[FieldOffset(0)]
public short Int16;
[FieldOffset(0)]
public ushort UInt16;
[FieldOffset(0)]
public int Int32;
[FieldOffset(0)]
public uint UInt32;
[FieldOffset(0)]
public long Int64;
[FieldOffset(0)]
public ulong UInt64;
[FieldOffset(0)]
public float Single;
[FieldOffset(0)]
public double Double;
[FieldOffset(0)]
public IntPtr IntPtr;
[FieldOffset(0)]
public UIntPtr UIntPtr;
[FieldOffset(8)]
public int Size;
}
}
使用示例:
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
static extern int vprintf(string format, IntPtr ptr);
和
using (var list = new VaList(false, // Ansi encoding
true, // bool test
short.MinValue + 1, int.MinValue + 2, long.MinValue + 3, // signed
ushort.MaxValue - 4, uint.MaxValue - 5, ulong.MaxValue - 6, // unsigned
float.MaxValue, double.MaxValue, // float/double
'A', "Foo", Encoding.Default.GetBytes("Bar\0"), null, // char/string
IntPtr.Size == sizeof(int) ? (IntPtr)(int.MinValue + 7) : (IntPtr)(long.MinValue + 7), // signed ptr
UIntPtr.Size == sizeof(uint) ? (UIntPtr)(uint.MaxValue - 8) : (UIntPtr)(ulong.MaxValue - 8))) // unsigned ptr
{
vprintf("%d\n %hd\n %d\n %lld\n %hu\n %u\n %llu\n %f\n %f\n %c\n %s\n %s\n %s\n %p\n %p\n", list.AddrOfPinnedObject());
}
请注意,此代码仅与用于Intel x86 / x64的Visual C ++兼容。 ARM使用另一种格式,GCC还有其他格式。