C# P/调用bug还是我做错了?

C# P/调用bug还是我做错了?,c#,windows,winapi,pinvoke,win32-process,C#,Windows,Winapi,Pinvoke,Win32 Process,因此,我编写了以下代码: using (var serviceController = new ServiceController(serviceName)) { var serviceHandle = serviceController.ServiceHandle; using (failureActionsStructure.Lock()) { success = NativeMethods.ChangeServiceConfig2W(

因此,我编写了以下代码:

using (var serviceController = new ServiceController(serviceName))
{
    var serviceHandle = serviceController.ServiceHandle;

    using (failureActionsStructure.Lock())
    {
        success = NativeMethods.ChangeServiceConfig2W(
            serviceHandle,
            ServiceConfigType.SERVICE_CONFIG_FAILURE_ACTIONS,
            ref failureActionsStructure);

        if (!success)
            throw new Win32Exception();
    }
}
p/Invoke声明如下:

[DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool ChangeServiceConfig2W(IntPtr hService, ServiceConfigType dwInfoLevel, ref SERVICE_FAILURE_ACTIONSW lpInfo);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct SERVICE_FAILURE_ACTIONSW
{
    public int dwResetPeriod;
    public string lpRebootMsg;
    public string lpCommand;
    public int cActions;
    public IntPtr lpsaActionsPtr;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
    public SC_ACTION[] lpsaActions;

    class DataLock : IDisposable
    {
        IntPtr _buffer;

        public DataLock(ref SERVICE_FAILURE_ACTIONSW data)
        {
            int actionStructureSize = Marshal.SizeOf(typeof(SC_ACTION));

            // Allocate a buffer with a bit of extra space at the end, so that if the first byte isn't aligned to a 64-bit
            // boundary, we can simply ignore the first few bytes and find the next 64-bit boundary.
            _buffer = Marshal.AllocHGlobal(data.lpsaActions.Length * actionStructureSize + 8);

            data.lpsaActionsPtr = _buffer;

            // Round up to the next multiple of 8 to get a 64-bit-aligned pointer.
            if ((data.lpsaActionsPtr.ToInt64() & 7) != 0)
            {
                data.lpsaActionsPtr += 8;
                data.lpsaActionsPtr -= (int)((long)data.lpsaActionsPtr & ~7);
            }

            // Copy the data from lpsaActions into the buffer.
            IntPtr elementPtr = data.lpsaActionsPtr;

            for (int i=0; i < data.lpsaActions.Length; i++, elementPtr += actionStructureSize)
                Marshal.StructureToPtr(data.lpsaActions[i], elementPtr, fDeleteOld: false);
        }

        public void Dispose()
        {
            Marshal.FreeHGlobal(_buffer);
        }
    }

    internal IDisposable Lock()
    {
        return new DataLock(ref this);
    }
}
ServiceConfigType
只是一个
enum
,此特定成员的值为2。
服务故障操作SW
结构定义如下:

[DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool ChangeServiceConfig2W(IntPtr hService, ServiceConfigType dwInfoLevel, ref SERVICE_FAILURE_ACTIONSW lpInfo);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct SERVICE_FAILURE_ACTIONSW
{
    public int dwResetPeriod;
    public string lpRebootMsg;
    public string lpCommand;
    public int cActions;
    public IntPtr lpsaActionsPtr;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
    public SC_ACTION[] lpsaActions;

    class DataLock : IDisposable
    {
        IntPtr _buffer;

        public DataLock(ref SERVICE_FAILURE_ACTIONSW data)
        {
            int actionStructureSize = Marshal.SizeOf(typeof(SC_ACTION));

            // Allocate a buffer with a bit of extra space at the end, so that if the first byte isn't aligned to a 64-bit
            // boundary, we can simply ignore the first few bytes and find the next 64-bit boundary.
            _buffer = Marshal.AllocHGlobal(data.lpsaActions.Length * actionStructureSize + 8);

            data.lpsaActionsPtr = _buffer;

            // Round up to the next multiple of 8 to get a 64-bit-aligned pointer.
            if ((data.lpsaActionsPtr.ToInt64() & 7) != 0)
            {
                data.lpsaActionsPtr += 8;
                data.lpsaActionsPtr -= (int)((long)data.lpsaActionsPtr & ~7);
            }

            // Copy the data from lpsaActions into the buffer.
            IntPtr elementPtr = data.lpsaActionsPtr;

            for (int i=0; i < data.lpsaActions.Length; i++, elementPtr += actionStructureSize)
                Marshal.StructureToPtr(data.lpsaActions[i], elementPtr, fDeleteOld: false);
        }

        public void Dispose()
        {
            Marshal.FreeHGlobal(_buffer);
        }
    }

    internal IDisposable Lock()
    {
        return new DataLock(ref this);
    }
}
..
SC_ACTION_TYPE
是一个简单的
enum

enum SC_ACTION_TYPE
{
    SC_ACTION_NONE = 0,
    SC_ACTION_RESTART = 1,
    SC_ACTION_REBOOT = 2,
    SC_ACTION_RUN_COMMAND = 3,
}
我要传递的结构初始化如下:

[StructLayout(LayoutKind.Sequential)]
struct SC_ACTION
{
    public SC_ACTION_TYPE Type;
    public int Delay;
}
var failureActionsStructure =
    new SERVICE_FAILURE_ACTIONSW()
    {
        dwResetPeriod = 60000, // 60 seconds
        lpRebootMsg = "",
        lpCommand = "",
        cActions = 6,
        lpsaActions =
            new SC_ACTION[]
            {
                new SC_ACTION() { Type = SC_ACTION_TYPE.SC_ACTION_RESTART /* 1 */, Delay = 5000 /* 5 seconds */ },
                new SC_ACTION() { Type = SC_ACTION_TYPE.SC_ACTION_RESTART /* 1 */, Delay = 15000 /* 15 seconds */ },
                new SC_ACTION() { Type = SC_ACTION_TYPE.SC_ACTION_RESTART /* 1 */, Delay = 25000 /* 25 seconds */ },
                new SC_ACTION() { Type = SC_ACTION_TYPE.SC_ACTION_RESTART /* 1 */, Delay = 35000 /* 35 seconds */ },
                new SC_ACTION() { Type = SC_ACTION_TYPE.SC_ACTION_RESTART /* 1 */, Delay = 45000 /* 45 seconds */ },
                new SC_ACTION() { Type = SC_ACTION_TYPE.SC_ACTION_NONE /* 0 */, Delay = 0 /* immediate, and this last entry is then repeated indefinitely */ },
            },
    };
当我在64位进程中运行这段代码时,它工作得很好。但是,当我在32位进程中运行它时(实际上我只在32位Windows安装上测试了32位进程——我不知道64位Windows安装上的32位进程会发生什么),我总是返回
ERROR\u INVALID\u HANDLE

我做了一些挖掘。
ChangeServiceConfig2W
API函数使用调用约定
stdcall
,这意味着当函数中的第一个操作码即将执行时,堆栈应包括:

  • (DWORD PTR)返回地址
  • (德沃德PTR)服务手柄
  • (DWORD)服务配置类型(2)
  • (DWORD PTR)指向故障动作结构的指针
但是,当我将本机调试器附加到32位C#进程并在
ChangeServiceConfig2W
的第一条指令上放置断点时(从技术上讲,是
_ChangeServiceConfig2WStub@12
),我发现堆栈包括:

  • (DWORD PTR)返回地址
  • (DWORD PTR)始终为值0x0000AFC8
  • (DWORD)值0x00000001,而不是预期的2
  • (DWORD PTR)实际上是指向故障动作结构的有效指针
< P>我用一个简单的C++应用程序确认了第二个和第三个<代码> dWord <代码> > <代码> [ESP] < /代码>应该是服务句柄和常量值2。我尝试了各种可选的P/Invoke声明,包括对前两个参数使用
int
而不是
IntPtr
ServiceConfigType
,但无法获得任何其他行为

最后,我更改了第三个参数
ref SERVICE\u FAILURE\u ACTIONSW
,改为直接使用
IntPtr
,并使用
Marshal.StructureToPtr
failureActionsStruct
手动封送到分配有
Marshal.AllocHGlobal
的块中。使用此声明,第一个和第二个参数现在可以正确封送

因此,我的问题是,我最初声明
ChangeServiceConfig2W
函数的方式是否有误,这可能是前两个参数未正确编组的原因?这种可能性似乎很小,但我无法消除在P/Invoke(特别是封送器)中遇到实际错误的可能性

奇怪的是,
DWORD
0x0000AFC8
是我要传递的
SC\u动作
结构中的一个动作的45000毫秒延迟。但是,它是该实例的最后一个成员,堆栈中
0x0000AFC8
后面的
0x00000001
将不是以下
SC\u操作的
类型。即使是这样,我也看不出是什么导致这些值被专门写入P/Invoke调用的参数区域。如果它将整个结构序列化到内存中的错误位置并覆盖堆栈的一部分,这不会导致内存损坏并可能终止进程吗


我很困惑。:-)

我相信我已经弄明白了发生了什么事。在我看来,这是P/Invoke封送器中的一个bug,但如果微软的官方说法是“这种行为是设计的”,我也不会感到惊讶

当您将数组配置为在结构中封送时,您的选项非常有限。据我所知,您可以对任何
VARIANT
基元类型使用
UnmanagedType.SafeArray
,也可以使用
UnmanagedType.ByValArray
,这要求您指定
SizeConst
——也就是说,封送处理程序只支持长度始终完全相同的嵌入式数组

然后,在计算结构的大小时,
Marshal.SizeOf
函数将数组的大小计算为
SizeConst*Marshal.SizeOf(arrayElementType)
。无论实例指向的数组的实际大小如何,情况总是如此

错误似乎是封送处理程序总是复制数组中的所有元素,即使该数字大于
SizeConst
。因此,在我的情况下,如果您将
SizeConst
设置为1,但提供一个包含6个元素的数组,那么它将基于
Marshal.SizeOf
分配内存,该函数为数组数据分配一个插槽,然后继续封送所有6个元素,写入超过缓冲区的末尾并损坏内存

参数的堆栈插槽以这种方式损坏的原因只能由封送拆收器在堆栈上为此序列化分配内存来解释。通过溢出缓冲区的末端,它会在堆栈上进一步重写数据,包括最终写入返回地址的位置,以及已经放入堆栈上插槽的前两个参数。在此封送处理操作之后,它将指向堆栈缓冲区的指针写入第三个参数,解释了为什么第三个参数的值实际上是指向数据结构的有效指针

我很幸运,在我特殊的配置下,第6个元素的结尾出现在进一步腐蚀堆栈上的其他元素之前,因为只有
ChangeServiceConfig2W
的前两个参数被损坏——在
ChangeServiceConfig2W
返回句柄无效的错误后,代码能够继续执行。具有更大的ar