¿Devolviendo una cadena de PInvoke?

Estoy usando PInvoke para la interoperabilidad entre el código nativo (C ++) y el código administrado (C #). Solo escribo una función simple que obtiene una cadena del código de C ++. Mi código parece C # Code:

[DllImport("MyDll.dll")] private static extern string GetSomeText(); public static string GetAllValidProjects() { string s = GetSomeText(); return s; } 

Código C ++

 char* GetSomeText() { std::string stri= "Some Text Here"; char * pchr = (char *)stri.c_str(); return pchr; } 

Todo funciona bien en c ++ end, es decir, el pchr contians dice “Some Text Here”, pero en C # la cadena s contiene una nota. No sé lo que estoy haciendo mal. Cualquier ayuda sería apreciada

En primer lugar, como otros han señalado, su C ++ está roto incluso antes de intentar la interoperabilidad. Está devolviendo un puntero al stri de stri . Pero como stri se destruye tan pronto como la función retorna, el valor de retorno no es válido.

Lo que es más, incluso si lo solucionó, debe hacer más. No funcionará asignando memoria en su código C ++ que necesitaría el código C # para desasignar.

Hay algunas opciones para hacerlo bien.

Su código C # puede preguntarle al código C ++ cuánto tiempo dura la cadena. Luego se crea un C # StringBuilder y se asigna al tamaño apropiado. A continuación, el objeto StringBuilder se pasa al código C ++ y su clasificación predeterminada es como un LPWSTR. En este enfoque, el código C # asigna la cadena y su código C ++ recibe una cadena C a la que debe copiar el búfer.

Alternativamente, puede devolver un BSTR desde el C ++ que permite la asignación en el código C ++ nativo y la desasignación en el código C #.

El enfoque BSTR es probablemente cómo lo haría. Se parece a esto:

C ++

 #include  BSTR GetSomeText() { return ::SysAllocString(L"Greetings from the native world!"); } 

DO#

 [DllImport(@"test.dll", CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.BStr)] private static extern string GetSomeText(); 

Actualizar

Hans Passant agregó un par de observaciones útiles en los comentarios. En primer lugar, la mayor parte de la interoperabilidad de P / Invoke se realiza frente a una interfaz existente que no puede modificarse y usted no tiene el lujo de elegir su enfoque de interfaz de interoperabilidad preferido. Parece que no es el caso aquí, entonces, ¿qué enfoque debería elegirse?

La opción 1 es asignar el búfer en el código administrado, después de haber preguntado primero al código nativo cuánto espacio se necesita. Tal vez sea suficiente usar un buffer de tamaño fijo en el que ambas partes estén de acuerdo.

Donde se cae la opción 1 es cuando el assembly de la cadena es costoso y no desea hacerlo dos veces (por ejemplo, una vez para devolver su longitud, y una vez más para los contenidos). Aquí es donde la opción 2, el BSTR entra en juego.

Hans señaló un inconveniente del BSTR , a saber, que lleva una carga útil UTF-16, pero los datos de su fuente pueden ser char* , lo cual es “un poco molesto”.

Para superar la molestia, puede resumir la conversión de char* a BSTR siguiente manera:

 BSTR ANSItoBSTR(char* input) { BSTR result = NULL; int lenA = lstrlenA(input); int lenW = ::MultiByteToWideChar(CP_ACP, 0, input, lenA, NULL, 0); if (lenW > 0) { result = ::SysAllocStringLen(0, lenW); ::MultiByteToWideChar(CP_ACP, 0, input, lenA, result, lenW); } return result; } 

Esa es la más difícil, y ahora es fácil agregar otras envolturas para convertir a BSTR de LPWSTR , std::string , std::wrstring etc.

Es porque

 std::string stri= "Some Text Here"; 

es un objeto de stack y se destruye después de la llamada GetSomeText (). Después de la llamada, el puntero pchr que está devolviendo no es válido.

Es posible que necesite asignar espacio para el texto de forma dinámica para poder acceder a este último.
Cambie su definición de función C ++ a:

 char* GetSomeText() { std::string stri = "Some Text Here"; return strcpy(new char[stri.size()], stri.c_str()); } 

Algo como arriba. Tienes la idea …

Otra forma de obtener una cadena de C ++. En caso de que no puedas modificar tu c ++ dll. Puede declarar el DllImport con IntPtr en lugar de cadena. Cuando se invoca la función, puede ordenar la Ptr nuevamente a Cadena.

 [DllImport("MyDll.dll")] private static extern IntPtr GetSomeText(); public static string GetAllValidProjects() { string s = Marshal.PtrToStringAnsi(GetSomeText()); return s; } 

Nota: como se menciona en la publicación anterior. “Some Text Here” está asignado en la stack, por lo que tan pronto como la función vuelva, la stack se anulará. Por lo tanto, los datos pueden ser anulados. Por lo tanto, deberá usar Marshal.PtrToStringAnsi justo después de la llamada. No sujete el IntPtr.

 char* GetSomeText() { std::string stri= "Some Text Here"; char * pchr = (char *)stri.c_str(); return pchr; } 

GetSomeText () está retornando el puntero de char y no funcionará si usted declara la variable de cadena para almacenarlo.

tratar

 [DllImport("MyDll.dll")] private static extern string GetSomeText(); public static string GetAllValidProjects() { string s = new string(GetSomeText()); return s; } 

hay un constructor que toma char *