Generic Wrapper for SendMessage calls in C# through reflection

Generic Wrapper for SendMessage calls in C# through reflection

So.. tada.. my first English post on my blog. I always thought there would be a big need for German explanations in the programming world (and world in general) but.. some things should not be hidden from a wider public – which speaks (or reads) English.

I recently wrote a more or less generic wrapper for SendMessage calls to any windows and controls the heart desires (especially the ones in processes that are not your own) – as long as you have got a handle.

There is also a lot of fun stuff in here, like working with pointers (IntPtr), allocating memory in foreign processes, linking your own structures via pointers and filling them with values and a lot more to keep yourself busy for the day.

The Setting

Even in version 4, the .net framework and also the C# language is still lacking. Truth to be told, there is a lot a lot there is that is really nice and helps productivity. But some things are just still awful. No multiple inheritance for example (which is not lacking because it is not „clean“ but rather because of technical problems).

Another big area of lack is if you are programming close to the hardware, or programming close to the OS. I am doing a lot of the latter recently, and there are just so many possibilities in the Win32 SDK that are not (directly) available in C#/.net that it is a real shame.

A lot of information can only be queried via the Win32 interface, some actions can only be performed that way, like when you want to enable or disable a control (in another program). (This also makes me think in the future to not only disable a button if I really want to remove a functionality – buttons can be re-enabled.)

This mostly applies of course if you want to meddle around with foreign programs (automation in my case) where generally you don’t have a very good range of possibilities from within .net.

Sendmessage and .net

So after sending a lot of messages and marshalling a lot of structs, I thought that there should be a better way. And it seems that there is. So I started working on a generic function to marshal arbitrary structs (kind of arbitrary, see below).

One simple version is the case where the struct is flat, meaning that it does not contain any pointers. Pointers are, some may remember, also used to represent arrays and strings, which used to be char-arrays. Still, quite a few (flat) structs remain, and the following is a straightforward implementation of a wrapped SendMessage with such a flat struct or class T.

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
/// <summary>
/// works for simple structs T (not containing references/pointers/strings)
/// </summary>
static IntPtr SendMessageGeneric<t>(int handle, uint messageId, int wParam, ref T lParam, SendMessageDirection smd)
{
    uint pid;
    // get process to hwnd - we need process to allocate memory in the right context
    ExternUser32.GetWindowThreadProcessId((IntPtr)handle, out pid);
    IntPtr process = ExternKernel.OpenProcess(ExternKernel.ProcessAccessFlags.VMOperation | ExternKernel.ProcessAccessFlags.VMRead |
        ExternKernel.ProcessAccessFlags.VMWrite | ExternKernel.ProcessAccessFlags.QueryInformation, false, pid);
     // allocate memory - pointer is valid in context of other process, not our own!
    IntPtr lParamPtrForeign = ExternKernel.VirtualAllocEx(process, IntPtr.Zero, (uint)Marshal.SizeOf(lParam),
        ExternKernel.AllocationType.Commit, ExternKernel.MemoryProtection.ReadWrite);
     if ((smd & SendMessageDirection._in) > 0)
    {
        IntPtr marshalPtrLocal = Marshal.AllocHGlobal(Marshal.SizeOf(lParam));
        Marshal.StructureToPtr(lParam, marshalPtrLocal, true);
        ExternKernel.WriteProcessMemory(process, lParamPtrForeign, marshalPtrLocal, Marshal.SizeOf(lParam), IntPtr.Zero);
        Marshal.FreeHGlobal(marshalPtrLocal);
    }
     IntPtr res = ExternMessage.SendMessage(handle, messageId, wParam, lParamPtrForeign);
     if ((smd & SendMessageDirection._out) > 0)
    {
        int read;
        // marshal memory back: first create memory to receive result from other process
        IntPtr readbackPtrLocal = Marshal.AllocHGlobal(Marshal.SizeOf(lParam));
        // get memory from other process into our own space (newly allocated memory)
        bool __res = ExternKernel.ReadProcessMemory(process, lParamPtrForeign, readbackPtrLocal, Marshal.SizeOf(typeof(T)), out read);
        System.Diagnostics.Debug.Assert(read == Marshal.SizeOf(typeof(T)));
        // marshal back into our struct
        lParam = (T)Marshal.PtrToStructure(readbackPtrLocal, typeof(T));
        Marshal.FreeHGlobal(readbackPtrLocal);
    }
    // clean up foreign memory in foreign process
    ExternKernel.VirtualFreeEx(process, lParamPtrForeign, 0, ExternKernel.FreeType.Release);
     return res;
}

