C# Language
Interopérabilité
Recherche…
Remarques
Travailler avec l'API Win32 en utilisant C #
Windows expose de nombreuses fonctionnalités sous la forme d'une API Win32. En utilisant ces API, vous pouvez effectuer des opérations directes dans Windows, ce qui améliore les performances de votre application.Source Cliquez ici
Windows expose une large gamme d'API. Pour obtenir des informations sur les différentes API, vous pouvez consulter des sites tels que pinvoke .
Fonction d'importation à partir d'une DLL C ++ non gérée
Voici un exemple d'importation d'une fonction définie dans une DLL C ++ non gérée. Dans le code source C ++ pour "myDLL.dll", la fonction add
est définie:
extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
{
return a + b;
}
Ensuite, il peut être inclus dans un programme C # comme suit:
class Program
{
// This line will import the C++ method.
// The name specified in the DllImport attribute must be the DLL name.
// The names of parameters are unimportant, but the types must be correct.
[DllImport("myDLL.dll")]
private static extern int add(int left, int right);
static void Main(string[] args)
{
//The extern method can be called just as any other C# method.
Console.WriteLine(add(1, 2));
}
}
Reportez-vous à la section Conventions d'appel et nom C ++ pour savoir pourquoi extern "C"
et __stdcall
sont nécessaires.
Trouver la bibliothèque dynamique
Lorsque la méthode extern est invoquée pour la première fois, le programme C # recherche et charge la DLL appropriée. Pour plus d'informations sur l'emplacement de recherche de la DLL et sur la manière dont vous pouvez influencer les emplacements de recherche, consultez cette question sur le stackoverflow .
Code simple pour exposer la classe pour com
using System;
using System.Runtime.InteropServices;
namespace ComLibrary
{
[ComVisible(true)]
public interface IMainType
{
int GetInt();
void StartTime();
int StopTime();
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class MainType : IMainType
{
private Stopwatch stopWatch;
public int GetInt()
{
return 0;
}
public void StartTime()
{
stopWatch= new Stopwatch();
stopWatch.Start();
}
public int StopTime()
{
return (int)stopWatch.ElapsedMilliseconds;
}
}
}
C ++ nom mangling
Les compilateurs C ++ codent des informations supplémentaires dans les noms des fonctions exportées, telles que les types d'argument, pour créer des surcharges avec différents arguments possibles. Ce processus s'appelle le mangling name . Cela pose des problèmes avec l'importation de fonctions dans C # (et l'interopérabilité avec d'autres langages en général), le nom de la fonction int add(int a, int b)
n'étant plus add
, cela peut être ?add@@YAHHH@Z
, _add@8
ou autre chose, en fonction du compilateur et de la convention d'appel.
Il y a plusieurs façons de résoudre le problème de la manipulation de noms:
Exporter des fonctions en utilisant
extern "C"
pour basculer vers la liaison externe C qui utilise la gestion du nom C:extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
[DllImport("myDLL.dll")]
Le nom de la fonction sera toujours
_add@8
(_add@8
), mais le nom deStdCall
+extern "C"
est reconnu par le compilateur C #.Spécification des noms de fonction exportés dans le fichier de définition de module
myDLL.def
:EXPORTS add
int __stdcall add(int a, int b)
[DllImport("myDLL.dll")]
Le nom de la fonction sera pur
add
dans ce cas.Importer un nom mutilé. Vous aurez besoin d'un visualiseur de DLL pour voir le nom tronqué, vous pourrez alors le spécifier explicitement:
__declspec(dllexport) int __stdcall add(int a, int b)
[DllImport("myDLL.dll", EntryPoint = "?add@@YGHHH@Z")]
Conventions d'appel
Il existe plusieurs conventions d'appel de fonctions, spécifiant qui (appelant ou appelé) affiche les arguments de la pile, comment les arguments sont transmis et dans quel ordre. C ++ utilise la convention d'appel Cdecl
par défaut, mais C # attend StdCall
, qui est généralement utilisé par l'API Windows. Vous devez changer l'un ou l'autre:
Changer la convention d'
StdCall
enStdCall
en C ++:extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
[DllImport("myDLL.dll")]
Ou, changez la convention d'
Cdecl
enCdecl
en C #:extern "C" __declspec(dllexport) int /*__cdecl*/ add(int a, int b)
[DllImport("myDLL.dll", CallingConvention = CallingConvention.Cdecl)]
Si vous souhaitez utiliser une fonction avec la convention d'appel Cdecl
et un nom tronqué, votre code ressemblera à ceci:
__declspec(dllexport) int add(int a, int b)
[DllImport("myDLL.dll", CallingConvention = CallingConvention.Cdecl,
EntryPoint = "?add@@YAHHH@Z")]
thiscall ( __thiscall ) est principalement utilisé dans les fonctions membres d'une classe.
Lorsqu'une fonction utilise thiscall ( __thiscall ), un pointeur vers la classe est transmis comme premier paramètre.
Chargement et déchargement dynamiques de DLL non gérées
Lorsque vous utilisez l'attribut DllImport
vous devez connaître le nom de la DLL et de la méthode au moment de la compilation . Si vous souhaitez être plus flexible et décider au moment de l' exécution quelles DLL et méthodes charger, vous pouvez utiliser les méthodes de l'API Windows LoadLibrary()
, GetProcAddress()
et FreeLibrary()
. Cela peut être utile si la bibliothèque à utiliser dépend des conditions d'exécution.
Le pointeur renvoyé par GetProcAddress()
peut être converti en un délégué à l'aide de Marshal.GetDelegateForFunctionPointer()
.
L'exemple de code suivant illustre cela avec le myDLL.dll
des exemples précédents:
class Program
{
// import necessary API as shown in other examples
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr LoadLibrary(string lib);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern void FreeLibrary(IntPtr module);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GetProcAddress(IntPtr module, string proc);
// declare a delegate with the required signature
private delegate int AddDelegate(int a, int b);
private static void Main()
{
// load the dll
IntPtr module = LoadLibrary("myDLL.dll");
if (module == IntPtr.Zero) // error handling
{
Console.WriteLine($"Could not load library: {Marshal.GetLastWin32Error()}");
return;
}
// get a "pointer" to the method
IntPtr method = GetProcAddress(module, "add");
if (method == IntPtr.Zero) // error handling
{
Console.WriteLine($"Could not load method: {Marshal.GetLastWin32Error()}");
FreeLibrary(module); // unload library
return;
}
// convert "pointer" to delegate
AddDelegate add = (AddDelegate)Marshal.GetDelegateForFunctionPointer(method, typeof(AddDelegate));
// use function
int result = add(750, 300);
// unload library
FreeLibrary(module);
}
}
Traitement des erreurs Win32
Lorsque vous utilisez des méthodes d'interopérabilité, vous pouvez utiliser l'API GetLastError pour obtenir des informations supplémentaires sur vos appels d'API.
Attribut DllImport Attribut SetLastError
SetLastError = true
Indique que l'appelé appellera SetLastError (fonction API Win32).
SetLastError = false
Indique que l'appelé n'appellera pas SetLastError (fonction API Win32), vous ne recevrez donc pas d'informations d'erreur.
Lorsque SetLastError n'est pas défini, il est défini sur false (valeur par défaut).
Vous pouvez obtenir le code d'erreur à l'aide de la méthode Marshal.GetLastWin32Error:
Exemple:
[DllImport("kernel32.dll", SetLastError=true)]
public static extern IntPtr OpenMutex(uint access, bool handle, string lpName);
Si vous essayez d'ouvrir mutex qui n'existe pas, GetLastError retournera ERROR_FILE_NOT_FOUND .
var lastErrorCode = Marshal.GetLastWin32Error();
if (lastErrorCode == (uint)ERROR_FILE_NOT_FOUND)
{
//Deal with error
}
Les codes d'erreur du système peuvent être trouvés ici:
https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx
API GetLastError
Il existe une API GetLastError native que vous pouvez également utiliser:
[DllImport("coredll.dll", SetLastError=true)]
static extern Int32 GetLastError();
- Lorsque vous appelez l'API Win32 à partir du code managé, vous devez toujours utiliser l' erreur Marshal.GetLastWin32Error .
Voici pourquoi:
Entre votre appel Win32 qui définit l'erreur (appelle SetLastError), le CLR peut également appeler d'autres appels Win32 pouvant appeler SetLastError , ce comportement peut remplacer votre valeur d'erreur. Dans ce scénario, si vous appelez GetLastError, vous pouvez obtenir une erreur non valide.
Si vous définissez SetLastError = true , le CLR récupère le code d'erreur avant d'exécuter d'autres appels Win32.
Objet épinglé
GC (Garbage Collector) est responsable du nettoyage de nos déchets.
Alors que GC nettoie nos déchets, il supprime les objets inutilisés du tas géré, ce qui provoque une fragmentation du tas. Lorsque le GC est terminé avec la suppression, il effectue une compression de tas (défragmintation) qui consiste à déplacer des objets sur le tas.
Comme GC n'est pas déterministe, lors du passage d'une référence / d'un pointeur d'objet géré à un code natif, GC peut intervenir à tout moment, s'il survient juste après l'appel Inerop. être déplacé sur le tas géré - par conséquent, nous obtenons une référence non valide du côté géré.
Dans ce scénario, vous devez épingler l'objet avant de le transmettre au code natif.
Objet épinglé
L'objet épinglé est un objet qui n'est pas autorisé à se déplacer par GC.
Poignée épinglée Gc
Vous pouvez créer un objet pin en utilisant la méthode Gc.Alloc
GCHandle handle = GCHandle.Alloc(yourObject, GCHandleType.Pinned);
- L'obtention d'un objet GCHandle épinglé à un objet géré marque un objet spécifique comme un objet qui ne peut pas être déplacé par GC , jusqu'à ce que la poignée soit libérée.
Exemple:
[DllImport("kernel32.dll", SetLastError = true)]
public static extern void EnterCriticalSection(IntPtr ptr);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern void LeaveCriticalSection(IntPtr ptr);
public void EnterCriticalSection(CRITICAL_SECTION section)
{
try
{
GCHandle handle = GCHandle.Alloc(section, GCHandleType.Pinned);
EnterCriticalSection(handle.AddrOfPinnedObject());
//Do Some Critical Work
LeaveCriticalSection(handle.AddrOfPinnedObject());
}
finaly
{
handle.Free()
}
}
Précautions
- Lorsque vous épinglez (surtout les plus gros), l'objet essaye de libérer le GcHandle épinglé aussi rapidement que possible, car il interrompt la défragmentation du tas.
- Si vous oubliez de libérer GcHandle, rien ne se passera. Faites-le dans une section de code sécurisée (telle que finaly)
Structures de lecture avec le maréchal
La classe Marshal contient une fonction nommée PtrToStructure , cette fonction nous donne la possibilité de lire des structures par un pointeur non géré.
La fonction PtrToStructure a beaucoup de surcharges, mais toutes ont la même intention.
PtrToStructure générique:
public static T PtrToStructure<T>(IntPtr ptr);
T -type de structure.
ptr - Un pointeur sur un bloc de mémoire non géré.
Exemple:
NATIVE_STRUCT result = Marshal.PtrToStructure<NATIVE_STRUCT>(ptr);
- Si vous traitez des objets gérés en lisant des structures natives, n'oubliez pas d'épingler votre objet :)
T Read<T>(byte[] buffer)
{
T result = default(T);
var gch = GCHandle.Alloc(buffer, GCHandleType.Pinned);
try
{
result = Marshal.PtrToStructure<T>(gch.AddrOfPinnedObject());
}
finally
{
gch.Free();
}
return result;
}