tde*_*may 1 c# c++ pinvoke marshalling variadic-functions
描述我试图解决的问题的最好方法是用代码说话.我在这个论坛上看到了很多__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) ;
}
Run Code Online (Sandbox Code Playgroud)
在另一个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 ?*/ ) ;
}
Run Code Online (Sandbox Code Playgroud)
那么其他模块中的用户代码应该如下所示......
SomeFunction("my format: %ld, %s", 5, "Some Useless string") ;
Run Code Online (Sandbox Code Playgroud)
那将是理想的,但我已准备好与之共存
SomeFunction("my format: %ld, %s", __arglist(5, "Some Useless string")) ;
Run Code Online (Sandbox Code Playgroud)
我不在乎数据是如何编组的.如果我使用__arglist或某些数组,只要我最终得到一个va_args,我就不在乎了
__arglist看起来像解决方案,我可以成功调用
ExportedSomeFunction(strFormat, __arglist(5, "Some Useless string")) ;
Run Code Online (Sandbox Code Playgroud)
但我无法弄清楚如何使用变量参数调用C#SomeFunction并将__arglist传递给导出的函数.
SomeFunction("my format: %ld, %s", __arglist(5, "Some Useless string")) ;
Run Code Online (Sandbox Code Playgroud)
我不能让这个工作....
[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
}
Run Code Online (Sandbox Code Playgroud)
这会编译,但不会产生预期的结果.在C++中收到的参数列表是错误的.
private static extern void ExportedSomeFunction(
[MarshalAs(UnmanagedType.LPWStr)] out string strPath,
RuntimeArgumentHandle args);
Run Code Online (Sandbox Code Playgroud)
这是我的建议如何解决这个问题.看看varargs.h哪些是VisualStudio的一部分.这可以让您了解其中的va_list含义.你可以看到:typedef char * va_list;.它只是一个指针.
不仅__arglist没有文档,我认为它不适用于64位进程.
您需要va_list在C#端动态构建.我相信这是一个比无证件更好的解决方案__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
}
Run Code Online (Sandbox Code Playgroud)
以及用法示例:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
该CombinedVariables班是用于构建va_list,然后你就可以将它传递给你的C++方法void SomeFunction(LPCWSTR pszFormat, va_list args).
您需要处理VariableStringArgument当前与ANSI一起使用的情况.你可能正在寻找Marshal.StringToHGlobalUni.