2005年09月23日

WTSEnumerateSessionsとP/Invoke

さて今日は、過去にInsider .NETに投稿した奴をきっちりとした形で書き直します。いつかはやらなきゃと思っていただけであって手抜きじゃないですよ?

発端はVB.netでのCopyMemoryを利用するにはと言うスレッドで、GUI登録されたタスクの編集スレッドでもコードが流用されています。リンク先をご覧になって頂ければ分かりますが、要はいかにP/Invokeをコーディングするか、です。

メインターゲットはWin32APIのWTSEnumerateSessions関数。指定したターミナルサーバー上のセッションのリストを取得するという関数です。この関数の特徴は、関数が自動的に独自にメモリを確保して、そのメモリに結果の配列を並べていくという点です。普通に配列(ポインタ)を返してくるだけなら、OutAttributeをつけた配列(の参照)を渡せば済む話なのですが、関数が独自にメモリを確保する以上、プログラマは明示的に関数コールでメモリを解放してやらなければなりません。これでは前述のようなお手軽手段は使えません。ポインタはポインタとして受け取って、なんとか自前で構造体配列に変換してやる必要があります。

こういうときにお世話になるのがSystem.Runtime.InteropServices名前空間のMarshalクラスです。まああんまりお世話になりたくないクラスでもありますがね。

基本的な考え方はこうです。まずC言語で考えます。MSDNではこの部分のパラメータは

PWTS_SESSION_INFO *ppProcessInfo
となっていますから、C言語から呼ぶ場合は
PWTS_SESSION_INFO infos;
WTSEnumerateSessions(...., &infos, ...);
という呼び方となります。そして実際に返ってきたのに対して、
for (i = 0; i < count; i++) {
    printf("%d", (*infos).SessionId)
    infos++;
}
といった処理を行うでしょう。

いや、普通は素直にinfos[i].SessionIdと書く(infosは動かさない)か、あるいはinfos->SessionId(わざわざ構造体にしないで直接参照する)でしょう。ここでは.NETへの翻訳に楽なように書いてます。

翻って.NETでは、まずWTSEnumerateSessionsを呼ぶと、&infosに当たるポインタは配列先頭の構造体を指します。まず一つ目、Marshal.PtrToStructureでWTSSessionInfo構造体を取得します。さて次はどうするか。C言語ではinfosをインクリメントしています。C言語において、ある型のポインタをインクリメントすると、その型が占ているバイトデータ分ポインタをずらします。例えばint(32bit)なら4バイトずらしますし、charなら1バイトだけずらします。そして構造体の場合、その構造体の各メンバが占める合計だけずらします。WTS_SESSION_INFO構造体なら、DWORD、LPSTR、列挙型の3つのメンバですから(32bitPCなら)12バイトという事になります。つまり、初め指しているところから12バイトずらせば次のWTS_SESSION_INFO構造体になるわけです。あとはその繰り返しですね。

今回の完全なC#のコードです。

P/Invokeの構文は非常に柔軟に表現できます。私はある程度慣れましたが、その結果私なりの表現に固まってしまったため、もっと便利な使い方に気づけない可能性もあります。そう言う意味でPINVOKE.NETなんかは時々見ると意外な発見があったりしますね。

巧く使用すれば.NETでは扱えないことがさらりと扱えるP/Invoke。CLIからすれば美しくはありませんが、巧く付き合っていきたいものです。

ところで一回使ったDllImportはXMLドキュメントをつけて残しているのですが、なんか250kBほどになってるんですがどうしたら。

