Search code examples
c#c++processpinvokemarshalling

How to Convert ProcessInfo to WTS_PROCESS_INFO


I am trying to invoke WTSEnumerateProcesses() but am having some trouble converting type ProcessInfo to WTS_PROCESS_INFO.

This is the code I have so far:

ProcessInfo

    class ProcessInfo
    {
        public int basePriority;
        public int handleCount;
        public int mainModuleId;
        public long pageFileBytes;
        public long pageFileBytesPeak;
        public long poolNonpagedBytes;
        public long poolPagedBytes;
        public long privateBytes;
        public int processId;
        public string processName;
        public int sessionId;
        public ArrayList threadInfoList;
        public long virtualBytes;
        public long virtualBytesPeak;
        public long workingSet;
        public long workingSetPeak;

        public ProcessInfo()
        {

        }
    }

WTS_PROCESS_INFO

    public struct WTS_PROCESS_INFO
    {
        public int SessionID;
        public int ProcessID;
        // This is spointer to a string...
        public IntPtr ProcessName;
        public IntPtr userSid;
    }

WTSEnumerateProcesses()

    [DllImport("wtsapi32.dll", SetLastError = true)]
    static extern bool WTSEnumerateProcesses(
        IntPtr serverHandle, // Handle to a terminal server. 
        Int32 reserved,     // must be 0
        Int32 version,      // must be 1
        ref IntPtr ppProcessInfo, // pointer to array of WTS_PROCESS_INFO
        ref Int32 pCount     // pointer to number of processes
    );

    public static WTS_PROCESS_INFO[] WTSEnumerateProcesses()
    {
        IntPtr pProcessInfo = IntPtr.Zero;
        int processCount = 0;
        var hServer = OpenServer("XA7-06");

        if (!WTSEnumerateProcesses(hServer, 0, 1, ref pProcessInfo, ref processCount))
            return null;

        IntPtr pMemory = pProcessInfo;
        WTS_PROCESS_INFO[] processInfos = new WTS_PROCESS_INFO[processCount];
        for (int i = 0; i < processCount; i++)
        {
            processInfos[i] = (ProcessInfo)Marshal.PtrToStructure(pProcessInfo, typeof(WTS_PROCESS_INFO));
            pProcessInfo = (IntPtr)((int)pProcessInfo + Marshal.SizeOf(processInfos[i]));
        }

        WTSFreeMemory(pMemory);
        return processInfos;
    }

My issue is near the end of the WTSEnumerateProcesses() method on this line:

processInfos[i] = (ProcessInfo)Marshal.PtrToStructure(pProcessInfo, typeof(WTS_PROCESS_INFO));

Where I think I may have missed out a step in defining the relationship between WTS_PROCESS_INFO and ProcessInfo, but I am not sure how to do this. Is anyone able to point me in the right direction?

This is the error I am seeing:

// Cannot implicitly convert type 'ProcessInfo' to 'WTS_PROCESS_INFO'

Solution

  • Your ProcessInfo type is unrelated to WTS_PROCESS_INFO. So this line:

    processInfos[i] = (ProcessInfo)Marshal.PtrToStructure(pProcessInfo, 
        typeof(WTS_PROCESS_INFO));
    

    actually contains two errors. The compiler picks up one. Specifically that processInfos[i] is of type WTS_PROCESS_INFO and is not assignment compatible with ProcessInfo. The other mistake is a little more subtle. Your cast of the value returned by Marshal.PtrToStructure() to the type ProcessInfo is wrong. The value returned by Marshal.PtrToStructure() is a boxed WTS_PROCESS_INFO. So that line of code should be:

    processInfos[i] = (WTS_PROCESS_INFO)Marshal.PtrToStructure(pProcessInfo, 
        typeof(WTS_PROCESS_INFO));
    

    Your pointer arithmetic can fail in a 64 bit process. If you are using a recent version of .net you can perform arithmetic directly on IntPtr:

    pProcessInfo += Marshal.SizeOf(processInfos[i]);
    

    Or if you target older .net versions you should perform the arithmetic in a 64 bit context by casting to ulong rather than int.

    As for your ProcessInfo type, I think that's just a mis-think on your part. There appears to be no use for that type here.

    If I were you I would force the use of the Unicode version of WTSEnumerateProcessesW, for instance like this:

    [DllImport("wtsapi32.dll", EntryPoint = "WTSEnumerateProcessesW", SetLastError = true)]
    static extern bool WTSEnumerateProcesses(
        ....
    );
    

    To read the process name as a string, pass the pointer returned in the WTS_PROCESS_INFO struct to Marshal.PtrToStringUni.