Voglio premettere che lo scopo di questa domanda è controllare se c'è almeno un modo, anche se attraverso l'hack più pericoloso, per mantenere un riferimento a un tipo di valore non bliabile . Sono consapevole che un tale tipo di design è paragonabile a un crimine; Non lo userei in nessun caso pratico, oltre all'apprendimento. Quindi per favore accetta di leggere heretic codice non sicuro per ora.Mantieni vivo un TypedReference dal blocco del metodo senza restituirlo
sappiamo che un riferimento ad un tipo copiabili può essere memorizzato e aumentata in questo modo:
unsafe class Foo
{
void* _ptr;
public void Fix(ref int value)
{
fixed (void* ptr = &value) _ptr = ptr;
}
public void Increment()
{
var pointer = (int*) _ptr;
(*pointer)++;
}
}
In termini di sicurezza, la classe sopra è paragonabile ad un salto nel vuoto (senza giochi di parole), ma funziona, come già detto here. Se viene passata una variabile allocata nello stack e quindi l'ambito del metodo del chiamante termina, è probabile che si verifichi un errore o un errore di violazione di accesso esplicito. Tuttavia, se si esegue un programma come questo:
static class Program
{
static int _fieldValue = 42;
public static void Main(string[] args)
{
var foo = new Foo();
foo.Fix(ref _fieldValue);
foo.Increment();
}
}
La classe non sarà disposto fino a quando il dominio applicazione relativa viene scaricato, e così vale per il campo. Onestamente non so se i campi nell'heap ad alta frequenza possano essere riallocati, ma personalmente non penso. Ma mettiamo da parte la sicurezza ancora di più adesso (se possibile). Dopo aver letto le domande this e this mi chiedevo se esistesse un modo per creare un approccio simile per i tipi statici non intercettabili, quindi ho creato questo programma, che funziona effettivamente. Leggi i commenti per vedere cosa fa.
L'attuale problema/opportunità:
Sappiamo che il compilatore non ci permetterà di memorizzare un valore passato da ref, ma la parola chiave __makeref
, per quanto privi di documenti e sconsigliato, offre la possibilità di incapsulare e ripristinare un riferimento a tipi blittable. Tuttavia, il valore restituito di __makeref
, TypedReference
, è ben protetto. Non puoi archiviarlo in un campo, non puoi metterlo in una scatola, non puoi creare un array, non puoi usarlo in metodi anonimi o lambda. Tutto quello che sono riuscito a fare è stato la modifica del codice di cui sopra come segue:
static void* _ptr;
static void MakerefTest(ref Action multicast)
{
Action handler =() => Console.WriteLine("Hello world.");
multicast += handler;
if (multicast != null) multicast();
var tr = __makeref(multicast);
//Storing the address of the TypedReference (which is on the stack!)
//inside of _ptr;
_ptr = (void*) &tr;
//Getting the TypedReference back from the pointer:
var restoredTr = *(TypedReference*) _ptr;
__refvalue(restoredTr, Action) -= handler;
}
Il codice qui sopra funziona altrettanto bene e sembra anche peggio di prima, ma per amore della conoscenza, ho voluto fare di più con esso, in modo da ho scritto il seguente:
unsafe class Horror
{
void* _ptr;
static void Handler()
{
Console.WriteLine("Hello world.");
}
public void Fix(ref Action action)
{
action += Handler;
var tr = __makeref(action);
_ptr = (void*) &tr;
}
public void Clear()
{
var tr = *(TypedReference*) _ptr;
__refvalue(tr, Action) -= Handler;
}
}
la classe Horror
è una combinazione della classe Foo
ed il metodo di cui sopra, ma come avrete sicuramente notato, ha un grosso problema. Nel metodo Fix
, viene dichiarato il TypedReference
tr
, il suo indirizzo viene copiato all'interno del puntatore generico _ptr
, quindi il metodo termina e tr
non esiste più. Quando viene chiamato il metodo Clear
, il "nuovo" tr
è danneggiato perché i punti _ptr
puntano a un'area della pila che non è più una TypedReference
. Quindi ecco la domanda:
C'è un modo per ingannare il compilatore in modo da mantenere in vita un'istanza TypedReference
per un periodo di tempo indeterminato?
Qualsiasi modo per ottenere il risultato desiderato sarà considerato buono, anche se comporta codice brutto, pericoloso, lento.Una classe che implementa la seguente interfaccia sarebbe l'ideale:
interface IRefStorage<T> : IDisposable
{
void Store(ref T value);
//IDisposable.Dispose should release the reference
}
Si prega di non giudicare la questione discussione generica perché il suo scopo è quello di vedere se, dopo tutto, non v'è un modo per memorizzare i riferimenti a tipi copiabili per quanto malvagio.
Un'ultima osservazione, sono a conoscenza delle possibilità di vincolare i campi tramite FieldInfo
, ma mi è sembrato che quest'ultimo metodo non supportasse molto i tipi derivanti da Delegate
.
Una possibile soluzione (risultato di taglie)
avrei segnato la risposta di AbdElRaheim a scelta, non appena ha curato il suo posto per includere la soluzione che ha fornito nel suo commento, ma suppongo che non era molto chiaro. In ogni caso, tra le tecniche che ha fornito, quella sintetizzata nella seguente classe (che ho leggermente modificato) è sembrata la più "affidabile" (ironico usare quel termine, visto che stiamo parlando di sfruttare un hack):
unsafe class Horror : IDisposable
{
void* _ptr;
static void Handler()
{
Console.WriteLine("Hello world.");
}
public void Fix(ref Action action)
{
action += Handler;
TypedReference tr = __makeref(action);
var mem = Marshal.AllocHGlobal(sizeof (TypedReference)); //magic
var refPtr = (TypedReference*) mem.ToPointer();
_ptr = refPtr;
*refPtr = tr;
}
public void Dispose()
{
var tr = *(TypedReference*)_ptr;
__refvalue(tr, Action) -= Handler;
Marshal.FreeHGlobal((IntPtr)_ptr);
}
}
che Fix
non è, a partire dalla linea contrassegnata come "magico" nel commento:
- Alloca memoria nel processo - nella parte non gestita di esso.
- Dichiara
refPtr
come puntatore aTypedReference
e imposta il valore sul puntatore dell'area di memoria allocata in precedenza. A tal fine, anziché utilizzare direttamente_ptr
, un campo con tipoTypedReference*
genererebbe un'eccezione. - Trasmette implicitamente
refPtr
avoid*
e assegna il puntatore a_ptr
. - Imposta
tr
come valore indicato darefPtr
e di conseguenza_ptr
.
Ha anche offerto un'altra soluzione, quella che ha originariamente scritto come risposta, ma sembrava meno affidabile di quella precedente. D'altra parte, c'era anche un'altra soluzione fornita da Peter Wishart, ma richiedeva una sincronizzazione accurata e ogni istanza di Horror
avrebbe "sprecato" una discussione. Prenderò la possibilità di ripetere che l'approccio di cui sopra è in no in modo destinato all'uso del mondo reale, era solo una questione accademica. Spero che possa essere d'aiuto a chiunque legga questa domanda.
Si sta richiedendo un bug come funzionalità. Abbastanza infame nella programmazione C e C++, lingue che creano molto facilmente questo tipo di bug. Google "puntatore penzolante" per saperne di più. –