Présentation
Je tente d'utiliser P/Invoke pour enregistrer un struct de callbacks avec une dll native. Lorsque vous appelez une fonction qui appelle la DLL native invoquer les rappels, une exception AccessViolationException se produit. J'ai construit un "petit" cas de test qui démontre le comportement composé de 2 fichiers, native.cpp qui compile dans native.dll et clr.cs qui compile dans l'exécutable.Pourquoi est-ce marshaling struct des délégués de rappel peuvent causer une AccessViolationException
native.cpp
extern "C" {
typedef void (*returncb)(int i);
typedef struct _Callback {
int (*cb1)();
int (*cb2)(const char *str);
void (*cb3)(returncb cb, int i);
} Callback;
static Callback *cbStruct;
__declspec(dllexport) void set_callback(Callback *cb) {
cbStruct = cb;
std::cout << "Got callbacks: " << std::endl <<
"cb1: " << std::hex << cb->cb1 << std::endl <<
"cb2: " << std::hex << cb->cb2 << std::endl <<
"cb3: " << std::hex << cb->cb3 << std::endl;
}
void return_callback(int i) {
std::cout << "[Native] Callback from callback 3 with input: " << i << std::endl;
}
__declspec(dllexport) void exec_callbacks() {
std::cout << "[Native] Executing callback 1 at " << std::hex << cbStruct->cb1 << std::endl;
std::cout << "[Native] Result: " << cbStruct->cb1() << std::endl;
std::cout << "[Native] Executing callback 2 at " << std::hex << cbStruct->cb2 << std::endl;
std::cout << "[Native] Result: " << cbStruct->cb2("2") << std::endl;
std::cout << "[Native] Executing callback 3 with input 3 at " << std::hex << cbStruct->cb3 << std::endl;
cbStruct->cb3(return_callback, 3);
std::cout << "[Native] Executing callback 3 with input 4 at " << std::hex << cbStruct->cb3 << std::endl;
cbStruct->cb3(return_callback, 4);
}
}
clr.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace clr {
public delegate void returncb(Int32 i);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int cb1();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int cb2(string str);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void cb3(returncb cb, Int32 i);
[StructLayout(LayoutKind.Sequential)]
struct Callback {
[MarshalAs(UnmanagedType.FunctionPtr)]
public cb1 c_cb1;
[MarshalAs(UnmanagedType.FunctionPtr)]
public cb2 c_cb2;
[MarshalAs(UnmanagedType.FunctionPtr)]
public cb3 c_cb3;
}
class Program {
static int cb1Impl() {
Console.WriteLine("[Managed] callback 1");
return 1;
}
static int cb2Impl(string c) {
Console.WriteLine("[Managed] callback 2");
return int.Parse(c);
}
static void cb3Impl(returncb cb, Int32 i) {
Console.WriteLine("[Managed] callback 3");
Console.WriteLine("[Managed] Executing callback to native.");
cb(i);
}
[DllImport("native.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void set_callback(ref Callback cb);
[DllImport("native.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void exec_callbacks();
static void Main(string[] args) {
Callback cb;
cb.c_cb1 = new cb1(cb1Impl);
cb.c_cb2 = new cb2(cb2Impl);
cb.c_cb3 = new cb3(cb3Impl);
Console.WriteLine("Beginning test.");
Console.WriteLine("Sending callbacks: ");
Console.WriteLine("cb1: " + Marshal.GetFunctionPointerForDelegate(cb.c_cb1));
Console.WriteLine("cb2: " + Marshal.GetFunctionPointerForDelegate(cb.c_cb1));
Console.WriteLine("cb3: " + Marshal.GetFunctionPointerForDelegate(cb.c_cb1));
set_callback(ref cb);
exec_callbacks();
Console.ReadLine();
}
}
}
Résultat
Invoquer il en résulte exec_callbacks() lancer un AccessViolationException. cb1 est invoqué avec succès, mais cb2 ne l'est pas. En outre, le code natif montre qu'avant l'appel de cb2, son adresse a changé. Pourquoi cela se produit-il? Au meilleur de ma connaissance, aucun des délégués n'aurait dû être nommé. Comme information supplémentaire, marshalling une structure de IntPtr et en utilisant Marshal.GetFunctionPtrForDelegate fonctionne correctement (même pour cb3 qui obtient un ptr natif à invoquer), cependant, être capable de rassembler les délégués directement est plus logique/est plus lisible.
Si possible, je voudrais ne pas avoir à changer le code natif. Y at-il quelque chose qui se traduira par un pointeur vers struct? – Ryan
Oui, si vous changez la signature de set \ _callback pour prendre un IntPtr au lieu d'un callback ref. Ensuite, vous pouvez utiliser le type GCHandle pour obtenir un pointeur vers une copie encadrée de la structure et passer dans celui-ci. Libérez le GCHandle après l'exécution de exec \ _callbacks. –
Quelque chose ne se traduit pas correctement, maintenant aucun des callbacks ne semble être à l'emplacement correct, c'est-à-dire avec quelques changements cosmétiques, mon code montre que les deux gérés/non gérés sont d'accord avec la structure, mais ne sont pas d'accord sur l'emplacement des callbacks callbacks d'envoi: Struct: 291194 CB1: 340A1A CB2: 340B62 CB3: 340BDA callbacks Got: Struct: 00291194 CB1: 02838F34 CB2: 02838DD0 CB3: 00000000 [Native ] Exécution du rappel 1 à 02838F34 – Ryan