2011-08-14 10 views
5

Actualmente estoy trabajando en algún código C#/C++ que hace uso de invoke. En el lado de C++ no es un std :: vector lleno de punteros cada uno identificado por el índice del código C#, por ejemplo, una declaración de la función se vería así:¿Es seguro mantener los punteros de C++ en C#?

void SetName(char* name, int idx) 

Pero ahora estoy pensando, ya que' estoy trabajando con los punteros no podía enviado a C# la dirección del puntero en sí y luego en el código que podría hacer algo como esto:

void SetName(char*name, int ptr) 
{ 
    ((TypeName*)ptr)->name = name; 
} 

Obviamente eso es una versión rápida de lo que quiero llegar (y probablemente no lo hará compilar).

¿Se garantizaría que la dirección del puntero permanezca constante en C++ de modo que pueda almacenar su dirección de forma segura en C# o esto sería demasiado inestable o peligroso por alguna razón?

+4

Solo para aclarar: ¿Sabes que el código anterior se compilará en C# solo si está dentro de un bloque 'inseguro'? –

Respuesta

2

Sí, no hay problema. Las asignaciones de memoria nativa nunca se mueven, por lo que almacenar el puntero en un IntPtr en el lado C# está bien. Es necesario algún tipo de función pinvoked que devuelve este puntero, entonces

[DllImport("something.dll", CharSet = CharSet.Ansi)] 
void SetName(IntPtr vector, string name, int index); 

que se encuentra intencionalmente acerca de esta función de C++:

void SetName(std::vector<std::string>* vect, const char* name, int index) { 
    std::string copy = name; 
    (*vect)[index] = copy; 
} 

Nota el uso de nueva en el código C++, tiene que copia la cadena.El argumento pasado nombre apunta a un búfer asignado por el contador de referencias pinvoke y solo es válido para la duración del cuerpo de la función. Tu código original no puede funcionar. Si tiene la intención de devolver los punteros a los elementos vector <>, entonces sea muy cuidado. Un vector reasigna su matriz interna cuando agrega elementos. Dicho puntero devuelto se volverá inválido y se corromperá el montón cuando lo use más adelante. Lo mismo ocurre con una lista de C# <> pero sin el riesgo de punteros colgantes.

+2

Por cierto, ese uso de 'new' es totalmente incorrecto en C++. Simplemente debería ser 'vect [index] = name;'. Y ni siquiera estoy seguro de si fingir que un puntero es realmente una referencia es incluso seguro. – Puppy

+0

Reparado. Demostrar la copia de la cuerda fue bastante importante. Y sí, es seguro. –

+0

Eso no es realmente lo que definiría como * mejor *. Es más, has intercambiado un problema que el compilador te dirá que está mal para un problema que solo descubrirás cuando obtienes corrupción del montón porque los indicadores sin procesar son tremendamente inseguros y lo borraste por accidente o lo filtraste. Cualquiera que pueda leer C++ básico almacenará 'std :: vector ' y simplemente 'vect [index] = name;' y sabrá que constituye una copia. Al menos usa un puntero inteligente o algo así. Por favor. Poner un código malo así como ejemplo es enseñar a otras personas cosas equivocadas. – Puppy

1

Creo que es estable hasta que se comanda el código C++ y perfectamente enterado de lo que hace, y otros desarrolladores que trabajan en el mismo código saben sobre ese peligro también.

Por lo tanto, en mi opinión, no es una forma de arquitectura muy segura, y la evitaría tanto como pudiera.

Atentamente.

7

En C#, no necesita utilizar un puntero aquí, puede usar simplemente una cadena C# simple.

[DllImport(...)] 
extern void SetName(string name, int id); 

Esto funciona porque el comportamiento predeterminado de cadenas en p/invoke es utilizar MarshalAs(UnmanagedType.LPStr), que se convierte en un char al estilo de C *. Puede marcar cada argumento en la declaración de C# explícitamente si requiere alguna otra forma de organización, por ejemplo, [MarshalAs(UnmanagedType.LPWStr)], para una arg que usa una cadena de 2 bytes por carácter.

La única razón para usar punteros es si necesita retener el acceso a los datos apuntados después de llamar a la función. Incluso entonces, puede usar los parámetros out la mayor parte del tiempo.

Puede p/invocar básicamente cualquier cosa sin necesidad de punteros en absoluto (y por lo tanto sin requerir código inseguro, que requiere una ejecución privilegiada en algunos entornos).

1

El C# GC mueve cosas, pero el montón de C++ no se mueve cualquier cosa: un puntero a un objeto asignado es garantizada siga siendo válida hasta que se delete. La mejor arquitectura para esta situación es simplemente enviar el puntero a C# como IntPtr y luego llevarlo nuevamente a C++.

Definitivamente es una idea increíblemente mejor que el elenco de enteros increíblemente MALO, HORRIBLE que tienes que ir allí.

+0

C# GC no mueve cuerdas. – Grozz

+1

@Grozz - ciertamente lo hace, no todas las cadenas están internados. –