Intégration d'une DLL dans un programme c#

Résolu
gregcedepe Messages postés 6 Date d'inscription jeudi 11 octobre 2007 Statut Membre Dernière intervention 11 avril 2008 - 2 avril 2008 à 18:47
olibara Messages postés 666 Date d'inscription dimanche 16 décembre 2007 Statut Membre Dernière intervention 11 mars 2010 - 11 avril 2008 à 21:37
Bonjour,

J'apelle une dll à l'intérieur d'un programme c#.
Les fonctions les plus simples retournant un booléen fonctionnent bien et je récupère bien le booléen en retour.
Là où ça se complique c'est que une fonction retourne un pointeur. Lorsque la fonction me retourne le pointeur, j'ai un bug (accès à des mémoires protégées). J'ai un programme en C qui fonctionne avec cette fonction et mon programme en c# lui ne fonctionne pas.

Extrait du programme en C (qui fonctionne):
////////Déclaration dans un .h:
MOCRAPI_USAGE

bool ScanAndRecognize(
char *cData,
int nDataSz);

////////Programme dans un .c:

char cMrzData[1000];
memset(cMrzData, NULL,
sizeof(cMrzData));

if (ScanAndRecognize(cMrzData,
sizeof(cMrzData) - 1) = =
false)
PrintError();

else
{

// Print the result
cout <<
"Received recognition output:" << endl << endl;

for (
int i = 0 ; cMrzData[i] != 0 ; i++)

if (cMrzData[i] !=
'\r')
cout << cMrzData[i];

else
cout << endl;
}

Programme en c# (le programme compile mais renvoie une erreur lorsque la dll retourne le pointeur):

//Déclaration
[
DllImport

("mOcrApi_dbg.dll", CharSet
=CharSet
.Auto
)]
public unsafe static extern bool ScanAndRecognize(char *cData
, int nDataSz
);

//Appel
bool bool1
;
unsafe
{
char[] mrz_data = new char [100];
fixed (char* mrz = mrz_data)
//memset(cMrzData, NULL, sizeof(cMrzData));
bool1 = ScanAndRecognize (mrz, 99);
// Print the result
for (int i = 0 ; i<=100 ; i++)
{
//if (cMrzData[i] != '\r')
textBox1.Text = textBox1.Text + mrz_data[i];
}
}

Je pense que le problème est au niveau de l'interprétation que je fais du pointeur mrz

16 réponses

olibara Messages postés 666 Date d'inscription dimanche 16 décembre 2007 Statut Membre Dernière intervention 11 mars 2010 6
5 avril 2008 à 08:02
Il n'est pas non plus impossible que cette dll utilise un contexte mal défini et que le plantage ne vienne pas du tout de la manipulation du pointeur en entrée mais d'une autre opération interne
- ouverture ou ecriture de fichier ?
- acces a une ressource
- etc...
3
Lutinore Messages postés 3246 Date d'inscription lundi 25 avril 2005 Statut Membre Dernière intervention 27 octobre 2012 41
3 avril 2008 à 05:48
Salut,

- char en C = 1 octet
- char en C# = 2 octets
0
olibara Messages postés 666 Date d'inscription dimanche 16 décembre 2007 Statut Membre Dernière intervention 11 mars 2010 6
3 avril 2008 à 08:05
Bonjour,

Je viens de resoudre un problème plus complexe mais similaire
Il faut considerer que tout pointeur dans une DLL c doit etre passé en tant que IntPtr en cSharp
La conversion se fera apres

Et pour bien isoler le code manage csharp et l'environnement dll, j'emballe l'appel dll dans une fonction qui s'occupera de toute la gestion des conversion

Je ne peux pas deviner a qoui sert exactement lenDatasz, mais si ta fonction rend un char * sur un string terminé par un null char, a mon avis tu devrais essayer ceci

("mOcrApi_dbg.dll", CharSet= CharSet.Auto
)]
public unsafe static extern bool ScanAndRecognize(ref IntPtr cData, int nDataSz
);

textBox1.Text = Marshal.PtrToStringAnsi(cData);
0
gregcedepe Messages postés 6 Date d'inscription jeudi 11 octobre 2007 Statut Membre Dernière intervention 11 avril 2008
3 avril 2008 à 10:49
J'ai essayé d'intégrer IntPtr. Le programme compile mais la dll plante toujours au retour. J'ai toujours le message : "Tentative de lecture ou d'écriture de mémoire protégée. Cela indique souvent qu'une autre mémoire est endommagée."

Peut-être y a t-il une erreur au niveau de mon code :

//Déclarations :
public

IntPtr cData
;

[DllImport("mOcrApi_dbg.dll", CharSet
= CharSet
.Auto
)]
public unsafe static extern bool ScanAndRecognize(ref IntPtr cData
, int nDataSz
);

// Appel:
bool1
= ScanAndRecognize(ref cData
, 1000);
textBox1
.Text
= Marshal
.PtrToStringAnsi(cData
);
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
olibara Messages postés 666 Date d'inscription dimanche 16 décembre 2007 Statut Membre Dernière intervention 11 mars 2010 6
3 avril 2008 à 11:27
Salut

