所以,我写了以下代码:
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);
ServiceConfigType只是一个枚举,此特定成员的值为2. SERVICE_FAILURE_ACTIONSW结构定义如下:
[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);
}
}
(此类型在结构的末尾有一个额外的成员,它没有出现在本机结构定义中,lpsaActions,它简化了这个结构的使用,并且只导致最后编组的额外数据 – 数据底层API只是忽略,因为它假设结构已经在内存中结束.)
SC_ACTION的定义如下:
[StructLayout(LayoutKind.Sequential)]
struct SC_ACTION
{
public SC_ACTION_TYPE Type;
public int Delay;
}
..和SC_ACTION_TYPE是一个简单的枚举:
enum SC_ACTION_TYPE
{
SC_ACTION_NONE = 0,
SC_ACTION_RESTART = 1,
SC_ACTION_REBOOT = 2,
SC_ACTION_RUN_COMMAND = 3,
}
我传入的结构初始化如下:
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位32位进程中会发生什么Windows安装),我总是得到ERROR_INVALID_HANDLE.
我做了一些挖掘. ChangeServiceConfig2W API函数使用stdcall调用约定,这意味着当函数中的第一个操作码即将执行时,堆栈应包含:
>(DWORD PTR)返回地址
>(DWORD PTR)服务处理
>(DWORD)服务配置类型(2)
>(DWORD PTR)指向失败动作结构的指针
但是,当我将本机调试器连接到我的32位C#进程并在ChangeServiceConfig2W的第一条指令上放置一个断点(技术上是_ChangeServiceConfig2WStub @ 12的第一条指令)时,我发现堆栈包含:
>(DWORD PTR)返回地址
>(DWORD PTR)始终为值0x0000AFC8
>(DWORD)值0x00000001,而不是预期的2
>(DWORD PTR)实际上是指向失败动作结构的有效指针
我在一个简单的C应用程序中确认,[ESP]的第二个和第三个DWORD应该是服务句柄和常量值2.我尝试了各种替代的P / Invoke声明,包括使用int而不是IntPtr和ServiceConfigType作为前两个参数,但无法获得任何其他行为.
最后,我更改了第三个参数ref SERVICE_FAILURE_ACTIONSW,直接取代IntPtr,并使用Marshal.StructuretoPtr手动将failureActionsstruct编组到一个用Marshal.AllocHGlobal分配的块中.有了这个声明,第一个和第二个参数现在正确编组.
所以,我的问题是,我在最初声明ChangeServiceConfig2W函数的方式上做错了什么,这可以解释前两个参数没有正确编组?可能性似乎很小,但我无法摆脱我在P / Invoke(特别是封送器)遇到实际错误的可能性.
奇怪的是,DWORD值0x0000AFC8是我传入的failureActionsstructure中的一个SC_ACTION结构的45,000 ms延迟.但是,它是该实例的最后一个成员,并且堆栈中0x0000AFC8之后的0x00000001将不是Type以下SC_ACTION.即使它是,我也看不出会导致这些值专门写入P / Invoke调用的参数区域的原因.如果它将整个结构序列化到内存中的错误位置并覆盖部分堆栈,是否会导致内存损坏并可能终止进程?
我很迷惑.