This version is actually kind of straightforward once you know how it works.

Explanation

The big point is that if you allocate just any chunk of memory, it will be in your local process, which is not much use to the other process, since it is going to assume the memory local to itself.

Memory Model (Virtual)

See the beautiful image I created that is nearly self-explanatory. On a typical 32 bit Windows system, you have 4GB of addressable virtual memory (independent of real memory, whether it is 128MB or 128GB) per process. From those 4GB, 2GB are reserved for the Kernel. They are reserved means: no, there are not always 2GB real memory used for the Kernel. Also, the Kernel/System is always in the upper 2GB – for each and every process. (Again, only on a standard Windows 32 bit system, especially without PAE which gives the OS a lot more memory without giving any process more memory (so the image and model stay the same) and without the /3GB switch which shrinks the kernel to 1GB.

Processes on the other hand only have their own address space, 4GB, of which only the lower 2GB are usable (though you can use some of the system memory, e. g. for shared memory files and the like, which would have made this article even a bit easier since that memory is shared between all processes). The problem is, that each process has its own 4GB of addressable virtual (virtual unequal real) memory and all of it (apart from the system part) point somewhere completely different. So: Address 10000 for process1 can have a string stored at its position, whereas process2 can have the memory at address 10000 completely uninitialized and thus accessing address 10000 for process2 results in an access violation, whereas accessing 10000 in process1 would simply return or manipulate a string.
So for something like what is being done here, you would have to have your data locally at e. g. address 50000 (whatever you get as a free location from the memory manager), but on the other process, you would have to allocate new memory at say address 80000 (again, where you get it from the system) and then use a function to copy the data there and back again. Note that 80000 will be local to the other process. If you look at 80000 in your own process, you will only get garbage – at least not, what you expected.

The solution here is to allocate memory in the process that is going to receive the message (you get the process from the handle), then to move your data into the data of that process (you can see this beautifully if you attach a debugger to the foreign process and open the memory window (from the debug menu) to the position you get in lParamPtrForeign).

Now you can send the message, using lParamPtrForeign as your lParam, since it now is local to the process you send the message to. And the process expects valid memory relative to itself. (Actually, if you modify the code and you run into issues with the foreign process crashing, typically, the pointer you give it is wrong, and – if it is a valid pointer at all – it will most often be a pointer that is valid only in your calling process but not in the other one – remember, each process has its completely own memory range.)

Once the SendMessage is completed (SendMessage is synchronous, so we can be sure that all the necessary data has been exchanged by that point), we only need to marshal the data back into our local struct, so that it can be accessed like we directly passed our class there (which we did not).

The exact way was class->some local memory that can be accessed via marshalling->memory in remote process->some local memory that can be accessed via mashalling->class.

Everything becomes a bit more tricky once we also want to allow for pointers in that class. Actually, till you have a working solution, you can spend some hours going through articles on Stackoverflow or PInvoke.net and then pointing with the right pointer to the right item at the right time. No, it’s not rocket science, but it certainly can take some time, especially if you don’t do this every day.

So after doing it manually once and thinking that this is a process that I might have to do quite a few times, I decided to do it more generic – in fact, as generic as (to me) possible.

So.. in order to allow users to create structs that are directly usable, I decided to allow for struct definitions that contain strings that can be used as-is. So, in order to use the object I would have to create a new type, replacing the strings with IntPtrs pointing to some memory allocated and pre-filled acting as the char-array.

At first I thought about using the suggestion by Mrion Abramson in his post about creating new types at runtime, since that is basically what I intended. After some grave thoughts about runtime performance (I would have to create the new type in a new assembly, …) I came up with the easy way, the C-style way, the pointer way. And.. before I didn’t even know that it was possible (kind of) this easy.

The Pointer Way

All things in memory must have a location at which they are stored, since that is also the way in which they are accessed.
Many things influence where the data is stored and typically you have no idea where they are stored. Things are a bit different with classes an structs, since you can make assumptions on where the data is stored, at least relative to each other. Data for classes and structs is always stored consecutively. So, knowing that a UInt is 4 byte and it is at position 0, we can guess that our second field will be at position 4 and so on. There are obviously some things that modify this like the packing order which is mostly interesting for alignment (many processors work a lot faster if data is only stored at certain addresses like multiples of 4 or 8) or for values of variable width like an IntPtr, which can be either 4 or 8 bytes.

Actually, here I have not taken alignment into account, and simply assume that all your data is packed tight with no filling-bytes in between. Before you demand a new version for classes with a different alignment, you can do the alignment yourself by including dummy data, like if you have a 1 byte value, but konw that the alignment is not to pack on every byte but only starting on every mutliple of 4, you can add 3 dummy bytes to make the alignment work again.
A pointer now is going to point to some variable or field. Actually a variable name is like a pointer too, but it always points to the same object. An IntPtr can point to any address and depending on how you treat that address, you can give it a special meaning (that better has to be correct, since this is not typesafe at all).

So.. I simply copy each value from the unknown struct, received with type information via reflection into an untyped memory segment, incrementing a pointer by the size of the data copied. For a string (this is tricky again), I allocate another memory chunk, initialize it, and see to it that all pointers are pointing the right way.

In the end, I get the data back the same way, copying it with a pointer and marshalling from the untyped memory chunk back into the typed stuct, where it can be easily and type-safely accessed.

Around this, there are some allocations of temporary memory, and especially the allocation of memory in the target process before the SendMessage, and the deallocation of that memory after grabbing the information back into the source/target-struct.

Important Considerations

Right now, the only pointer-type that is supported is the string, though other types are probably even easier to implement by simply generating untyped chunks of memory and marshal them yet again.

Another restriction is that for string-output-parameters, the size parameter, that typically is part of the struct, is not set automatically. I might come up with an automatic way, like using naming conventions, after all, some recent software developments have shown the great usability advantage of convention over configuration (so I might have the string parameter have an arbitrary name and then the size parameter have the name „(arbitrary string name)size“ or something similarly insightful). Or just use the next int after a string (though I don’t have enough data to justify this). But for now, this parameter has to be set manually.

Also, don’t forget that some structs need preinitialization in order to return meaningful data: often they contain flags of what data you want to get and what parts of your struct are valid and sometimes even about the size of the struct.

Also, ANSI is hardcoded – so you might either use the appropriate ANSI-messages (which most often end in an A instead of a U) or you would have to rewrite some code here.

Very important: the objects you pass in need to be classes and cannot be structs. This is due to the factor that the method I use to write the values back to the original object is FieldInfo.SetValue, which expects an object. In order to use structs there, they are boxed. And then.. the update is done on the boxed object, which has no effect on the original struct. so.. USE CLASSES!

And, one more thing: I applied all my little knowledge to make this code work in a 64 bit environment. I have little experience in that area so only can go by technical articles and best practices. A major point seems to be the signature of the SendMessage function which should be fine, as well as the handling of pointer, for which I have made distinctions based on IntPtr.Size (which is also a good indicator for what kind of OS you are running, as far as 32 and 64 bit is concerned).

So here is the solution I came up with:

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
174
175
176
177
178
179
180
181
182
183
/// <summary>
/// works for complex structs, containing an arbitrary number of strings
/// (can also contain Int32, UInt32, Int64, UInt64, Single, Double)
/// only fields are taken into account
/// </summary>
static IntPtr SendMessageComplexGeneric<t>(int handle, uint messageId, int wParam, ref T lParam, SendMessageDirection smd)
{
    const int stringMaxAddedSize = 128;
    int maxStringSize = 0;
    uint pid;
    // get process to hwnd - we need process to allocate memory in the right context
    ExternUser32.GetWindowThreadProcessId((IntPtr)handle, out pid);
    IntPtr process = ExternKernel.OpenProcess(ExternKernel.ProcessAccessFlags.VMOperation | ExternKernel.ProcessAccessFlags.VMRead |
        ExternKernel.ProcessAccessFlags.VMWrite | ExternKernel.ProcessAccessFlags.QueryInformation, false, pid);
     #region recreate type for marshalling with strings replaced by IntPtrs
    IntPtr marshalPtrLocal = Marshal.AllocHGlobal(Marshal.SizeOf(lParam));
    Type t = typeof(T);
    System.Reflection.BindingFlags flags = System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic
        | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.DeclaredOnly;
    var fields = t.GetFields(flags);
    IntPtr curPtr = marshalPtrLocal;
    foreach (var field in fields)
    {
        switch (field.FieldType.Name)
        {
            case "Int32":
            case "UInt32":
                {
                    Int32 val = Convert.ToInt32(field.GetValue(lParam));
                    Marshal.Copy(new Int32[] { val }, 0, curPtr, 1);
                    curPtr = (IntPtr)(curPtr.ToInt64() + Marshal.SizeOf(val));
                }
                break;
            case "Int64":
            case "UInt64":
                {
                    Int64 val = Convert.ToInt64(field.GetValue(lParam));
                    Marshal.Copy(new Int64[] { val }, 0, curPtr, 1);
                    curPtr = (IntPtr)(curPtr.ToInt64() + Marshal.SizeOf(val));
                }
                break;
            case "Single":
                {
                    Single val = Convert.ToSingle(field.GetValue(lParam));
                    Marshal.Copy(new Single[] { val }, 0, curPtr, 1);
                    curPtr = (IntPtr)(curPtr.ToInt64() + Marshal.SizeOf(val));
                }
                break;
            case "Double":
                {
                    Double val = Convert.ToDouble(field.GetValue(lParam));
                    Marshal.Copy(new Double[] { val }, 0, curPtr, 1);
                    curPtr = (IntPtr)(curPtr.ToInt64() + Marshal.SizeOf(val));
                }
                break;
            case "String":
                string str = (string)field.GetValue(lParam);
                if (str == null) // not initialized
                    str = new string('x', 32);
                int size = str.Length + stringMaxAddedSize;
                if (size > maxStringSize)
                    maxStringSize = size;
                IntPtr pszTextPtrForeign = ExternKernel.VirtualAllocEx(process, IntPtr.Zero, (uint)size, ExternKernel.AllocationType.Commit, ExternKernel.MemoryProtection.ReadWrite);
                if ((smd & SendMessageDirection._in) > 0)
                {
                    IntPtr TextPtrLocal = Marshal.StringToHGlobalAnsi(str);
                    ExternKernel.WriteProcessMemory(process, pszTextPtrForeign, TextPtrLocal, size, IntPtr.Zero);
                    Marshal.FreeHGlobal(TextPtrLocal);
                }
                // pszTextPtrForeign will be cleaned at the end
                switch (IntPtr.Size)
                {
                    case 4: // 32bit pointer
                        Marshal.Copy(new Int32[] { (Int32)pszTextPtrForeign }, 0, curPtr, 1);
                        break;
                    case 8: // 64bit pointer
                        Marshal.Copy(new Int64[] { (Int64)pszTextPtrForeign }, 0, curPtr, 1);
                        break;
                }
                curPtr = (IntPtr)(curPtr.ToInt64() + IntPtr.Size);
                break;
        }
    }
    #endregion
     // allocate memory - pointer is valid in context of other process, not our own!
    IntPtr lParamPtrForeign = ExternKernel.VirtualAllocEx(process, IntPtr.Zero, (uint)Marshal.SizeOf(lParam),
        ExternKernel.AllocationType.Commit, ExternKernel.MemoryProtection.ReadWrite);
    ExternKernel.WriteProcessMemory(process, lParamPtrForeign, marshalPtrLocal, Marshal.SizeOf(lParam), IntPtr.Zero);
    Marshal.FreeHGlobal(marshalPtrLocal);
     IntPtr res = ExternMessage.SendMessage(handle, messageId, wParam, lParamPtrForeign);
     #region get back data via marshalling and fill strings in struct
     marshalPtrLocal = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(T)));
    int bytesRead;
    ExternKernel.ReadProcessMemory(process, lParamPtrForeign, marshalPtrLocal, (int)Marshal.SizeOf(typeof(T)), out bytesRead);
    System.Diagnostics.Debug.Assert(bytesRead == Marshal.SizeOf(typeof(T)));
     curPtr = marshalPtrLocal;
    foreach (var field in fields)
    {
        switch (field.FieldType.Name)
        {
            case "UInt32":
            case "Int32":
                {
                    Int32[] dest = new Int32[1];
                    Marshal.Copy(curPtr, dest, 0, 1);
                    if (field.FieldType.Name.Equals("Int32"))
                        field.SetValue(lParam, dest[0]);
                    else
                        field.SetValue(lParam, Convert.ToUInt32(dest[0]));
                    curPtr = (IntPtr)(curPtr.ToInt64() + Marshal.SizeOf(dest[0]));
                }
                break;
            case "UInt64":
            case "Int64":
                {
                    Int64[] dest = new Int64[1];
                    Marshal.Copy(curPtr, dest, 0, 1);
                    if (field.FieldType.Name.Equals("Int64"))
                        field.SetValue(lParam, dest[0]);
                    else
                        field.SetValue(lParam, Convert.ToUInt64(dest[0]));
                    curPtr = (IntPtr)(curPtr.ToInt64() + Marshal.SizeOf(dest[0]));
                }
                break;
            case "Single":
                {
                    Single[] dest = new Single[1];
                    Marshal.Copy(curPtr, dest, 0, 1);
                    field.SetValue(lParam, dest[0]);
                    curPtr = (IntPtr)(curPtr.ToInt64() + Marshal.SizeOf(dest[0]));
                }
                break;
            case "Double":
                {
                    Double[] dest = new Double[1];
                    Marshal.Copy(curPtr, dest, 0, 1);
                    field.SetValue(lParam, dest[0]);
                    curPtr = (IntPtr)(curPtr.ToInt64() + Marshal.SizeOf(dest[0]));
                }
                break;
            case "String":
                if ((smd & SendMessageDirection._out) > 0)
                {
                    IntPtr pszTextPtrForeign = IntPtr.Zero;
                    //IntPtr[] curPtrTempArr = new IntPtr[] { curPtr };
                    switch (IntPtr.Size)
                    {
                        case 4: // 32bit pointer
                            {
                                Int32[] temp = new Int32[1];
                                Marshal.Copy(curPtr, temp, 0, 1);
                                pszTextPtrForeign = (IntPtr)temp[0];
                            }
                            break;
                        case 8: // 64bit pointer
                            {
                                Int64[] temp = new Int64[1];
                                Marshal.Copy(curPtr, temp, 0, 1);
                                pszTextPtrForeign = (IntPtr)temp[0];
                            }
                            break;
                    }
                     IntPtr TextPtrLocal = Marshal.AllocHGlobal(maxStringSize);
                    //int bytesRead;
                    ExternKernel.ReadProcessMemory(process, pszTextPtrForeign, TextPtrLocal, maxStringSize, out bytesRead);
                     //char[] dest = new char[maxStringSize];
                    string str = Marshal.PtrToStringAnsi(TextPtrLocal);
                    //Marshal.Copy(TextPtrLocal, dest, 0, maxStringSize);
                    //field.SetValue(lParam, new string(dest));
                    field.SetValue(lParam, str);
                     Marshal.FreeHGlobal(TextPtrLocal);
                    ExternKernel.VirtualFreeEx(process, pszTextPtrForeign, 0, ExternKernel.FreeType.Release);
                }
                 curPtr = (IntPtr)(curPtr.ToInt64() + IntPtr.Size);
                break;
        }
    }
    Marshal.FreeHGlobal(marshalPtrLocal);
    ExternKernel.VirtualFreeEx(process, lParamPtrForeign, 0, ExternKernel.FreeType.Release);
    #endregion
 
     return res;
}

