调用WTSEnumerateSessionsExA枚举终端服务会话触发AccessViolationException
解决WTSEnumerateSessionsExA调用时的AccessViolationException问题
你遇到的AccessViolationException主要是因为结构体定义不符合原生类型布局、P/Invoke函数参数声明错误这两个核心问题导致的,下面一步步拆解并修正:
1. 修正WTS_SESSION_INFO_1A结构体的定义
原生的WTS_SESSION_INFO_1A是C风格结构体,C#中必须严格匹配其内存布局和成员类型:
- 添加
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]属性,确保成员顺序和字符集与原生一致(A版本函数对应ANSI字符集) - 字符串成员不能直接用
string,原生返回的是char*指针,需要用IntPtr存储,之后手动通过Marshal.PtrToStringAnsi转换 - 成员名称建议和原生结构体保持一致,便于对照官方文档
修正后的结构体:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] struct WTS_SESSION_INFO_1A { public uint ExecEnvId; public WTS_CONNECT_STATE_CLASS State; public uint SessionId; public IntPtr pSessionName; public IntPtr pHostName; public IntPtr pUserName; public IntPtr pDomainName; public IntPtr pFarmName; }
2. 修正WTS_CONNECT_STATE_CLASS枚举
虽然C#枚举默认从0开始,但显式指定每个值的数值更安全,避免和原生定义不一致:
enum WTS_CONNECT_STATE_CLASS { WTSActive = 0, WTSConnected = 1, WTSConnectQuery = 2, WTSShadow = 3, WTSDisconnected = 4, WTSIdle = 5, WTSListen = 6, WTSReset = 7, WTSDown = 8, WTSInit = 9 }
3. 修正P/Invoke函数声明
WTSEnumerateSessionsExA的参数声明有几个关键错误:
ppSessionInfo:原生返回指向结构体数组的指针,不能直接用out WtsSessionInfoClass[],应该用out IntPtr,之后手动Marshal为结构体数组pCount:这是输入输出参数,需要用ref UIntPtr,函数会通过它返回会话数量- 添加
CallingConvention = CallingConvention.StdCall(WtsApi32函数使用stdcall调用约定) - 补充
WTSFreeMemoryEx函数声明,用来释放函数返回的内存,避免泄漏
修正后的Native32类:
internal static class Native32 { [DllImport("WtsApi32.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] internal static extern IntPtr WTSOpenServerEx(string name); [DllImport("WtsApi32.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool WTSEnumerateSessionsExA( IntPtr hServer, ref UIntPtr pLevel, uint Filter, out IntPtr ppSessionInfo, ref UIntPtr pCount ); [DllImport("WtsApi32.dll", CallingConvention = CallingConvention.StdCall)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool WTSFreeMemoryEx( WTS_MEMORY_TYPE MemoryType, IntPtr pMemory, UIntPtr NumberOfEntries ); [DllImport("WtsApi32.dll", CallingConvention = CallingConvention.StdCall)] internal static extern void WTSCloseServer(IntPtr hServer); internal enum WTS_MEMORY_TYPE { WTSMemTypeSessionInfoLevel1 = 1 } }
4. 正确的调用流程
现在可以安全调用函数,注意要处理内存释放和句柄关闭:
IntPtr serverHandle = Native32.WTSOpenServerEx("MyMachineName"); if (serverHandle == IntPtr.Zero) { // 处理服务器打开失败的情况 return; } UIntPtr sessionCount = UIntPtr.Zero; IntPtr sessionInfoPtr = IntPtr.Zero; try { UIntPtr level = new UIntPtr(1); // 指定信息级别为1,对应WTS_SESSION_INFO_1A bool success = Native32.WTSEnumerateSessionsExA( serverHandle, ref level, 0, // 0表示枚举所有会话 out sessionInfoPtr, ref sessionCount ); if (!success || sessionCount == UIntPtr.Zero || sessionInfoPtr == IntPtr.Zero) { // 处理枚举失败,可通过Marshal.GetLastWin32Error()获取错误码 return; } // 将指针转换为结构体数组 WTS_SESSION_INFO_1A[] sessions = new WTS_SESSION_INFO_1A[(int)sessionCount]; int structSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO_1A)); for (int i = 0; i < (int)sessionCount; i++) { IntPtr currentPtr = IntPtr.Add(sessionInfoPtr, i * structSize); sessions[i] = Marshal.PtrToStructure<WTS_SESSION_INFO_1A>(currentPtr); // 转换字符串成员 string sessionName = Marshal.PtrToStringAnsi(sessions[i].pSessionName); string hostName = Marshal.PtrToStringAnsi(sessions[i].pHostName); string userName = Marshal.PtrToStringAnsi(sessions[i].pUserName); // 其他字符串成员同理处理 } // 这里可以使用sessions数组做后续业务逻辑 } finally { // 释放会话信息内存 if (sessionInfoPtr != IntPtr.Zero) { Native32.WTSFreeMemoryEx(Native32.WTS_MEMORY_TYPE.WTSMemTypeSessionInfoLevel1, sessionInfoPtr, sessionCount); } // 关闭服务器句柄 Native32.WTSCloseServer(serverHandle); }
这些修正应该能解决你遇到的AccessViolationException问题,核心是确保C#的结构体和函数声明完全匹配原生API的定义,同时正确处理非托管内存的分配和释放。
内容的提问来源于stack exchange,提问作者PauMAVA
相关产品推荐
相关产品推荐

