C#: Speicher in einem anderen Prozess befüllen lassen sowie checken, ob ein Button gedrückt ist (in einem fremden Prozess)

C#: Speicher in einem anderen Prozess befüllen lassen sowie checken, ob ein Button gedrückt ist (in einem fremden Prozess)

Alle naselang macht man als Programmierer eine besondere Erfahrung. Man hat über viele Stunden an einem Problem rumkniffelt. Sich zwischendurch schon gefragt, ob man an einer unlösbaren Aufgabe verzweifelt. Und schließlich – ganz wie Goethes Mathematiker – hat man das Problem bis zur Lösung gebracht.

Er ist ein Mathematiker und also hartnäckig.
~~Goethe

Das Gefühl, wenn dann alles funktioniert, ist ähnlich, wie es Cloud gegangen sein mag, nachdem er Sephirot besiegt hat, und auf Level 73 aufgestiegen ist. Wenn er denn Gefühle hätte. Oder wenn ein Yogi gerade eine Erscheinung hatte – oder seine Gedanken für eine Weile komplett ausgeschaltet hat. Oder so ähnlich.

Jedenfalls habe ich wieder eine Menge über p/invoke gelernt, wo man immer wieder gut auf der Pinvoke-Seite aufgehoben ist, und festgestellt, wie man per SendMessage Speicher in einem fremden Prozess befüllen lassen kann – das macht die SendMessage nämlich normalerweise automatisch (daher muß man nicht diesen Aufstand machen), allerdings wohl nur, wenn die Message < WM_USER ist. In diesem Fall sieht man dann also, was sonst alles gemacht werden muß, um Daten aus einem fremden Prozess abzuholen.

Ich meine auch, daß das alles zu den guten, alten C/C++-Zeiten einfacher war. Da konnte man einfach Speicher im Kernel-Bereich allokieren, der dann für alle Prozesse gleich eingeblendet wurde und zugreifbar war. Ich muß gestehen, genau das hätte ich bei einer Allokation über AllocHGlobal auch erwartet – wofür sonst würde das Global stehen? Aber zuerst..

Ursprüngliche Motivation war, festzustellen, ob ein Button in einer Fremden Applikation gedrückt ist. Insbesondere der Toolbar-Fall war hier recht interessant (= aufwendig). Buttons in einem Toolbar besitzen nämlich erstmal keinen Windows Handle und müssen etwas aufwendiger adressiert werden. Insbesondere läuft dies über die Command Id, die man zunächst gar nicht hat, und ein paar weitere Stolpersteine ergeben sich auch noch, auf dem Weg.

So muß z. B. zunächst Speicher im externen Prozess erzeugt werden (Pointer nur im anderen Prozess gültig!), dann im eigenen Prozess unmanaged Speicher erzeugt werden, die Daten zwischen den Prozessen kopiert werden, und anschließend der prozessinterne unmanaged Speicher wieder in einen managed Speicherbereich übertragen werden.

Am Ende hat man jedenfalls in der res-Variablen den Zustand des gewünschten Buttons (der sollte im AutomationElement ae stehen, z. B. indem man sinnige Bildschirmkoordinaten in den ersten Aufruf einfügt).