Que fait exactement ta DLL avec
char *cData
 int nDataSz

C'est elle qui alloue cData ?
Ou bien elle recopie dans cData un truc de la longueur que tu donne en nDataSz
0
gregcedepe Messages postés 6 Date d'inscription jeudi 11 octobre 2007 Statut Membre Dernière intervention 11 avril 2008
3 avril 2008 à 14:08
Vu le code en C qui fonctionne, je pense effectivement que la dll recopie dans cData un truc de la longueur nDataSz. Je pense aussi que ce n'est pas cette interprétation là que je fais dans mon programme c#.


Dans ce cas là, comment faire l'équivalent du memset en c#?
0
olibara Messages postés 666 Date d'inscription dimanche 16 décembre 2007 Statut Membre Dernière intervention 11 mars 2010 6
3 avril 2008 à 14:54
Dans ce genre de truc je travaille par essai erreur pour essaye de faire comprendre au compilo ce que je lui passe et ce qu'il doit me rendre



Et a vrai dire si c'est vraiment une recopie dans ta dll je comprends pas bien pourquoi le premier exemple que tu a donné ne marche pas. Essaye de reduire le size que tu lui donne!



Sinon j'ai vu un truc un peu plus sophistiqué que tu pourrais adapter, ici on passe un tableau de string mais tu pourrais te contenter d'un UnmanagedType.LPWStr



[DllImport("NativeCDll.dll", CharSet=CharSet.Unicode)]
externstaticint TakesArrayOfStrings([In][Out][MarshalAsAttribute(UnmanagedType.LPArray,
ArraySubType=UnmanagedType.LPWStr)]string[] str, refint size);

Dis moi ce que ca donne !
0
Lutinore Messages postés 3246 Date d'inscription lundi 25 avril 2005 Statut Membre Dernière intervention 27 octobre 2012 41
4 avril 2008 à 00:06
A croire que mon message précédent est invisible..

Passe lui déja le bon type, byte et pas char. Vu le code en C c'est pas forcément le seul problème, mais ce sera déja plus correct.

Si tu passe un pointeur :

type = byte* ( qui pointe sur un tableau de byte pas de char ).
charset = inutile

Si tu passe un tableau :

type = char[ ] pré-alloué
charset = Ansi ( marshalling de 2 bits vers 1 bit )

Passer un ref IntPtr qui pointe sur rien, ça revient à passer un double pointeur null, riens à voir ici..

Marrshaller une LPWStr unicode alors que le tableau attend un tableau de char ANSI, rien à voir ici non plus..

Le memset est automatique avec l'opérateur new en C#.
0
olibara Messages postés 666 Date d'inscription dimanche 16 décembre 2007 Statut Membre Dernière intervention 11 mars 2010 6
4 avril 2008 à 07:40
Salut Lutinore


Bien sur tu a raison sur certains points et j'ai bien dis qu'il fallait adapter le code que j'avais trouvé
C'est clair que si la DLL recopie elle meme un string sur le pointeur qu'on lui passe on ne peut pas lui passer un pointeur null

Mais dans cette assertion, le premier code montré n'a aucune raison de planter, que ce soit byte ou char ou unicode etc.. pratiquement elle recopie 99 byte dans un tableau de 100
Maintenant si c'est un problème de longueur  (pour une raison a determiner) gregcedepe peut jouer sur sa declaration et l'appel (d'ailleurs je vois qu'en c il dimensionne a 1000)

si par contre la dll chipote le pointeur passé l'exemple que j'ai montré permetrait de le voir.


Mail il peut aussi passer un byte **

