2010-07-06 12 views
1

J'ai une méthode C++ plus qui génère une valeur. J'appelle cette méthode à partir d'une application C#.
La méthode C++ est comme ceci:char * en code managé?

extern "C" REGISTRATION_API char * generate(char dIn[],char dOut[]) 

La méthode generate retourne un tableau de caractères (sOut[]=returnvalue; return sOut;)

Maintenant, je vous appelle cette méthode de mon application C#:

[DllImport("mydll.dll")] 
static extern string generate(string sIn, string sOut); 

Comme vous pouvez le voir, le type de retour dans C# est string. Ce qui se passe, c'est que la valeur retournée dans C# n'est pas correcte et qu'elle est corrompue. (La valeur est correcte à l'intérieur de la méthode generate, mais chaque fois que je l'appelle de C# pour l'extraire, je reçois une valeur erronée.)

Est-il acceptable que ma méthode en C# a une valeur string de retour alors que dans C++ c'est un char*? S'il vous plaît partager vos commentaires, il est urgent, merci.

+1

Est-ce que 'char *' représente réellement une chaîne (ANSI/Unicode?) Ou une séquence d'octets? Si une chaîne, utilisez un StringBuilder, sinon un octet [] dans la déclaration 'static extern'. – dtb

Répondre

1

Cette méthode ne peut pas non plus être appelée en toute sécurité à partir d'une application C++ native. Il renvoie un pointeur vers un tableau sur le cadre de la pile. Tout appel à une autre fonction écrasera cette trame de pile et corrompra le tableau.

Cela fonctionne probablement par accident dans un programme C++ en ce moment car il n'y a pas d'appel de fonction après l'obtention de la valeur de retour de cette fonction. Ce genre de chance n'est plus disponible lorsque le marshaller P/Invoke se trouve entre les deux.

Vous devrez revoir la fonction C++. Vous devriez lui donner des arguments qui permettent à l'appelant de passer son propre tampon pour être rempli avec le résultat. Par exemple:

extern "C" void __stdcall generate(const char* input, char* output, int outputSize) 

Appelez ce dans le code C# en passant un StringBuilder pour l'argument de sortie, correctement initialisé avec une capacité suffisante pour recevoir la chaîne. L'argument outputSize garantit que la fonction C++ peut éviter l'écriture après la fin du tampon et corrompre le tas collecté par le garbage. Ne l'ignorez pas.

3

Malheureusement, char* peut être quelque peu ambigu. (Littéralement, c'est un pointeur vers un seul caractère, où vous pouvez ou ne pouvez pas obtenir d'autres caractères en déréférençant le pointeur comme un tableau.) Habituellement, cependant, c'est un pointeur vers une chaîne terminée par zéro d'octets simples dans Codage ANSI (ce que Microsoft appelle un LPStr). Par défaut, P/Invoke classe les instances de System.String en tant que type BStr, ce qui correspond à une chaîne Pascal Unicode (avec une longueur au début). Vous devrez utiliser le MarshalAs attribute pour spécifier explicitement comment vous souhaitez que les chaînes soient rassemblées entre le code managé et le code non managé.

Très probablement, vous aurez envie de le déclarer comme ceci:

[DllImport("mydll.dll")] 
[return: MarshalAs(UnmanagedType.LPStr)] 
static extern string generate(
    [MarshalAs(UnmanagedType.LPStr)] string sIn, 
    [MarshalAs(UnmanagedType.LPStr)] out string sOut); 

Cependant, votre signature de la fonction est source de confusion: est-il retour la chaîne, ou est-il définir une variable de sortie ? Vous devrez peut-être ajuster la déclaration P/Invoke pour répondre à vos besoins.

2

Est-il acceptable que ma méthode en C# a une valeur de retour de chaîne tandis que dans C++ il est un char *?

Cela dépend vraiment.

Si cette méthode C++ alloue la mémoire pour la chaîne et attend que l'appelant la libère, alors vous avez des problèmes ... gros problèmes. Vous pouvez essayer d'appeler Marshal.FreeHGlobal dessus, mais il n'y a aucune garantie que la méthode C++ a effectivement alloué la mémoire sur le tas global. La plupart des API renvoient le pointeur dans l'une de leurs méthodes pour libérer la mémoire.

Il est également possible que le char * pointe vers une mémoire qui n'a pas besoin d'être libérée. Dans ce cas, l'utilisation d'un string devrait être bien.

Vous pouvez demander à la déclaration C# de renvoyer un IntPtr, puis d'appeler l'une des méthodes Marhsal.PtrToStringXXX.