封送变量参数-__arglist或替代方法 [英] marshaling variable arguments - __arglist or alternative
问题描述
描述我要解决的问题的最佳方法是用代码交谈.我在该论坛上看到很多__arglist问题,但没有很多有用的答案.我知道_arglist应该避免使用,因此我愿意接受其他方法
Best way to describe the problem I'm trying to solve is to talk in code. I see a lot of __arglist questions on this forum, but not a lot of helpful answers. I know _arglist should be avoided so I'm open to alternative methods
在一个C ++模块中,我有类似以下内容
In one C++ module I have something like the following
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的代码. 目的是隐藏本机API的所有复杂性,并从用户代码中进行编组. 最终目标是C ++开发人员或C#开发人员进行SAME API调用,但是代码只编写一次并导出到
in another C# module I have code that handles all marshalling to the C++ DLL. The intent is to hide all complexity of Native APIs and marshalling from user code. The ultimate goal being a C++ developer or C# developer make the SAME API calls, but the code is written once and exported to both
[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 ?*/ ) ;
}
然后其他模块中的用户代码应如下所示……
Then the user code in some other module should look like this...
SomeFunction("my format: %ld, %s", 5, "Some Useless string") ;
那将是理想的,但准备与之共存
That would be ideal, but am prepared to live with
SomeFunction("my format: %ld, %s", __arglist(5, "Some Useless string")) ;
我不在乎数据如何封送.如果我使用__arglist或某些数组,则只要我以va_args结尾,我就不会在意
I don't care how the data gets marshaled. If I use __arglist or some array, I don't care as long as I end up with a va_args
__ arglist看起来像解决方案,我可以成功致电
__arglist looks like the solution, and I can successfully call
ExportedSomeFunction(strFormat, __arglist(5, "Some Useless string")) ;
但是我无法弄清楚如何使用可变参数调用C#SomeFunction并将__arglist传递给导出的函数.
But I cannot figure out how to call the C# SomeFunction with variable arguments and pass a __arglist to the exported function.
SomeFunction("my format: %ld, %s", __arglist(5, "Some Useless string")) ;
我无法使它正常工作....
I cannot get this to work....
[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 ++中收到的参数列表是错误的.
This compiles, but doesn't produce the desired results. The argument list received in C++ is wrong.
private static extern void ExportedSomeFunction(
[MarshalAs(UnmanagedType.LPWStr)] out string strPath,
RuntimeArgumentHandle args);
推荐答案
这是我的解决方法.看一下varargs.h
,它是VisualStudio的一部分.这使您可以洞悉va_list
的含义.您可以看到:typedef char * va_list;
.只是一个指针.
Here is my suggestion how to tackle this. Take a look at varargs.h
which is part of VisualStudio. This gives you some insight what the va_list
means. You can see: typedef char * va_list;
. It's just a pointer.
You need to build the va_list
dynamically on C# side. I believe that this is better solution than undocumented __arglist
and it seems to be working nicely. For C#, you want to use params[]
, and on C++ receiving side, va_list
. Every variadic function should have function starting with v...
, such as vsprintf
, that receives va_list
, instead of fiddling with arguments in the stack.
将这种美丽复制/粘贴到您的解决方案中:
Copy/paste this beauty to your solution:
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)
.
The CombinedVariables
class is used to build va_list
, and then you can pass it to your C++ method void SomeFunction(LPCWSTR pszFormat, va_list args)
.
由于VariableStringArgument
当前与ANSI兼容,因此需要注意.您可能正在寻找Marshal.StringToHGlobalUni
.
You need to take care of the VariableStringArgument
as it works with ANSI currently. You're probably looking for Marshal.StringToHGlobalUni
.
这篇关于封送变量参数-__arglist或替代方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!