bool bool1
;
unsafe
{
char[] mrz_data = new char [100];
fixed (char* mrz = mrz_data)
bool1 = ScanAndRecognize(mrz, 99);
0
gregcedepe Messages postés 6 Date d'inscription jeudi 11 octobre 2007 Statut Membre Dernière intervention 11 avril 2008
4 avril 2008 à 17:12
Merci pour vos réponses. Je pense que le problème est effectivement le traitement d'un char sur 1 octet par un programme qui attend un char à 2 octets. Est-ce que le fait d'utiliser CharSet= CharSet.Ansi
doit résoudre tout le problème. En effet, le programme suivant me retourne le même problème:

//Déclaration

[ DllImport
("mOcrApi_dbg.dll", CharSet
=CharSet
.Ansi
)]
public unsafe static extern bool ScanAndRecognize(char *cData
, int nDataSz
);

//Appel
bool bool1
;
unsafe
{
char[] mrz_data = new char [1000];
fixed (char* mrz = mrz_data)
//memset(cMrzData, NULL, sizeof(cMrzData));
bool1 = ScanAndRecognize (mrz, 999);
// Print the result
for (int i = 0 ; i<=100 ; i++)
{
//if (cMrzData[i] != '\r')
textBox1.Text = textBox1.Text + mrz_data[i];
}
}

J'ai aussi essayé de récupérer du type byte (1 octet):

bool bool1;

unsafe
{

byte[] mrz_data =
new
byte[1000];

fixed (
byte* mrz = mrz_data)

//memset(cMrzData, NULL, sizeof(cMrzData));

bool1 = ScanAndRecognize(mrz, 999);

// Print the result

for (
int i = 0; i <= 100; i++)
{

//if (cMrzData[i] != '\r')
//textBox1.Text = textBox1.Text + mrz_data[i];
}

J'ai toujours le message d'erreur "Tentative de lecture ou d'écriture de mémoire protégée. Cela indique souvent qu'une autre mémoire est endommagée."
0
olibara Messages postés 666 Date d'inscription dimanche 16 décembre 2007 Statut Membre Dernière intervention 11 mars 2010 6
4 avril 2008 à 17:46
Si tu fais

bool1 = ScanAndRecognize(mrz, 10);

(en gardant le dimensionnement a 1000)
Tu as toujours une erreur ?

Tu certain de l'exemple natif que tu a donné ?

Tu fais bien

bool1 = ScanAndRecognize(mrz, 999);

Dans le block fixed ?
0
Lutinore Messages postés 3246 Date d'inscription lundi 25 avril 2005 Statut Membre Dernière intervention 27 octobre 2012 41
5 avril 2008 à 06:46
Si ce genre de définition ne passe pas à mon avis le problème ne provient pas du marshaling :

[ DllImport( "mOcrApi_dbg.dll" ) ]
[ return: MarshalAs( UnmanagedType.I1 ) ]
private static extern bool ScanAndRecognize( [ In, Out ] byte[ ] cData, int nDataSz ); // Pre-alloué le tableau.

Recherche aussi dans le header C la déclaration de MOCRAPI_USAGE, c'est peut être pas une convention d'appel StdCall.
0
gregcedepe Messages postés 6 Date d'inscription jeudi 11 octobre 2007 Statut Membre Dernière intervention 11 avril 2008
7 avril 2008 à 11:32
Merci pour vos réponses. J'ai essayé le code de Lutinore:

[ DllImport( "mOcrApi_dbg.dll" ) ]
[ return: MarshalAs( UnmanagedType.I1 ) ]
private static extern bool ScanAndRecognize( [ In, Out ] byte[ ] cData, int nDataSz ); // Pre-alloué le tableau

Malheureusement j'ai toujours le même problème.

Le header du fichier cpp (autant pour moi il s'agit d'un cpp et non d'un c) est le suivant:



#include








#include



<string>

#include



<cstdlib>

#include



<fstream>

//---------------------------------------------------------------------------




#include






"mOcrApiIntf.h"

//---------------------------------------------------------------------------




using






namespace
std;


using



namespace
mOCR;


//---------------------------------------------------------------------------




fstream fDebug;

Quant au fichier .h, voici un extrait (j'ai enlevé les déclarations des fonctions qui n'ont rien à voir avec le problème) :


#ifndef



mocr_api_intf_h


#define
mocr_api_intf_h


//---------------------------------------------------------------------------




#include






<windows.h>

//---------------------------------------------------------------------------




#ifdef



MOCRAPI_EXPORTS


#define MOCRAPI_USAGE extern "C" __declspec(dllexport)

#else
#define
MOCRAPI_USAGE

extern



"C"



__declspec
(

dllimport
)


#endifnamespace


mOCR
{
      MOCRAPI_USAGE
bool ScanAndRecognize(
char *cData,
int nDataSz);
};
#endif

Si je fais :
bool1 = ScanAndRecognize(mrz, 10);
j'ai toujours le problème que ce soit avec 10 ou avec 999.

L'exemple natif que j'ai donné est bon à priori
0
olibara Messages postés 666 Date d'inscription dimanche 16 décembre 2007 Statut Membre Dernière intervention 11 mars 2010 6
7 avril 2008 à 12:15
C'est zarbi !!

Moi j'ai de plus en plus l'impression que cette fonction fait AUTRE chose qui engendre le problème : lecture de fichier, etc...

Je serais curieux de voir ce qui se passe si tu met la longueur a zero !
0
gregcedepe Messages postés 6 Date d'inscription jeudi 11 octobre 2007 Statut Membre Dernière intervention 11 avril 2008
11 avril 2008 à 18:31
Merci pour vos réponses.

J'ai trouvé mon erreur. Il fallait envoyer une commande Initialize() inclue dans l'API de mon composant avant de lui envoyer une demande de scan, comme pour le préparer à envoyer des données. Ce forum m'a cependant aidé pour ce qui est de la conversion du char 1 octet en char 2 octet : je passe par un type byte puis je fais un cast vers du char.

Merci à tous pour votre participation!
0
olibara Messages postés 666 Date d'inscription dimanche 16 décembre 2007 Statut Membre Dernière intervention 11 mars 2010 6
11 avril 2008 à 21:37
Super !
Je suis content que ca marche et que mon intuition n'était pas fausse !
Je connais bien ce type de bug et j'appelle ca le syndrome du réverbère : dans le noir on cherche la ou il y a un peu de lumière mais on néglige plus facilement l'ombre
Dans ton cas il etait evidement aisé de penser que le probleme venait de l'appel et de la maniere de passer les parametres mais moins évident de jeter un coup d'oeil su le contexte géneral !

Bon travail
0
Rejoignez-nous