summaryrefslogtreecommitdiff
path: root/src/mscorlib/src/System/Runtime/InteropServices/ComEventsMethod.cs
blob: 5b851f3dae26aefecaddca1a658a4bd5c093553e (plain)
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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.


/*============================================================
**
**
** Purpose: part of ComEventHelpers APIs which allow binding 
** managed delegates to COM's connection point based events.
**
**/
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Reflection;


namespace System.Runtime.InteropServices {

    // see code:ComEventsHelper#ComEventsArchitecture
    internal class ComEventsMethod {

        // This delegate wrapper class handles dynamic invocation of delegates. The reason for the wrapper's
        // existence is that under certain circumstances we need to coerce arguments to types expected by the
        // delegates signature. Normally, reflection (Delegate.DynamicInvoke) handles types coercion
        // correctly but one known case is when the expected signature is 'ref Enum' - in this case
        // reflection by design does not do the coercion. Since we need to be compatible with COM interop
        // handling of this scenario - we are pre-processing delegate's signature by looking for 'ref enums'
        // and cache the types required for such coercion.
        internal class DelegateWrapper {
            private Delegate _d;

            private bool _once = false;
            private int _expectedParamsCount;
            private Type[] _cachedTargetTypes;

            public DelegateWrapper(Delegate d) {
                _d = d;
            }

            public Delegate Delegate {
                get { return _d; }
                set { _d = value; }
            }

            public object Invoke(object[] args) {
                if (_d == null)
                    return null;

                if (_once == false) {
                    PreProcessSignature();
                    _once = true;
                }

                if (_cachedTargetTypes != null && _expectedParamsCount == args.Length) {
                    for (int i = 0; i < _expectedParamsCount; i++) {
                        if (_cachedTargetTypes[i] != null) {
                            args[i] = Enum.ToObject(_cachedTargetTypes[i], args[i]);
                        }
                    }
                }

                return _d.DynamicInvoke(args);
            }

            private void PreProcessSignature() {
                ParameterInfo[] parameters = _d.Method.GetParameters();
                _expectedParamsCount = parameters.Length;

                Type[] enumTypes = new Type[_expectedParamsCount];

                bool needToHandleCoercion = false;

                for (int i = 0; i < _expectedParamsCount; i++) {
                    ParameterInfo pi = parameters[i];
                    // recognize only 'ref Enum' signatures and cache
                    // both enum type and the underlying type.
                    if (pi.ParameterType.IsByRef && 
                        pi.ParameterType.HasElementType && 
                        pi.ParameterType.GetElementType().IsEnum) {

                        needToHandleCoercion = true;
                        enumTypes[i] = pi.ParameterType.GetElementType();
                    }
                }

                if (needToHandleCoercion == true) {
                    _cachedTargetTypes = enumTypes;
                }
            }
        }

        #region private fields

        /// <summary>
        /// Invoking ComEventsMethod means invoking a multi-cast delegate attached to it.
        /// Since multicast delegate's built-in chaining supports only chaining instances of the same type,
        /// we need to complement this design by using an explicit linked list data structure.
        /// </summary>
        private DelegateWrapper [] _delegateWrappers;

        private int _dispid;
        private ComEventsMethod _next;

        #endregion

        
        #region ctor
        
        internal ComEventsMethod(int dispid) {
            _delegateWrappers = null;
            _dispid = dispid;
        }
        
        #endregion

        
        #region static internal methods
        
        internal static ComEventsMethod Find(ComEventsMethod methods, int dispid) {
            while (methods != null && methods._dispid != dispid) {
                methods = methods._next;
            }
            return methods;
        }

        internal static ComEventsMethod Add(ComEventsMethod methods, ComEventsMethod method) {
            method._next = methods;
            return method;
        }

        internal static ComEventsMethod Remove(ComEventsMethod methods, ComEventsMethod method) {
            if (methods == method) {
                methods = methods._next;
            } else {
                ComEventsMethod current = methods;
                while (current != null && current._next != method)
                    current = current._next;
                if (current != null)
                    current._next = method._next;
            }

            return methods;
        }
 
        #endregion

        
        #region public properties / methods

        internal int DispId {
            get { return _dispid; }
        }

        internal bool Empty {
            get { return _delegateWrappers == null || _delegateWrappers.Length == 0; }
        }

        internal void AddDelegate(Delegate d) {
            int count = 0;
            if (_delegateWrappers != null) {
                count = _delegateWrappers.Length;
            }

            for (int i = 0; i < count; i++) {
                if (_delegateWrappers[i].Delegate.GetType() == d.GetType()) {
                    _delegateWrappers[i].Delegate = Delegate.Combine(_delegateWrappers[i].Delegate, d);
                    return;
                }
            }

            DelegateWrapper [] newDelegateWrappers = new DelegateWrapper[count + 1];
            if (count > 0) {
                _delegateWrappers.CopyTo(newDelegateWrappers, 0);
            }

            DelegateWrapper wrapper = new DelegateWrapper(d);
            newDelegateWrappers[count] = wrapper;

            _delegateWrappers = newDelegateWrappers;
        }

        internal void RemoveDelegate(Delegate d) {

            int count = _delegateWrappers.Length;
            int removeIdx = -1;

            for (int i = 0; i < count; i++) {
                if (_delegateWrappers[i].Delegate.GetType() == d.GetType()) {
                    removeIdx = i;
                    break;
                }
            }

            if (removeIdx < 0)
                return;

            Delegate newDelegate = Delegate.Remove(_delegateWrappers[removeIdx].Delegate, d);
            if (newDelegate != null) {
                _delegateWrappers[removeIdx].Delegate = newDelegate;
                return;
            }

            // now remove the found entry from the _delegates array

            if (count == 1) {
                _delegateWrappers = null;
                return;
            }

            DelegateWrapper [] newDelegateWrappers = new DelegateWrapper[count - 1];
            int j = 0;
            while (j < removeIdx) {
                newDelegateWrappers[j] = _delegateWrappers[j];
                j++;
            }
            while (j < count-1) {
                newDelegateWrappers[j] = _delegateWrappers[j + 1];
                j++;
            }

            _delegateWrappers = newDelegateWrappers;
        }

        internal object Invoke(object[] args) {
            BCLDebug.Assert(Empty == false, "event sink is executed but delegates list is empty");

            // Issue: see code:ComEventsHelper#ComEventsRetValIssue
            object result = null;
            DelegateWrapper[] invocationList = _delegateWrappers;
            foreach (DelegateWrapper wrapper in invocationList) {
                if (wrapper == null || wrapper.Delegate == null)
                    continue;

                result = wrapper.Invoke(args);
            }

            return result;
        }

        #endregion
    }
}