Und weil schöner Code selbsterklärend ist, kann ich diesen auch einfach so stehen lassen.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
using System.Runtime.InteropServices;
using System.Windows.Automation;
 
        void SomeFunc()
        {
            AutomationElement ae = AutomationElement.FromPoint(new System.Windows.Point(100,100));
            int res;
            #region direct method
            int hwnd = ae.Current.NativeWindowHandle;
            if (hwnd != 0)
            {
                const UInt32 BM_GETSTATE = 0x00F2;
                res = SendMessage(hwnd, BM_GETSTATE, 0, 0);
            }
            #endregion
            else
            #region method via toolbar
            {
                AutomationElement parent = TreeWalker.RawViewWalker.GetParent(ae);
                while ((parent != null) && (parent.Current.ControlType != ControlType.ToolBar))
                    parent = TreeWalker.RawViewWalker.GetParent(ae);
                if (parent != null)
                {
                    int toolBarHandle = parent.Current.NativeWindowHandle;
                    #region defines
                    const int WM_USER = 0x400;
                    const int TB_GETSTATE = (WM_USER + 18);
                    const int TB_GETBUTTON = (WM_USER + 23);
                    const int TB_BUTTONCOUNT = (WM_USER + 24);
                    #endregion
                    #region get correct child number
                    int numButtons = SendMessage(toolBarHandle, TB_BUTTONCOUNT, 0, 0);
                    AutomationElement sibling = ae;
                    int cnt = -1;
                    while (sibling != null)
                    {
                        sibling = TreeWalker.RawViewWalker.GetPreviousSibling(sibling);
                        ++cnt;
                    }
                    if (cnt >= numButtons)
                        cnt = 0; // nonsense value, but pass a valid one
                    #endregion
                    #region get command id
                    TBBUTTON butInfo = new TBBUTTON();
                    butInfo.idCommand = 1234;
                    uint pid;
                    // get process to hwnd - we need process to allocate memory in the right context
                    GetWindowThreadProcessId((IntPtr)toolBarHandle, out pid);
                    IntPtr process = OpenProcess(ProcessAccessFlags.VMOperation | ProcessAccessFlags.VMRead |
                    ProcessAccessFlags.VMWrite | ProcessAccessFlags.QueryInformation, false, pid);
                    // allocate memory - pointer is valid in context of other process, not our own!
                    IntPtr p = VirtualAllocEx(process, IntPtr.Zero, (uint)Marshal.SizeOf(typeof(TBBUTTON)), AllocationType.Commit
                    , MemoryProtection.ReadWrite);
                    int _res = SendMessage(toolBarHandle, TB_GETBUTTON, cnt, p.ToInt32());
                    #region getresult
                    int read;
                    // marshal memory back: first create memory to receive result from other process
                    IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(TBBUTTON)));
                    Marshal.StructureToPtr(butInfo, ptr, true);
                    // get memory from other process into our own space (newly allocated memory)
                    bool __res = ReadProcessMemory(process, p, ptr, Marshal.SizeOf(typeof(TBBUTTON)), out read);
                    System.Diagnostics.Debug.Assert(read == Marshal.SizeOf(typeof(TBBUTTON)));
                    // marshal back into our struct
                    butInfo = (TBBUTTON)Marshal.PtrToStructure(ptr, typeof(TBBUTTON));
                    #endregion
                    int commandId = butInfo.idCommand;
                    Marshal.FreeHGlobal(ptr);
                    VirtualFreeEx(process, p, 0, FreeType.Release);
                    #endregion
                    //!define BST_UNCHECKED 0
                    //!define BST_CHECKED 1
                    //!define BST_INDETERMINATE 2
                    //!define BST_PUSHED 4
                    //!define BST_FOCUS 8
                    #region get state
                    res = SendMessage(toolBarHandle, TB_GETSTATE, commandId, 0);
                    #endregion
                }
            }
            #endregion
        }
 
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        static extern int SendMessage(int hWnd, UInt32 Msg, int wParam, int lParam);
        [DllImport("user32.dll", SetLastError = true)]
        static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress,
        uint dwSize, AllocationType flAllocationType, MemoryProtection flProtect);
        [DllImport("kernel32.dll")]
        static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwProcessId);
        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress,
        int dwSize, FreeType dwFreeType);
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool ReadProcessMemory(
        IntPtr hProcess,
        IntPtr lpBaseAddress,
        IntPtr lpBuffer,
        int dwSize,
        out int lpNumberOfBytesRead
        );
 
        [Flags]
        public enum FreeType
        {
            Decommit = 0x4000,
            Release = 0x8000,
        }
 
        [Flags]
        enum ProcessAccessFlags : uint
        {
            All = 0x001F0FFF,
            Terminate = 0x00000001,
            CreateThread = 0x00000002,
            VMOperation = 0x00000008,
            VMRead = 0x00000010,
            VMWrite = 0x00000020,
            DupHandle = 0x00000040,
            SetInformation = 0x00000200,
            QueryInformation = 0x00000400,
            Synchronize = 0x00100000
        }
 
        [Flags]
        public enum AllocationType
        {
            Commit = 0x1000,
            Reserve = 0x2000,
            Decommit = 0x4000,
            Release = 0x8000,
            Reset = 0x80000,
            Physical = 0x400000,
            TopDown = 0x100000,
            WriteWatch = 0x200000,
            LargePages = 0x20000000
        }
 
        [Flags]
        public enum MemoryProtection
        {
            Execute = 0x10,
            ExecuteRead = 0x20,
            ExecuteReadWrite = 0x40,
            ExecuteWriteCopy = 0x80,
            NoAccess = 0x01,
            ReadOnly = 0x02,
            ReadWrite = 0x04,
            WriteCopy = 0x08,
            GuardModifierflag = 0x100,
            NoCacheModifierflag = 0x200,
            WriteCombineModifierflag = 0x400
        }
 
        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct TBBUTTON
        {
            public int iBitmap;
            public int idCommand;
            public byte fsState;
            public byte fsStyle;
            public byte bReserved0;
            public byte bReserved1;
            //#region 64 bit - just in case (don't need the later elements, so make structure too big
            //public byte bReserved2;
            //public byte bReserved3;
            //public byte bReserved4;
            //public byte bReserved5;
            //#endregion
            public int dwData;
            public IntPtr _string;
        }

Links
Sehr geholfen haben mir die Antworten auf diese Frage bei Stackoverflow.
Immer wieder sehr hilfreich bei P/Invoke Fragen (und der lästigen Suche nach der richtigen Signatur) ist die Pinvoke-Seite.
Und auch hier auf der Seite gibt es einen ergänzenen Artikel zum Thema Aufrufen externer .dlls.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.