封送变量参数-__arglist或替代方法 [英] marshaling variable arguments - __arglist or alternative

查看:105
本文介绍了封送变量参数-__arglist或替代方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

描述我要解决的问题的最佳方法是用代码交谈.我在该论坛上看到很多__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.

__arglist不仅没有文档,您需要在C#端动态构建va_list.我相信,这是比未公开的__arglist更好的解决方案,并且似乎运行良好.对于C#,您要使用params[],在C ++接收端,请使用va_list.每个可变参数函数都应具有以v...开头的函数,例如vsprintf,该函数接收va_list,而不是摆弄堆栈中的参数.

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