494 lines
19 KiB
C#
494 lines
19 KiB
C#
// ==++==
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
// ==--==
|
|
|
|
// Verbose runtime tracing
|
|
//#define ENABLE_DEBUG_PRINT
|
|
|
|
// This class provides runtime support for managing delegates that may have
|
|
// been passed in to native code.
|
|
//
|
|
// The problem with these delegates is that the GC may move the delegate
|
|
// object without being aware of references from native code. Rather than
|
|
// pinning the objects (which currently operates at a page granularity), we
|
|
// allocate GC-stable indexes ("delegate idxs") which the native code uses in
|
|
// place of the underlying Delegate reference.
|
|
//
|
|
// The entry points from compiled code are:
|
|
//
|
|
// GetCodePtr, which allocates a short function to enter a delegate, passing
|
|
// in the delegate idx.
|
|
//
|
|
// ReleaseCodePtr, which deallocates the function and releases the delegate
|
|
// idx.
|
|
//
|
|
// The NativeDelegateTable is used to map delegate idxs to Delegate references.
|
|
// References from this table are ordinary strong references as far as the GC
|
|
// is concerned.
|
|
namespace System
|
|
{
|
|
|
|
using System.Reflection;
|
|
using System.Runtime.InteropServices;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Threading;
|
|
|
|
//| <include path='docs/doc[@for="Delegate"]/*' />
|
|
public abstract partial class Delegate : ICloneable
|
|
{
|
|
private IntPtr CodePtr;
|
|
|
|
// equals returns true IIF the delegate is not null and has the
|
|
// same target, method and invocation list as this object
|
|
//| <include path='docs/doc[@for="Delegate.Equals"]/*' />
|
|
public override bool Equals(Object obj)
|
|
{
|
|
if (obj != null && obj is Delegate) {
|
|
Delegate d = (Delegate) obj;
|
|
if (IsStatic()) {
|
|
if (_methodPtrAux == d._methodPtrAux) {
|
|
return true;
|
|
}
|
|
}
|
|
else {
|
|
if (d._target == _target &&
|
|
d._methodPtr == _methodPtr) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//| <include path='docs/doc[@for="Delegate.GetHashCode"]/*' />
|
|
public override int GetHashCode()
|
|
{
|
|
if (IsStatic())
|
|
return this._methodPtrAux.ToInt32();
|
|
else
|
|
return this._methodPtr.ToInt32();
|
|
}
|
|
|
|
// Combine creates a new delegate based upon the contents of the
|
|
// delegates passed in.
|
|
//| <include path='docs/doc[@for="Delegate.Combine"]/*' />
|
|
public static Delegate Combine(Delegate a, Delegate b)
|
|
{
|
|
//@TODO: Should this really check to see that they are both multicast
|
|
// because it really is an error to try and combine non-multicasts?
|
|
// Spec says that it returns null and only does the check if a and b
|
|
// are both null.
|
|
|
|
// boundary conditions -- if either (or both) delegates is null
|
|
// return the other.
|
|
if (a == null)
|
|
return b;
|
|
if (b == null)
|
|
return a;
|
|
|
|
// Verify that the types are the same...
|
|
if (a.GetType() != b.GetType())
|
|
throw new ArgumentException("Arg_DlgtTypeMis");
|
|
return a.CombineImpl(b);
|
|
}
|
|
|
|
// This method creates a new delegate based upon the passed
|
|
// array of delegates.
|
|
//| <include path='docs/doc[@for="Delegate.Combine1"]/*' />
|
|
public static Delegate Combine(Delegate[] delegates)
|
|
{
|
|
if (delegates == null || delegates.Length == 0)
|
|
return null;
|
|
|
|
Delegate d = delegates[0];
|
|
for (int i = 1; i < delegates.Length; i++)
|
|
d = Combine(d,delegates[i]);
|
|
return d;
|
|
}
|
|
|
|
// Return an array of delegates that represent the invocation list.
|
|
// This is basically THIS for a Delegate. MulticastDelegates may
|
|
// have multiple members.
|
|
//| <include path='docs/doc[@for="Delegate.GetInvocationList"]/*' />
|
|
public virtual Delegate[] GetInvocationList() {
|
|
Delegate[] d = new Delegate[1];
|
|
d[0] = this;
|
|
return d;
|
|
}
|
|
|
|
// This routine will return the target
|
|
//| <include path='docs/doc[@for="Delegate.Target"]/*' />
|
|
public Object Target
|
|
{
|
|
get {return IsStatic() ? null : _target;}
|
|
}
|
|
|
|
|
|
|
|
//A quick test to see if this is a delegate to a static method.
|
|
//@ToDo: Verify that this is a sufficient test.
|
|
private bool IsStatic() {
|
|
if (_target is Delegate) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// This will remove the value delegate from the source delegate
|
|
// if it found.
|
|
//| <include path='docs/doc[@for="Delegate.Remove"]/*' />
|
|
public static Delegate Remove(Delegate source, Delegate value)
|
|
{
|
|
if (source == null)
|
|
return null;
|
|
if (value == null)
|
|
return source;
|
|
return source.RemoveImpl(value);
|
|
}
|
|
|
|
//| <include path='docs/doc[@for="Delegate.RemoveAll"]/*' />
|
|
public static Delegate RemoveAll(Delegate source, Delegate value)
|
|
{
|
|
Delegate newDelegate = null;
|
|
do {
|
|
newDelegate = source;
|
|
source = Remove(source, value);
|
|
} while (newDelegate != source);
|
|
return newDelegate;
|
|
}
|
|
|
|
// This is an internal routine that is called to do the combine. We
|
|
// use this to do the combine because the Combine routines are static
|
|
// final methods. In Delegate, this simply throws a MulticastNotSupportedException
|
|
// error. Multicast delegate must implement this.
|
|
//| <include path='docs/doc[@for="Delegate.CombineImpl"]/*' />
|
|
protected virtual Delegate CombineImpl(Delegate d)
|
|
{
|
|
throw new MulticastNotSupportedException("Multicast_Combine");
|
|
}
|
|
|
|
// This is an internal routine that is called to do the remove. We use this
|
|
// to do the remove because Remove is a static final method. Here we simply
|
|
// make sure that d is equal to this and return null or this.
|
|
//| <include path='docs/doc[@for="Delegate.RemoveImpl"]/*' />
|
|
protected virtual Delegate RemoveImpl(Delegate d)
|
|
{
|
|
if (_target == d._target && _methodPtr == d._methodPtr)
|
|
return null;
|
|
else
|
|
return this;
|
|
}
|
|
|
|
//| <include path='docs/doc[@for="Delegate.Clone"]/*' />
|
|
public virtual Object Clone()
|
|
{
|
|
return MemberwiseClone();
|
|
}
|
|
|
|
//| <include path='docs/doc[@for="Delegate.operatorEQ"]/*' />
|
|
public static bool operator ==(Delegate d1, Delegate d2) {
|
|
if ((Object)d1 == null)
|
|
return (Object)d2 == null;
|
|
return d1.Equals(d2);
|
|
}
|
|
|
|
//| <include path='docs/doc[@for="Delegate.operatorNE"]/*' />
|
|
public static bool operator != (Delegate d1, Delegate d2) {
|
|
if ((Object)d1 == null)
|
|
return (Object)d2 != null;
|
|
return !d1.Equals(d2);
|
|
}
|
|
|
|
// GetCodePtr should be called by the marshalling code with the
|
|
// delegate and the address of the call back stub for that
|
|
// delegate class.
|
|
[RequiredByBartok]
|
|
private static IntPtr GetCodePtr(Delegate d,IntPtr callBackStub)
|
|
{
|
|
IntPtr codePtr = d.CodePtr;
|
|
if (codePtr == IntPtr.Zero) {
|
|
|
|
CriticalSection.AcquireMutex();
|
|
try {
|
|
codePtr = d.CodePtr;
|
|
if (codePtr == IntPtr.Zero) {
|
|
int idx = AllocateNativeDelegateRecord(d);
|
|
codePtr = CreateCodePtr(idx, callBackStub);
|
|
d.CodePtr = codePtr;
|
|
}
|
|
}
|
|
finally {
|
|
CriticalSection.ReleaseMutex();
|
|
}
|
|
}
|
|
return codePtr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
//
|
|
internal static IntPtr FixedAlloc(int sizetdwBytes)
|
|
{
|
|
VTable.DebugBreak();
|
|
return IntPtr.Zero;
|
|
}
|
|
|
|
internal static void FixedFree(IntPtr handle)
|
|
{
|
|
VTable.DebugBreak();
|
|
}
|
|
|
|
// Create a little piece of assembly code that adds idx onto the
|
|
// argument list (by pushing it onto the stack) and jumps to the
|
|
// call back stub.
|
|
|
|
// The idx is added as the first argument. The calling
|
|
// convention is assumed to be STDCALL.
|
|
//
|
|
// The call back stub is a function created by the Bartok code
|
|
// generator and:
|
|
// 1. Uses the idx to get to the delegate via the NativeDelegateTable
|
|
// 2. Calls the invoke method of the delegate
|
|
|
|
private static unsafe IntPtr CreateCodePtr(int idx,
|
|
IntPtr callBackStub) {
|
|
IntPtr buffer = FixedAlloc(32);
|
|
if (buffer == IntPtr.Zero) {
|
|
return IntPtr.Zero;
|
|
}
|
|
// The desired instructions:
|
|
// push [sp]
|
|
// mov [sp+4], idx
|
|
// jmp callBackStub
|
|
//
|
|
byte* codeBuffer = (byte *) buffer.ToPointer();
|
|
// push [sp]
|
|
*(codeBuffer) = 0xFF;
|
|
*(codeBuffer+1) = 0x34;
|
|
*(codeBuffer+2) = 0x24;
|
|
// mov [sp+4], idx
|
|
*(codeBuffer+3) = 0xC7;
|
|
*(codeBuffer+4) = 0x44;
|
|
*(codeBuffer+5) = 0x24;
|
|
*(codeBuffer+6) = 0x04;
|
|
*((int*) (codeBuffer+7)) = idx;
|
|
// jmp callBackStub
|
|
*(codeBuffer+11) = 0xE9;
|
|
IntPtr disp = callBackStub - buffer;
|
|
*((int *) (codeBuffer+12)) = (disp - 16).ToInt32();
|
|
//
|
|
// pad the rest of the buffer with int 3
|
|
//
|
|
|
|
for (int i = 16; i < 32; i++) {
|
|
*(codeBuffer+i) = 0xcc;
|
|
}
|
|
return buffer;
|
|
}
|
|
|
|
// This method accepts a code pointer and allows the NativeDelegateRecord
|
|
// associated with it to be collected. In order to call this method
|
|
// safely it must be certain that no references to the code ptr exist
|
|
// in native code.
|
|
private static unsafe void ReleaseCodePtr(IntPtr codePtr) {
|
|
byte* codeBuffer = (byte *)codePtr.ToPointer();
|
|
int idx = *(int*)(codeBuffer+7);
|
|
Delegate del = IdxToDelegate(idx);
|
|
del.CodePtr = IntPtr.Zero;
|
|
CriticalSection.AcquireMutex();
|
|
try {
|
|
FreeNativeDelegateRecord(idx);
|
|
}
|
|
finally {
|
|
CriticalSection.ReleaseMutex();
|
|
}
|
|
FixedFree(codePtr);
|
|
}
|
|
|
|
// <summary>
|
|
// The CriticalSection protects the CodePtr fields during
|
|
// initialization and the NativeDelegateTable arrays when claiming
|
|
// indexes in them.
|
|
// </summary>
|
|
private static readonly Mutex CriticalSection;
|
|
|
|
// <summary>
|
|
// The NativeDelegateTable is held as a triangular array, starting
|
|
// with small arrays of NativeDelegateRecord structures and using
|
|
// successively larger arrays as the table is expanded. This avoids
|
|
// frequent allocations in programs that pass a lot of delegates
|
|
// to native code, and allows an idx to map to a (table,offset) pair
|
|
// in O(log n) steps.
|
|
// </summary>
|
|
private static NativeDelegateRecord[][] NativeDelegateTable;
|
|
|
|
// In a debug build, start the NativeDelegateTable size very small in order to
|
|
// force testing of table expansion
|
|
#if DEBUG
|
|
private const int FIRST_TABLE_SIZE = 1;
|
|
#else
|
|
private const int FIRST_TABLE_SIZE = 16;
|
|
#endif
|
|
|
|
// <summary>
|
|
// FreeListStartIdx is the idx is the head of the list of NativeDelegateTable
|
|
// entries that have been freed. -1 indicates that the free list is empty.
|
|
// </summary>
|
|
private static int FreeListStartIdx;
|
|
private const int FREE_LIST_EMPTY = -1;
|
|
|
|
// <summary>
|
|
// FreeExtentStartIdx is the idx of the start of the contiguous
|
|
// extent of NativeDelegateTable entries. These are logically
|
|
// considered at the end of the free list: we use entries from the free
|
|
// list in preference to using 'pristine' space from the free extent.
|
|
// </summary>
|
|
private static int FreeExtentStartIdx;
|
|
|
|
// <summary>
|
|
// Each entry in the NativeDelegateTable currently holds an ordinary
|
|
// reference to the Delegate and an integer used to form the free list
|
|
// of unused records. We could overload a single field, but note that
|
|
// (a) we would then need to special-case the GC visiting code and
|
|
// that (b) during GC we could not simply iterate through the arrays because
|
|
// we couldn't distinguish free-list entries for allocated ones.
|
|
//
|
|
// Of course, we could steal a bit in the object reference to distinguish
|
|
// the free list, using odd values for the free list to avoid needing
|
|
// to mask the contents in IdxToDelegate.
|
|
//
|
|
// Seems like needless complexity unless we find the table is getting
|
|
// large.
|
|
// </summary>
|
|
private struct NativeDelegateRecord {
|
|
Delegate del;
|
|
int nextIdx;
|
|
|
|
internal void Allocate(Delegate del) {
|
|
VTable.Assert(this.del == null);
|
|
this.del = del;
|
|
}
|
|
|
|
internal void DeAllocate(int nextIdx) {
|
|
VTable.Assert(this.del != null);
|
|
this.del = null;
|
|
this.nextIdx = nextIdx;
|
|
}
|
|
|
|
internal Delegate Delegate() {
|
|
VTable.Assert(this.del != null);
|
|
return this.del;
|
|
}
|
|
|
|
internal int NextIdx() {
|
|
VTable.Assert(this.del == null);
|
|
return this.nextIdx;
|
|
}
|
|
}
|
|
|
|
// Allocate a native delegate record for "del". The caller must hold
|
|
// the CriticalSection.
|
|
private static int AllocateNativeDelegateRecord(Delegate del) {
|
|
int idx;
|
|
int table;
|
|
int offset;
|
|
|
|
DebugPrint("AllocateNativeDelegateRecord (list={0}, extent {1})\n",
|
|
__arglist(FreeListStartIdx, FreeExtentStartIdx));
|
|
|
|
if (FreeListStartIdx != FREE_LIST_EMPTY) {
|
|
// There's space in the free list: use that
|
|
int nextFreeIdx;
|
|
idx = FreeListStartIdx;
|
|
SplitIdx (idx, out table, out offset);
|
|
nextFreeIdx = NativeDelegateTable[table][offset].NextIdx();
|
|
FreeListStartIdx = nextFreeIdx;
|
|
}
|
|
else {
|
|
// There's no space in the free list, use the next slot in the
|
|
// free extent.
|
|
idx = FreeExtentStartIdx;
|
|
SplitIdx (idx, out table, out offset);
|
|
FreeExtentStartIdx ++;
|
|
if (offset + 1 == NativeDelegateTable[table].Length) {
|
|
// We've used the last slot in the free extent: allocate some
|
|
// more.
|
|
int currentLength = NativeDelegateTable[table].Length;
|
|
int nextLength = currentLength * 2;
|
|
DebugPrint("AllocateNativeDelegateRecord expanding to {0} len {1}\n",
|
|
__arglist(table + 1, nextLength));
|
|
NativeDelegateTable[table + 1] = new NativeDelegateRecord[nextLength];
|
|
}
|
|
}
|
|
|
|
NativeDelegateTable[table][offset].Allocate(del);
|
|
|
|
DebugPrint("AllocateNativeDelegateRecord got {0} (list={1}, extent {2})\n",
|
|
__arglist(idx, FreeListStartIdx, FreeExtentStartIdx));
|
|
|
|
return idx;
|
|
}
|
|
|
|
private static void FreeNativeDelegateRecord(int idx) {
|
|
int table;
|
|
int offset;
|
|
|
|
DebugPrint("FreeNativeDelegateRecord {0} (list={1}, extent {2})\n",
|
|
__arglist(idx, FreeListStartIdx, FreeExtentStartIdx));
|
|
|
|
SplitIdx (idx, out table, out offset);
|
|
NativeDelegateTable[table][offset].DeAllocate(FreeListStartIdx);
|
|
FreeListStartIdx = idx;
|
|
}
|
|
|
|
[RequiredByBartok]
|
|
private static Delegate IdxToDelegate(int idx) {
|
|
int table;
|
|
int offset;
|
|
Delegate result;
|
|
|
|
SplitIdx (idx, out table, out offset);
|
|
result = NativeDelegateTable[table][offset].Delegate();
|
|
DebugPrint("IdxToDelegate {0} -> {1}\n", __arglist(idx, result));
|
|
|
|
return result;
|
|
}
|
|
|
|
private static void SplitIdx(int idx, out int table, out int offset) {
|
|
table = 0;
|
|
offset = idx;
|
|
while (offset >= NativeDelegateTable[table].Length) {
|
|
offset -= NativeDelegateTable[table].Length;
|
|
table ++;
|
|
}
|
|
DebugPrint("SplitIdx {0} -> {1}.{2}\n", __arglist(idx, table, offset));
|
|
}
|
|
|
|
static Delegate() {
|
|
CriticalSection = new Mutex();
|
|
NativeDelegateTable = new NativeDelegateRecord[24][];
|
|
NativeDelegateTable[0] = new NativeDelegateRecord[FIRST_TABLE_SIZE];
|
|
FreeExtentStartIdx = 0;
|
|
FreeListStartIdx = FREE_LIST_EMPTY;
|
|
|
|
#if FALSE // Enable this code to force testing of the FreeNativeDelegateRecord fn
|
|
for (int i = 0; i < FIRST_TABLE_SIZE * 2; i ++) {
|
|
int idx = AllocateNativeDelegateRecord(CriticalSection);
|
|
VTable.Assert (idx == i);
|
|
}
|
|
for (int i = 0; i < FIRST_TABLE_SIZE * 2; i ++) {
|
|
FreeNativeDelegateRecord(i);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
[System.Diagnostics.Conditional("ENABLE_DEBUG_PRINT")]
|
|
[NoInline]
|
|
internal static void DebugPrint(String v, __arglist)
|
|
{
|
|
VTable.DebugPrint(v, new ArgIterator(__arglist));
|
|
}
|
|
}
|
|
}
|