Dll non managé: Marshal type stdcall ?

Signaler
Messages postés
15
Date d'inscription
mercredi 14 février 2007
Statut
Membre
Dernière intervention
28 juin 2007
-
Messages postés
2676
Date d'inscription
vendredi 28 juin 2002
Statut
Membre
Dernière intervention
13 janvier 2016
-
Bonjour à tous
J'essaie d'utiliser dans mon appli les fonctions d'une dll qui a été développé en C++. L'une de ces fonction me pose un vrai problème puisque voici son prototype: 
   int fnSetCallBack(const char *ncpaIniFilePath , void (*pfnCallback)(char *));
Voila comment je fais:
   [DllImport
(
"ma.dll", EntryPoint =
"fnSetCallBack",CallingConvention=
CallingConvention.StdCall)]
   public

static
extern
int fnSetCallBack([
MarshalAs(
UnmanagedType.LPStr)]
string ncpaIniFilePath,
CallbackDelegate CallBack);

CallbackDelegate est un délégué que j'ai crée: public

delegate
void
CallbackDelegate(
string text);

ensuite afin d'appeler cette fonction pour pouvoir enregistrer les messages de retours:

CallbackDelegate
callback =
new
CallbackDelegate(SaveLog);

USB_Com.fnSetCallBack(IniFilePath, callback);

j'ai fait ça parce que je pense que pour chaque message reçu, le programme devrait passer ce message en argument à la fonction SaveLog.
Sauf que ceci ne marche pas ! Est ce que vous avez des idées sur d'où pourrait venir le problème?
Merci d'avance de votre aide.

20 réponses

Messages postés
3246
Date d'inscription
lundi 25 avril 2005
Statut
Modérateur
Dernière intervention
27 octobre 2012
39
Salut, pourquoi penses tu que le problème vient de stdcall, c'est la convention d'appel par défaut. As tu une erreur particulière ou une exception ? Dans un premier temps essaye de protéger ton délégué du GC, fait en sorte que ce ne soit pas une variable locale mais un champ d'une classe.
Messages postés
15
Date d'inscription
mercredi 14 février 2007
Statut
Membre
Dernière intervention
28 juin 2007

En effet, j'ai crée le délégué comme une instance de classe. Après je ne sais pas comment protéger le délégué du GC (c'est peut être une question bette mais c'est quoi le GC?). Sinon pour l'éxécution du programme ya pas d'erreur ni d'exeption. Il se pourrait aussi que la fonction c++ en elle même soit mal programmé dans la dll puisque je n ai pas accés au code source.
Voila, si tu connais un bon tuto qui parle de ce problème de protection de delegue, je suis preneur.
Merci bien
Messages postés
3246
Date d'inscription
lundi 25 avril 2005
Statut
Modérateur
Dernière intervention
27 octobre 2012
39
Le GC c'est le Garbage Collector, le "ramasse-miettes" en français..Tu as peut être un prolème d'encodage Ansi/Unicode essaye de jouer avec l'attricut CharSet de DllImport pour voir si ça vient de là.. C'est peut être même le paramètre IniFilePath qui pose un problème plus que le callback.
Messages postés
15
Date d'inscription
mercredi 14 février 2007
Statut
Membre
Dernière intervention
28 juin 2007

