2011-12-30 10 views
16

Ho sperimentato con delegati C++/CLI (come sto cercando di creare una libreria di riferimento .NET) e ho riscontrato il seguente problema.C++/CLI delegato come puntatore di funzione (System.AccessViolationException)

Definisco un delegato in C++/CLI, quindi creo un'istanza del delegato in C# e quindi richiamo l'istanza del delegato tramite C++ non gestito tramite un puntatore a funzione. Tutto funziona come previsto.

Codice per illustrare

using System; 

namespace TestProgram 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      Library.Test.MessageDelegate messageDelegate = new Library.Test.MessageDelegate(Message); 
      Library.Test test = new Library.Test(messageDelegate); 
      test.WriteMessage(); 
      Console.Read(); 
     } 

     static void Message() 
     { 
      Console.WriteLine(1024); 
     } 
    } 
} 

Successivo file di mio Managed C++ (Managed.cpp)

#include "Unmanaged.hpp" 
#include <string> 

namespace Library 
{ 
    using namespace System; 
    using namespace System::Runtime::InteropServices; 

    public ref class Test 
    { 
    public: 
     delegate void MessageDelegate(); 
    internal: 
     MessageDelegate^ Message; 
     void* delegatePointer; 

    public: 
     Test(MessageDelegate^ messageDelegate) 
     { 
      delegatePointer = (void*)Marshal::GetFunctionPointerForDelegate(messageDelegate).ToPointer(); 
     } 

     void WriteMessage() 
     { 
      Unmanaged::WriteMessage(delegatePointer); 
     } 
    }; 
} 

E la mia non gestito C++ di file (Unmanaged.cpp) questo (prima il mio C#)

#include "Unmanaged.hpp" 

namespace Unmanaged 
{ 
    typedef void (*WriteMessageType)(); 
    WriteMessageType WriteMessageFunc; 

    void WriteMessage(void* Function) 
    { 
     WriteMessageType WriteMessageFunc = (WriteMessageType)(Function); 
     WriteMessageFunc(); 
    } 
} 

Questo codice funziona come previsto e l'output è "1024", poiché Method() viene chiamato dal puntatore alla funzione del metodo di esempio.

Il mio problema sorge quando provo ad applicare lo stesso metodo con un delegato con argomenti, ovvero: delegate void MessageDelegate (int number);

Il mio codice è ora la seguente (C#):

using System; 

namespace AddProgram 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      Library.Test.MessageDelegate messageDelegate = new Library.Test.MessageDelegate(Message); 
      Library.Test test = new Library.Test(messageDelegate); 
      test.WriteMessage(1024); 
      Console.Read(); 
     } 

     static void Message(int number) 
     { 
      Console.WriteLine(number); 
     } 
    } 
} 

mio gestito di file C++:

#include "Unmanaged.hpp" 
#include <string> 

namespace Library 
{ 
    using namespace System; 
    using namespace System::Runtime::InteropServices; 

    public ref class Test 
    { 
    public: 
     delegate void MessageDelegate(int number); 
    internal: 
     MessageDelegate^ Message; 
     void* delegatePointer; 

    public: 
     Test(MessageDelegate^ messageDelegate) 
     { 
      delegatePointer = (void*)Marshal::GetFunctionPointerForDelegate(messageDelegate).ToPointer(); 
     } 

     void WriteMessage(int number) 
     { 
      Unmanaged::WriteMessage(delegatePointer, number); 
     } 
    }; 
} 

E la mia non gestito di file C++:

#include "Unmanaged.hpp" 

namespace Unmanaged 
{ 
    typedef void (*WriteMessageType)(int number); 
    WriteMessageType WriteMessageFunc; 

    void WriteMessage(void* Function, int number) 
    { 
     WriteMessageType WriteMessageFunc = (WriteMessageType)(Function); 
     WriteMessageFunc(number); 
    } 
} 

Quando ho eseguire il programma ottengo il seguente errore:

Un'eccezione non gestita di tipo 'System.AccessViolationException' si è verificata nella libreria Unmanaged Test.dll

Ulteriori informazioni: Tentativo di leggere o scrivere memoria protetta. Questo è spesso un'indicazione che un'altra memoria è corrotta.

Per inciso, la finestra della console mostra 1024, ma è seguita da un int casuale (~ 1000000), quindi viene visualizzato l'errore.

Posso iniziare ad immaginare alcuni dei motivi per cui ho riscontrato questo errore, ma non sono sicuro e sto avendo difficoltà a scoprirlo. Se qualcuno potesse dirmi perché sto ricevendo questo errore e cosa potrei fare per risolverlo, lo apprezzerei molto.

+0

+1 per una domanda ben descritta –

risposta

11
void WriteMessage(void* Function, int number) 

Il passaggio dei puntatori alle funzioni come vuoto * è un'idea piuttosto negativa. Impedisce al compilatore di controllare che stai facendo qualcosa di sbagliato. C'è qualcosa di sbagliato, anche se il compilatore non può rilevarlo in questo caso specifico. Il delegato viene eseguito il marshalling come puntatore a funzione che utilizza la convenzione di chiamata __stdcall, mentre il puntatore della funzione effettiva utilizza la convenzione di chiamata __cdecl, l'impostazione predefinita per il codice nativo. Che fa sì che lo stack si sbilanci nella chiamata.

È possibile risolvere il problema applicando lo [UnmanagedFunctionPointer] attribute alla dichiarazione del delegato, specificare CallingConvention :: Cdecl.

+0

Grazie mille, perché è che senza l'argomento questo non è richiesto? – xcvd

+4

E 'ancora richiesto, non fa alcuna differenza visto che nulla viene messo in pila. –

1

Un puntatore di funzione creato da un delegato è invisibile al garbage collector e non viene conteggiato durante l'analisi di raggiungibilità.

Da the documentation:

You must manually keep the delegate from being collected by the garbage collector from managed code. The garbage collector does not track reference [sic] to unmanaged code.

Se il delegato viene raccolto, il puntatore a funzione è lasciato penzoloni, e il programma si comportano male. Una violazione di accesso è uno dei risultati più probabili, ma non l'unica possibilità. Se la memoria utilizzata per contenere il trampolino nativo/gestito viene riutilizzato per altri dati, la CPU potrebbe tentare di interpretarlo come istruzioni, il che potrebbe significare qualsiasi cosa.

La soluzione è di mantenere il delegato raggiungibile, ad esempio tramite la classe C++/CLI gcroot, che è un sottile involucro attorno a .NET GCHandle.

+0

Non sono stato in grado di risolvere il problema utilizzando GCHandle. Sembra che anche quando il delegato viene allocato con GCHandle si verifichi lo stesso errore, quindi non penso che questo sia un problema con il garbage collector. – xcvd

+0

Devo anche aggiungere che non appena compilo Unmanaged.cpp con/cli il problema scompare – xcvd