Here come the definitions of external functions to call – most of them are from PInvoke.

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
class ExternUser32
{
    #region Class Functions
 
    [DllImport("user32.dll", SetLastError = true)]
    public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
 
    #endregion
}
 
class ExternKernel
{
    [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
    public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress,
       uint dwSize, AllocationType flAllocationType, MemoryProtection flProtect);
    [DllImport("kernel32.dll")]
    public static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwProcessId);
    [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
    public static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress,
       int dwSize, FreeType dwFreeType);
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern bool ReadProcessMemory(
     IntPtr hProcess,
     IntPtr lpBaseAddress,
        IntPtr lpBuffer,
     int dwSize,
     out int lpNumberOfBytesRead
    );
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, int nSize, IntPtr lpNumberOfBytesWritten);
 
    [Flags]
    public enum FreeType
    {
        Decommit = 0x4000,
        Release = 0x8000,
    }
 
    [Flags]
    public 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
    }
}
 
class ExternMessage
{
    #region class functions
 
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr SendMessage(int hWnd, UInt32 Msg, int wParam, IntPtr lParam);
 
    #endregion
 
    #region messages
    public const UInt32 TCM_FIRST = 0x1300;
    public const UInt32 TCM_GETITEMCOUNT = (TCM_FIRST + 4);
    public const UInt32 TCM_GETCURSEL = (TCM_FIRST + 11);
    public const UInt32 TCM_SETCURSEL = (TCM_FIRST + 12);
    public const UInt32 TCM_GETITEMRECT = (TCM_FIRST + 10);
    public const UInt32 TCM_GETITEMA = (TCM_FIRST + 5);
    #endregion
 
