2012-11-29 5 views
9

Je pense que j'ai fondamentalement compris comment écrire des délégués C# pour les rappels, mais celui-ci me déroute. Le C++ définition est la suivante:Délégué C# pour le rappel C++

typedef int (__stdcall* Callback)(
long lCode, 
long lParamSize, 
void* pParam 
); 

et mon approche C# serait:

unsafe delegate int CallbackDelegate (int lCode, int lParamSize, IntPtr pParam); 

Bien que cela semble être incorrect, parce que je reçois une erreur PInvokeStackInbalance, ce qui signifie que ma définition du délégué est faux.

Le reste des paramètres de la fonction sont des chaînes ou des ints, ce qui signifie qu'ils ne peuvent pas causer l'erreur, et si je viens de passer un IntPtr.Zero au lieu du délégué (ce qui signifierait que je pointe vers un non- fonction de rappel existante) Je reçois une erreur AccessViolation, ce qui est également logique.

Qu'est-ce que je fais mal?

EDIT:

La fonction complète C++ est:

int 
__stdcall 
_Initialize (
const char*   FileName, 
Callback   cbFunction, 
int     Code, 
const char*   Name, 
unsigned int  Option, 
unsigned int  Option2 
); 

Ma version C# est:

[DllImport("MyDll.dll", CallingConvention = CallingConvention.StdCall)] 
public static extern int _Initialize (string FileName, CallbackDelegate cbFunction, int Code, string Name, uint Options, uint Options2); 

La fonction est (pour le test) vient d'appeler à l'intérieur de la routine principale une application de console:

static void Main(string[] args) 
{ 
    CallbackDelegate del = new CallbackDelegate(onCallback); 

    Console.Write(_Initialize("SomeFile.dat", del, 1000, "", 0, 4)); 
    Console.Read(); 
} 

onCallback est la suivante:

static int onCallback(int lCode, int lParamSize, IntPtr pParam) 
{ 
    return 0; 
} 

Je reçois l'erreur PInvokeStackInbalance sur la ligne où j'appelle _Initialize, si je passe un IntPtr.Zero au lieu du délégué et de modifier la définition de la fonction à IntPtr au lieu de CallbackDelegate puis Je reçois un AccessViolationException.

+0

'Le reste des paramètres de la fonction sont cordes ou ints'. Ne cachez pas les informations. –

+1

Avez-vous essayé de ne pas utiliser dangereux? i.e: délégué int CallbackDelegate (int lCode, int lParamSize, IntPtr pParam); – DougEC

+0

@HansPassant Je n'essaie pas de cacher des informations, j'essayais de laisser de côté des choses que je considérais comme non pertinentes. Je vais modifier la question et ajouter les informations. – Valandur

Répondre

3

Un .NET long est 64 bits. Un C++ long peut être juste 32 bits. Vérifiez avec le compilateur C++ qui a compilé cette définition quant à la taille de long.

+0

Malheureusement, cela ne semble pas résoudre le problème. Je ne peux pas vérifier le compilateur C++, car je n'ai que le fichier .dll et le fichier .h, mais changer le long en Int32 n'aide pas. – Valandur

3
[UnmanagedFunctionPointerAttribute(CallingConvention.StdCall)] 
unsafe delegate int CallbackDelegate (int lCode, int lParamSize, IntPtr pParam); 

Si .NET utilise cdecl à la place de stdcall, votre pile sera très certainement arrosée.

4

J'ai ajouté votre code à mon projet actuel où je fais beaucoup d'interopérabilité C#/C++ dans VS2012. Et même si je déteste utiliser le "ça a marché sur ma machine", ça a bien marché pour moi. Le code que j'ai couru est indiqué ci-dessous juste pour illustrer que je n'ai pas fait et les changements fondamentaux.

Ma suggestion est que vous construisez une nouvelle DLL natif avec une fonction stub pour _Initialize comme ci-dessous et voir si cela fonctionne lorsque vous pouvez contrôler les deux côtés de l'interface. Si cela fonctionne mais que la vraie DLL ne le fait pas, elle se résume soit aux paramètres du compilateur du côté natif, soit à un bug du côté natif et à un empilement sur la pile.

extern "C" { 

    typedef int (__stdcall* Callback)(long lCode,long lParamSize,void* pParam); 

    TRADITIONALDLL_API int __stdcall _Initialize (const char* FileName,Callback cbFunction, int     Code, 
           const char*   Name,unsigned int  Option,unsigned int  Option2) 
    { 
     cbFunction(0, 0, nullptr); 
     return 0; 
    } 
} 

Du côté C#, j'ai ajouté les déclarations à ma classe d'interface:

public delegate int CallbackDelegate(int lCode, int lParamSize, IntPtr pParam); 

[DllImport("XXX.dll", CallingConvention = CallingConvention.StdCall)] 
public static extern int _Initialize(string FileName, CallbackDelegate cbFunction, int Code, string Name, uint Options, uint Options2); 

Et puis dans mon principal:

private int onCallback(int lCode, int lParamSize, IntPtr pParam) 
{ 
     return 0; 
} 
XXXInterface.CallbackDelegate del = new XXXInterface.CallbackDelegate(onCallback); 

Console.Write(XXXInterface._Initialize("SomeFile.dat", del, 1000, "", 0, 4)); 
+0

Merci de vérifier cela, aurait dû le faire moi-même. Je l'ai essayé aussi, et cela semble fonctionner. Ce qui a également été découvert, c'est que l'application semble fonctionner si je viens de démarrer le fichier exe, SANS le débogueur ... – Valandur

+0

C'est étrange. Il semble qu'il y a une bonne chance qu'il y ait un bogue dans la DLL côté C++ qui affecte la pile. Est-il possible pour vous d'obtenir la source pour la DLL native afin que vous puissiez les exécuter combinés dans le débogueur? Je ne connais pas d'autre bonne façon de l'aborder et il n'y a presque certainement aucun moyen de le FIXER avec la source. –

+0

Non, malheureusement, je ne peux pas mettre la main sur le code source, sinon je pense que ce serait beaucoup plus facile. J'ai essayé d'appeler la fonction C++ à partir d'un programme C++, et cela semble fonctionner parfaitement. Donc, je pense que le dll va bien, et je fais juste une erreur avec la fonction de rappel. Merci pour l'aide en tout cas. – Valandur