C#のコードです。

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Text;
namespace HongliangSoft.Samples {
    public class TerminalService {
        public static readonly IntPtr CurrentServerHandle = IntPtr.Zero;
        public static WTSSessionInfo[] GetSessionInfos(IntPtr server) {
            IntPtr buffer;  //返ってくる配列を受け取るポインタ。
            int count;      //数。どちらもoutパラメータなので初期化不要。
            //早速実行。失敗したら例外。
            if (!Win32Api.WTSEnumerateSessions(server, 0, 1, out buffer, out count)) {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
            //バッファが空ならセッション無しと言うこと。
            if (buffer == IntPtr.Zero)
                return new WTSSessionInfo[0];
            WTSSessionInfo[] infos = new WTSSessionInfo[count];
            //WTSSessionInfo構造体のサイズを取得。
            int size = Marshal.SizeOf(typeof(WTSSessionInfo));
            try {
                for (int i = 0; i < count; i++) {
                    //ポインタを、sizeずつずらしていく。
                    IntPtr current = new IntPtr(buffer.ToInt64() + (size * i));
                    //ポインタから構造体に変換して配列に格納。
                    infos[i] = (WTSSessionInfo)Marshal.PtrToStructure(
                                                   current, typeof(WTSSessionInfo));
                }
                return infos;
            }
            catch (Exception ex) {
                throw new InvalidOperationException(
                                "バッファの読み出し中にエラーが発生しました。", ex);
            }
            //確実に後始末
            finally {
                Win32Api.WTSFreeMemory(buffer);
            }
        }
        public static WTSProcessInfo[] GetProcessInfos(IntPtr server) {
            //やってることはGetSessionInfosと同じ。使う関数と構造体が違うだけ。
            IntPtr buffer;
            int count;
            if (!Win32Api.WTSEnumerateProcesses(server, 0, 1, out buffer, out count)) {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
            if (buffer == IntPtr.Zero)
                return new WTSProcessInfo[0];
            WTSProcessInfo[] infos = new WTSProcessInfo[count];
            int size = Marshal.SizeOf(typeof(WTSProcessInfo));
            try {
                for (int i = 0; i < count; i++) {
                    IntPtr current = new IntPtr(buffer.ToInt64() + (size * i));
                    infos[i] = (WTSProcessInfo)Marshal.PtrToStructure(current,
                                                             typeof(WTSProcessInfo));
                }
                return infos;
            }
            catch (Exception ex) {
                throw new InvalidOperationException(
                                "バッファの読み出し中にエラーが発生しました。", ex);
            }
            finally {
                Win32Api.WTSFreeMemory(buffer);
            }
        }
        [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
        public struct WTSSessionInfo {
            public int SessionId;
            public string WinStationName;
            public ConnectState State;
        }
        public enum ConnectState {
            Active       = 0x0,
            Connected    = 0x1,
            ConnectQuery = 0x2,
            Shadow       = 0x3,
            Disconnected = 0x4,
            Idle         = 0x5,
            Listen       = 0x6,
            Reset        = 0x7,
            Down         = 0x8,
            Init         = 0x9,
        }
        [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
        public struct WTSProcessInfo {
            public int SessionId;
            public int ProcessId;
            public string ProcessName;
            public IntPtr UserSid;
        }

        private sealed class Win32Api {
            [DllImport("wtsapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
            public static extern IntPtr WTSOpenServer(string name);
            [DllImport("wtsapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
            public static extern bool WTSEnumerateSessions(
                    IntPtr server, int reserved, int version, 
                    out IntPtr infos, out int count);
            [DllImport("wtsapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
            public static extern bool WTSEnumerateProcesses(
                    IntPtr server, int reserved, int version,
                    out IntPtr infos, out int count);
            [DllImport("wtsapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
            public static extern void WTSFreeMemory(IntPtr memory);
        }
    }
}

VB.NETのコードです。

Imports System
Imports System.ComponentModel
Imports System.Runtime.InteropServices
Imports System.Text
Namespace HongliangSoft.Samples
    Public Class TerminalService
        Public Shared ReadOnly CurrentServerHandle As IntPtr = IntPtr.Zero
        Public Shared Function GetSessionInfos(ByVal server As IntPtr) As WTSSessionInfo()
            Dim buffer As IntPtr  '返ってくる配列を受け取るポインタ。
            Dim count As Integer  '数。どちらも初期化不要。
            '早速実行。失敗したら例外。
            If Not(Win32Api.WTSEnumerateSessions(server, 0, 1, buffer, count)) Then
                Throw New Win32Exception(Marshal.GetLastWin32Error())
            End If
            'バッファが空ならセッション無しと言うこと。
            If buffer.Equals(IntPtr.Zero) Then
                Return New WTSSessionInfo(){}
            End If
            Dim infos(count - 1) As WTSSessionInfo
            'WTSSessionInfo構造体のサイズを取得。
            Dim size As Integer = Marshal.SizeOf(GetType(WTSSessionInfo))
            Try
                Dim i As Integer
                For i = 0 To count - 1
                    'ポインタを、sizeずつずらしていく。
                    Dim current As IntPtr = New IntPtr(buffer.ToInt64() + (size * i))
                    'ポインタから構造体に変換して配列に格納。
                    infos(i) = CType(Marshal.PtrToStructure(current, _
                                             GetType(WTSSessionInfo)), WTSSessionInfo)
                Next
                Return infos
            Catch ex As Exception
                Throw New InvalidOperationException( _
                                "バッファの読み出し中にエラーが発生しました。", ex)
            '確実に後始末
            Finally
                Win32Api.WTSFreeMemory(buffer)
            End Try
        End Function
        Public Shared Function GetProcessInfos(ByVal server As IntPtr) _
                                                                 As WTSProcessInfo()
            'やってることはGetSessionInfosと同じ。使う関数と構造体が違うだけ。
            Dim buffer As IntPtr
            Dim count As Integer
            If Not(Win32Api.WTSEnumerateProcesses(server, 0, 1, buffer, count)) Then
                Throw New Win32Exception(Marshal.GetLastWin32Error())
            End If
            If buffer.Equals(IntPtr.Zero) Then
                Return New WTSProcessInfo(){}
            End If
            Dim infos(count - 1) As WTSProcessInfo
            Dim size As Integer = Marshal.SizeOf(GetType(WTSProcessInfo))
            Try
                Dim i As Integer
                For i = 0 To count - 1
                    Dim current As IntPtr = New IntPtr(buffer.ToInt64() + (size * i))
                    infos(i) = CType(Marshal.PtrToStructure(current, _
                                             GetType(WTSProcessInfo)), WTSProcessInfo)
                Next
                Return infos
            Catch ex As Exception
                Throw New InvalidOperationException( _
                                "バッファの読み出し中にエラーが発生しました。", ex)
            Finally
                Win32Api.WTSFreeMemory(buffer)
            End Try
        End Function
        <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
        Public Structure WTSSessionInfo
            Public SessionId As Integer
            Public WinStationName As String
            Public State As ConnectState
        End Structure
        Public Enum ConnectState
            Active       = &H0
            Connected    = &H1
            ConnectQuery = &H2
            Shadow       = &H3
            Disconnected = &H4
            Idle         = &H5
            Listen       = &H6
            Reset        = &H7
            Down         = &H8
            Init         = &H9
        End Enum
        <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
        Public Structure WTSProcessInfo
            Public SessionId As Integer
            Public ProcessId As Integer
            Public ProcessName As String
            Public UserSid As IntPtr
        End Structure

        Private NotInheritable Class Win32Api
            Declare Auto Function WTSOpenServer Lib "wtsapi32.dll" ( _
                    ByVal name As String) As IntPtr
            Declare Auto Function WTSEnumerateSessions Lib "wtsapi32.dll" ( _
                    ByVal server As IntPtr, ByVal reserved As Integer, _
                    ByVal version As Integer, ByRef infos As IntPtr, _
                    ByRef count As Integer) As Boolean
            Declare Auto Function WTSEnumerateProcesses Lib "wtsapi32.dll" ( _
                    ByVal server As IntPtr, ByVal reserved As Integer, _
                    ByVal version As Integer, ByRef infos As IntPtr, _
                    ByRef count As Integer) As Boolean
            Declare Auto Sub WTSFreeMemory Lib "wtsapi32.dll" (ByVal memory As IntPtr)
        End Class
    End Class
End Namespace

posted by Hongliang at 23:37| Comment(0) | TrackBack(0) | .NET | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。

この記事へのトラックバック

ここ(hongliang.seesaa.net)で公開しているものについて、利用は自由に行って頂いて構いません。改変、再頒布もお好きになさって下さい。利用に対しこちらが何かを要求することはありません。

ただし、公開するものを使用、または参考したことによって何らかの損害等が生じた場合でも、私はいかなる責任も負いません。

あ、こんなのに使ったってコメントを頂ければ嬉しいです。

×

この広告は1年以上新しい記事の投稿がないブログに表示されております。