    #region constants
    public const UInt32 PRF_CLIENT = 4, PRF_CHILDREN = 0x10, PRF_NON_CLIENT = 2,
        COMBINED_PRINTFLAGS = PRF_CLIENT | PRF_CHILDREN | PRF_NON_CLIENT;
    public const int TCIF_STATE = 0x10;
    public const int TCIF_TEXT = 0x1;
    #endregion
 
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct RECT
    {
        public int left;
        public int top;
        public int right;
        public int bottom;
    };
 
    #region classes
 
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public class TCITEM // this needs to be a class!
    {
        public uint mask;
        public int state;
        public int statemask;
        public string text;
        public int size;
        public int image;
        public int param;
    }
 
    #endregion
}

There is also some example code on how to use it – and also showing which fields still need initialization, like the string length and also some flags influencing the behaviour of the message. The examples are from working with Tab Controls.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public string GetTabName(int handle, int index)
{
    ExternMessage.TCITEM tcitem = new ExternMessage.TCITEM();
    tcitem.mask = ExternMessage.TCIF_STATE | ExternMessage.TCIF_TEXT;
    tcitem.size = 200;
    tcitem.text = new string('x', tcitem.size);
    int res = (int)SendMessageComplexGeneric<externMessage.TCITEM>(handle, ExternMessage.TCM_GETITEMA, index, ref tcitem, SendMessageDirection._inout);
    return tcitem.text;
}
 