Pour IniFilePath , c est déclaré en const char *. j'ai essayé de le marcheller directement en string, puis en utilisant ([MarshalAs(UnmanagedType.LPStr)]string, et mtn j ai fait avec CharSet:
[
DllImport("ma.dll", CharSet CharSet.Auto, EntryPoint
"fnSetCallBack", CallingConvention =
CallingConvention.StdCall);

public
static
extern
int fnSetCallBack(
string ncpaIniFilePath,
CallbackDelegate CallBack);

mais pas de changement, j'ai toujours rien qui arrive à la fonction SaveLog. Normalement même si le IniFilePath est faut je devrais avoir une erreur qui reviens qui dit: Invalid Init File.
Messages postés
3246
Date d'inscription
lundi 25 avril 2005
Statut
Modérateur
Dernière intervention
27 octobre 2012
39
Je ne vois pas trop.. Quand je bug sur le marshalling pour comprendre ce qui se passe j'écris le prototype en unsafe avec des pointeurs pour coller le plus près possible avec le prototype en C. Là notamment faut peut être voir ce que ça donne avec un pointeur byte à la place des chaines.

Essaye un StringBuilder dans le délégué à la place du string.
Messages postés
15
Date d'inscription
mercredi 14 février 2007
Statut
Membre
Dernière intervention
28 juin 2007

J'ai essayé ça aussi mais rien de nouveau. Je pense enfin de compte que le problème vient de la dll elle meme ! Merci comme même pour votre aide .
Messages postés
3246
Date d'inscription
lundi 25 avril 2005
Statut
Modérateur
Dernière intervention
27 octobre 2012
39
J'ai oulié de préciser que la StringBuilder doit être initialisée avant d'être marshallée ce qui implique qu'on doit connaitre le nombre de caratères.

Si ça ne passe pas avec ce genre de définition unsafe c'est que ça vient vraiment de la lib :

[ DllImport( "ma.dll", EntryPoint = "fnSetCallBack" ) ]
private unsafe static extern int SetCallBack( sbyte* ncpaIniFilePath, Callback callBack );


private unsafe delegate void Callback( sbyte* text );


private unsafe void SaveLog( sbyte* text )
{
    string s = new string( text );
}
Messages postés
15
Date d'inscription
mercredi 14 février 2007
Statut
Membre
Dernière intervention
28 juin 2007

Merci pour ces précisions Lutinore.
J ai ajouté les unsafe et j'ai changé les stringbuilder en sbyte* dans le code et j'ai autorisé le code unsafe pour la compil, mais j ai une erreur : "Erreur 1 Les pointeurs et les mémoires tampons de taille fixe ne peuvent être utilisés que dans un contexte unsafe"
Pourtant l'option "autoriser du code unsafe" est selectionnée !!
PS: j'utilise VS.NET 2005
Messages postés
15
Date d'inscription
mercredi 14 février 2007
Statut
Membre
Dernière intervention
28 juin 2007

Désolé, autant pour moi! j'avais pas rajouté le mot clé unsafe pour la méthode SaveLog
Messages postés
15
Date d'inscription
mercredi 14 février 2007
Statut
Membre
Dernière intervention
28 juin 2007

Le problème mtn c'est comment declarer le inifilepath en sbyte* ?? j'ai tjrs cette erreur:"Erreur 1 Impossible de convertir implicitement le type 'string' en 'sbyte*" !!
Messages postés
3246
Date d'inscription
lundi 25 avril 2005
Statut
Modérateur
Dernière intervention
27 octobre 2012
39
Plusieurs soluces, notamment :

unsafe // Utile seulement pour SetCallBack
{
    // Allocation sur le tas non-managé.
    IntPtr hStr = Marshal.StringToHGlobalAnsi( str );


    // On l'utilise ..
    SetCallBack( ( sbyte* )hStr, cb );


    // puis on la delete.
    Marshal.FreeHGlobal( hStr );
    hStr = IntPtr.Zero;
}
Messages postés
2676
Date d'inscription
vendredi 28 juin 2002
Statut
Membre
Dernière intervention
13 janvier 2016
20
salut,

sans passer par des unsafes, ne serait-il pas possible de :
-> mettre une var de classe pour le délégué vers la méthode SaveLog statique...
-> passer ce var à ta setcallback...
-> passer un simple string en mettant charset.ansi pour ton ini...

je pense que si ta callback n'est pas appelée, c'est que le délégué a été pris par le GC ou peut être que savelog pas statique...ensuite dans savelog le paramètre string peut avoir un marshalas LPStr...

ShareVB
Messages postés
3246
Date d'inscription
lundi 25 avril 2005
Statut
Modérateur
Dernière intervention
27 octobre 2012
39
ShareVB, le code unsafe c'est pour essayer de comprendre ou est le problème, sinon les deux solutions que tu proposes, c'est-à -dire proteger le délégué du GC et utiliser CharSet c'est les 2 premières choses qu'on a tester. Par contre c'est tout à fait possible que la chaine de SaveLog ne soit pas correctement marshaller en LPStr, là avec le pointeur sbyte on va bien voir si on reçoit une chaine ANSI.
Messages postés
15
Date d'inscription
mercredi 14 février 2007
Statut
Membre
Dernière intervention
28 juin 2007

Bon , j'ai toujour spas de résultat même en mode unsafe. Dans la classe1, j ai déclaré le délégué:

public unsafe delegate void CallbackDelegate(sbyte* text);



[
DllImport("ma.dll", CharSet CharSet.Auto, EntryPoint "fnSetCallBack", CallingConvention = CallingConvention
.StdCall)]

public unsafe static extern int fnSetCallBack(sbyte* ncpaIniFilePath, CallbackDelegate CallBack);

ensuite, dans classe2, je crée le délégué dans une instance de cette classe pour le protéger du GC:






callback =
new classe1.CallbackDelegate


(SaveLog);

j'appele la fonction fnSetCallBack:





IntPtr
hStr = Marshal.StringToHGlobalAnsi(IniFilePath);
classe1.fnSetCallBack((sbyte*)hStr, callback);




et la méthode SaveLog:





private



unsafe
static
void SaveLog(sbyte* DataReceived)
{





   string hString = Marshal.PtrToStringAnsi((IntPtr)DataReceived);
   maclasse objet = maclasse.Instance;
   objet.OStream.WriteLine(hString);




}




Voila, et j ai toujours rien dans mon fichier log !! Est ce que maintenant je peux être sur la faute vien de la dll ?
Messages postés
3246
Date d'inscription
lundi 25 avril 2005
Statut
Modérateur
Dernière intervention
27 octobre 2012
39
La fonction SetCallback renvoi un int, j'imagine que c'est un code d'erreur.. non !? Comme la fonction semble manipuler un fichier, il y a peut être un problème d'acces/d'autorisation sur le fichier/répertoire..
Messages postés
15
Date d'inscription
mercredi 14 février 2007
Statut
Membre
Dernière intervention
28 juin 2007

En effet la fonction fnsetcallback retourne un code d'erreur. Ici elle me retourne 0, donc pas d'erreur. Pour ce qui est du fichier de log, j ai bien vérifier dès le depart que mon StreamWriter arrive bien a ouvrir le fichier en rajoutant: OStream.WriteLine(
"début test\n"); et ceci marche parfaitement.. Je vais continuer mes investigation et je vous tiendrais au courant dès que j'ai qque chose nouveau.
Encore merci pour votre aide !
Messages postés
2676
Date d'inscription
vendredi 28 juin 2002
Statut
Membre
Dernière intervention
13 janvier 2016
20
salut,

je viens de tester sur un projet fictif...et j'arrive à avoir la callback appelées...pour référence :
-> partie C :
typedef void (_stdcall *callfct)(char *s);
callfct c=0;
int __stdcall setcallback(const char *ini,callfct cb) {    c = cb;    return 1; }
void __stdcall call(){    c("azerty"); }
-> partie C# (appli console) :
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace ConsoleApplication1
{
    class Class1
    {
        public delegate void CallBackDelegate([MarshalAs(UnmanagedType.LPStr)]string s);

        [DllImport("testcallback.dll", CharSet=CharSet.Ansi, EntryPoint = "setcallback", CallingConvention = CallingConvention.StdCall)]
        public static extern int fnSetCallBack(string ncpaIniFilePath, CallBackDelegate CallBack);
        [DllImport("testcallback.dll", CharSet = CharSet.Ansi, EntryPoint = "call", CallingConvention = CallingConvention.StdCall)]
        public static extern void Call();

        public static CallBackDelegate c = new CallBackDelegate(SaveLog);

        public static void SaveLog([MarshalAs(UnmanagedType.LPStr)]string s)
        {
            Console.WriteLine(s);
        }

        public static void Init()
        {
            fnSetCallBack("qsqdqdsqsd", c);
            Call();
        }
    }
}

si ca peut t'aider...

ShareVB
Messages postés
15
Date d'inscription
mercredi 14 février 2007
Statut
Membre
Dernière intervention
28 juin 2007

Merci pour ton exemple ShareVB, j'ai essayé ta méthode avec [MarshalAs(UnmanagedType.LPStr)], ça devrais marcher normalement, mais toujours rien. ceci dit, est-il normal que que tu transforme les char* en string directement ?
Sinon, de mon coté j'ai mis mtn tout le code dans la même classe histoire d'avoir tout sous les yeux. Sauf que en exécutant cette fois , j'ai une exeption qui dit qu'il y a un essaie d'accès memoire à un endroit protégé, et ceci dès que mon programme appelle une fonction de la dll. Cependant la même fonction marchait lorsque je l'appelais de l'autre classe. j'ai tout revérifié ya pas d'erreur de syntax. Le prototype en C++  de cette fonction est



int fnInitStdioMedia( STDIOMEDIA enmaCharManagement ,STDIOMEDIA enmaFileManagement );
Or STDIOMEDIA est un type enum dans la dll, donc j'ai redéféni un enum dans ma classe avec les même éléments et et je l ai appelé:

[DllImport
(
"PCIORetarget.dll", EntryPoint =
"fnInitStdioMedia")]

public
static
extern
int fnInitStdioMedia(
STDIOMEDIA enmaCharManagement,
STDIOMEDIA enmaFileManagement);







public
enum
STDIOMEDIA
{
STDIO_UART0,
STDIO_UART1,
STDIO_UART2,
STDIO_USB_HS,
STDIO_USB_FS,
STDIO_XTI,
STDIO_Ethernet,
STDIO_MMC,
STDIO_NONE
};

fnInitStdioMedia(
STDIOMEDIA.STDIO_USB_HS,
STDIOMEDIA.STDIO_USB_HS);

Et la j ai l'exeption : Attempted to read or write protected memory

est ce que c est la correpondance entre les deux enum qui pose problème ?
Messages postés
3246
Date d'inscription
lundi 25 avril 2005
Statut
Modérateur
Dernière intervention
27 octobre 2012
39
Ce genre d'erreur "Attempted to read or write protected memory"
arrive souvent lorsque l'on se trompe de type dans le proptotype managé genre long à la place de int, donc un débordement en mémoire.. Mais là en C++ comme en C# les enums sont de type int donc ça ne semble pas être le problème.
Messages postés
2676
Date d'inscription
vendredi 28 juin 2002
Statut
Membre
Dernière intervention
13 janvier 2016
20
salut,

il y a deux cas pour char* :
-> soit c'est un const char* ou char* que l'on passe avec un contenu : string
-> soit c'est un buffer char* que l'on passe avec une taille définie : stringbuilder

sinon es-tu certain que toutes tes fonctions soient toutes stdcall, callback y compris ? (car dans les prototypes que tu donnes il n'y a pas de _stdcall). Je pense que ca vient de là ou alors du code c lui-même...

ShareVB