2011-12-30 16 views
16

He estado experimentando con delegados de C++/CLI (ya que estoy tratando de crear una biblioteca de referencia .NET), y he tenido el siguiente problema.Delegado de C++/CLI como puntero de función (System.AccessViolationException)

Defino un delegado en C++/CLI, y luego creo una instancia del delegado en C#, y luego invoco la instancia del delegado a través de un C++ no administrado mediante un puntero de función. Todo esto funciona como se esperaba.

Código para ilustrar esta (primera mi C#)

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); 
     } 
    } 
} 

Archivo siguiente mi manejado 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); 
     } 
    }; 
} 

Y mi no administrado C++ archivo (Unmanaged.cpp)

#include "Unmanaged.hpp" 

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

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

Este código funciona como se esperaba, y la salida es "1024", como el puntero de función llama al método() método de egate

Mi problema surge cuando trato de aplicar el mismo método con un delegado con argumentos, es decir: delegate void MessageDelegate (int number);

Mi código es ahora de la siguiente manera (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); 
     } 
    } 
} 

Mi archivo gestionado 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); 
     } 
    }; 
} 

Y mi no administrado archivo de 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); 
    } 
} 

cuando ejecuto el programa me sale el siguiente error:

Se produjo una excepción no controlada del tipo 'System.AccessViolationException' en la biblioteca no administrada Test.dll

Información adicional: Intentó leer o escribir en la memoria protegida. Esto a menudo es una indicación de que otra memoria está corrupta.

Dicho sea de paso, la ventana de la consola muestra 1024, pero luego aparece un int aleatorio (~ 1000000), y luego aparece el error.

Puedo comenzar a imaginar algunas de las razones por las que recibo este error, pero no estoy seguro y estoy teniendo dificultades para descubrirlo. Si alguien pudiera decirme por qué recibo este error y qué podría hacer para solucionarlo, lo agradecería mucho.

+0

+1 para una pregunta bien descrita –

Respuesta

11
void WriteMessage(void* Function, int number) 

Pasar punteros a la función como void * es una muy mala idea. Impide que el compilador compruebe que está haciendo algo mal. Hay algo mal, aunque el compilador no puede detectarlo en este caso específico. El delegado se clasifica como un puntero a función que utiliza la convención de llamada __stdcall, su puntero de función real usa la convención de llamada __cdecl, el valor predeterminado para el código nativo. Lo que hace que la pila se desequilibre en la llamada.

Puede solucionarlo aplicando [UnmanagedFunctionPointer] attribute a la declaración de delegado, especifique CallingConvention :: Cdecl.

+0

Muchas gracias, ¿por qué es que sin el argumento esto no es necesario? – xcvd

+4

Todavía se requiere, simplemente no hace ninguna diferencia ya que no se empuja nada en la pila. –

1

Un puntero de función creado a partir de un delegado es invisible para el recolector de elementos no utilizados y no se cuenta durante el análisis de accesibilidad.

De 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.

Si se recoge el delegado, el puntero de función queda colgando, y su programa se comporta mal. Una violación de acceso es uno de los resultados más probables, pero no la única posibilidad. Si la memoria que solía contener el trampolín nativo/administrado se reutiliza para algunos otros datos, la CPU podría tratar de interpretarlo como instrucciones, lo que podría significar casi cualquier cosa.

La solución es mantener al delegado al alcance, por ejemplo, a través de la clase C++/CLI gcroot, que es un envoltorio delgado alrededor de .NET GCHandle.

+0

No he podido resolver el problema usando GCHandle. Parece que incluso cuando el delegado está asignado con GCH y se produce el mismo error, entonces no creo que esto sea un problema con el recolector de basura. – xcvd

+0

También debería agregar que tan pronto como compilo Unmanaged.cpp con/cli el problema desaparezca – xcvd

Cuestiones relacionadas