public Rectangle GetTabRectangle(int handle, int index)
{
    ExternMessage.RECT rect = new ExternMessage.RECT();
    SendMessageGeneric<externMessage.RECT>(handle, ExternMessage.TCM_GETITEMRECT, index, ref rect, SendMessageDirection._inout);
    System.Drawing.Rectangle res = new System.Drawing.Rectangle(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
    return res;
}

As always, I hope it is useful to you. Having received so much help myself from a lot smarter people, I am always working on giving something little back out of heart-felt gratitude. It certainly was (yet again like so many other things) an eye-opener to me about some inner workings of Windows, Messaging and Marshalling.

Please feel to comment upon anything and everything, I always enjoy getting feedback from my readers.

References

As you may have seen, this is based on ideas and experiences I had in this article about filling memory in a foreign process, which was meant for the SendMessage function as well.
Also, there is always loads of information up on StackOverflow on any matter of programming in C#, as well as the information on PInvoke.net, which is very helpful, especially for beginners, but also for advanced tips on many matters (like 64 bit issues with SendMessage).

3 thoughts on “Generic Wrapper for SendMessage calls in C# through reflection

  1. Hi, I was looking for a way to sendmessage from a worker thread to the UI for update and came across your post. So I tried out your code with very minor change (change int to IntPtr for the handle and added SendMessageDirection). I got crashed without much feedback. I know the crash has to do with SendMessage when I did it the „standard“ way like this:

    1
    2
    3
    4
    5
    
                            IntPtr lParam = Marshal.AllocHGlobal(Marshal.SizeOf(rcv_data));
                            Marshal.StructureToPtr(rcv_data,lParam, false);
                            SendMessage(hwnd, Msg, RCV_REPORT, lParam); // will crash
                            Marshal.PtrToStructure(lParam, rcv_data);
                            Marshal.FreeHGlobal (lParam);

    if I commented out the SendMessage statement, then no crash and no update, of course.

    Could you please take a look of my code snippet and tell me what I did wrong?

    I defined two structs (one in another) as follows:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 1)]
    public struct tagRCV_FILE_HEADEx
    {                      
        public int m_dwLen;                                     
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 260)]
        public byte[] m_szFileName;                        
    }
     
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 1)]
    public struct tagRCV_DATA
    {
        public int m_nPacketNum;                                        
        [MarshalAs(UnmanagedType.Struct)]
        public tagRCV_FILE_HEADEx m_File;                                 
    }

    Here’s how I initialized:

    1
    2
    3
    4
    
    tagRCV_DATA rcv_data = new tagRCV_DATA ();
    rcv_data.m_File = new tagRCV_FILE_HEADEx();
    rcv_data.m_File.m_szFileName = new byte[260];    
    rcv_data.m_nPacketNum = 1;

    and here’s how I did marshalling and call SendMessage:
    (// let’s say hwnd, Msg and RCV_REPORT are known parameters)

    1
    
    SendMessageGeneric(hwnd, Msg, RCV_REPORT, ref rcv_data, SendMessageDirection._inout);

    Thanks in advance!

    1. Hello Sean!

      Some things:

      1. I assume your 2 threads run in the same process (worker thread and UI thread)
      2. My post is about using SendMessage from one process to another process, which makes the whole thing a lot more complicated, amongst others, one first needs to find the hwnd of the target window, allocate memory in another process, …
      3. If your 2 threads are in the same process, everything should be really easy, pinvoke should have a much better description on using SendMessage for that use case
      4. As far as I know, there is no restriction on thread context, so you can fire your SendMessage in any worker thread, and it will correctly address the UI thread. SendMessage does wait for the message to be processed, alternatively, there is also PostMessage, which does not wait
      5. Should you still want to execute the Sendmessage within the context of the UI thread, you should read this article by me on using invoke, that makes sure any code wrapped by the invoke-call runs in the context of the UI thread
      6. You can also start with a very easy message (just see that nothing crashes) like given in the Tips & Tricks section on pinovke
      7. You need to correctly define the structs. Quick google shows that the struct should be
        1
        2
        3
        4
        5
        6
        7
        
        typedef struct tagRCV_FILE_HEADEx
        {
        DWORD m_dwAttrib; / / file subtype
        DWORD m_dwLen; / / file length
        DWORD m_dwSerialNo; / / serial number
        char m_szFileName [MAX_PATH]; / / file name or URL
        } RCV_FILE_HEADEx;

        Most structs I have come across have as one of their parameters their length, and (I think, that is a long time ago) most often arrays are not realized as pointers but simply directly within the struct. Maybe that is not relevant to you since I do not know there stuctures, you definitely need to have them 100%ly accurate.

      I suppose the whole concept is still a little new to you. Start with easy things and make it more like what you want. What is involved is not always easy at first sight when never used before. Pinvoke is a great resource to get syntax and examples, also, there is msdn or the Windows header files, if all else fails.

Schreibe einen Kommentar

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