尝试枚举终端服务器会话会产生 AccessViolationException(.NET 和本机 DLL)

Trying to enumerate Terminal Server sessions produces AccessViolationException (.NET and Native DLL)

我正在尝试使用 WtsApi32.dll 中定义的函数 WtsEnumerateSessionsExA 在 C# 中枚举所有远程桌面会话。

此函数写入一个 WTS_SESSION_INFO_1A 结构数组,其指针作为参数传递。您可以在此处找到文档:

我在 C# 中实现了 WTS_SESSION_INFO_1A 结构和 WTS_CONNECT_STATE_CLASS,如下所示:

struct WtsSessionInfoClass
{
    uint ExecEnvId;
    WtsConnectStateClass state;
    uint sessionId;
    string pSessionName;
    string pHostName;
    string pUserName;
    string pDomainName;
    string pFarmName;
}
enum WtsConnectStateClass
{
    WTSActive,
    WTSConnected,
    WTSConnectQuery,
    WTSShadow,
    WTSDisconnected,
    WTSIdle,
    WTSListen,
    WTSReset,
    WTSDown,
    WTSInit
}

我已经在名为 Native32:

的 class 中导入了所需的函数
[DllImport("WtsApi32.dll")]
internal static extern IntPtr WTSOpenServerEx(string name);

[DllImport("WtsApi32.dll")]
internal static extern bool WTSEnumerateSessionsExA(
    IntPtr hServer,
    UIntPtr pLevel,
    uint Filter,
    out WtsSessionInfoClass[] ppSessionInfo,
    UIntPtr pCount
);

最后我的实现如下:

UIntPtr len = new UIntPtr();
WtsSessionInfoClass[] sessions;
IntPtr handle = Native32.WTSOpenServerEx("MyMachineName"); // This function gets the server handle and works fine
Native32.WTSEnumerateSessionsExA(handle, new UIntPtr(1), 0, out sessions, len); // This function will produce AccessViolationException
...

执行代码时,调用Native32.WTSEnumerateSessionsExA失败,原因是AccessViolationException

我在这里做错了什么?感谢任何帮助。

我最好的猜测是您没有以正确的方式传递第二个参数 pLevel。您试图传递一个指向值为 1 的 DWORD 的指针,但您实际传递的是地址 1。相反,声明一个初始化为 1 的局部变量并传递它的地址。

更多详情:

您链接的 WTSEnumerateSessionsExA 的文档页面是这样说的:

pLevel

This parameter is reserved. Always set this parameter to one. On output, WTSEnumerateSessionsEx does not change the value of this parameter.

这里的措辞不是最好的,但我的解释是你需要为 pLevel 传递一个有效的指针,并且该指针需要指向一个正确对齐的值为 1 的 DWORD。

对我来说,另一种解读是您需要传递文字内存地址 1 作为 pLevel 的指针。我本能地觉得这很荒谬。

但是,您的代码:

Native32.WTSEnumerateSessionsExA(
  handle, new UIntPtr(1), 0, out sessions, len);

通过 new UIntPtr(1) 获得 pLevel。根据我对 UIntPtr doc pages 的阅读,该表达式将创建一个字面值为 1 的指针,换句话说,地址 1.

如果您调试 WTSEnumerateSessionsEx 实现的汇编代码,我猜您会看到它尝试取消引用 pLevel,然后由于您在尝试加载 DWORD 时看到的访问冲突而崩溃来自地址 1.

要解决此问题,您可以声明一个包含值 1 的 uint 变量,并将地址传递给该变量:

uint enumerateSessionsLevel = 1;
Native32.WTSEnumerateSessionsExA(
  handle, &enumerateSessionsLevel, 0, out sessions, len);