Maison  >  Article  >  développement back-end  >  Explication détaillée de la solution pour C# appelant C++DLL pour transmettre un tableau de structure

Explication détaillée de la solution pour C# appelant C++DLL pour transmettre un tableau de structure

黄舟
黄舟original
2017-03-28 11:52:152085parcourir

Cet article présente principalement des informations pertinentes sur la solution ultime pour que C# appelle C++DLL pour transmettre le tableau de structure. Les amis dans le besoin peuvent se référer à

C# pour appeler C++DLL pour transmettre. structure La solution ultime pour les tableaux de corps

Lors du développement d'un projet, vous devez appeler une DLL encapsulée en C++. Les types courants correspondent généralement à C#. Utilisez simplement DllImport pour introduire la fonction à partir de la DLL. C'est tout. Mais lorsqu'une structure, un tableau de structures ou un pointeur de structure est transmis, vous constaterez qu'il n'y a pas de type correspondant en C#. Que faire à ce moment-là ? La première réaction est que C# définit également la structure et la transmet ensuite en paramètre. Cependant, lorsque nous définissons une structure et que nous voulons y transmettre des paramètres, une exception sera levée, ou la structure sera transmise, mais la valeur de retour n'est pas celle que nous voulons. Après le débogage suivi, nous. J'ai constaté que ces valeurs n'ont pas changé du tout, le code est le suivant.

[DllImport("workStation.dll")] 
    private static extern bool fetchInfos(Info[] infos); 
    public struct Info 
    { 
      public int OrderNO; 
 
      public byte[] UniqueCode; 
 
      public float CpuPercent;          
 
    }; 
    private void buttonTest_Click(object sender, EventArgs e) 
    { 
      try 
      { 
      Info[] infos=new Info[128]; 
        if (fetchInfos(infos)) 
        { 
          MessageBox.Show("Fail"); 
        } 
      else 
      { 
          string message = ""; 
          foreach (Info info in infos) 
          { 
            message += string.Format("OrderNO={0}\r\nUniqueCode={1}\r\nCpu={2}", 
                       info.OrderNO, 
                       Encoding.UTF8.GetString(info.UniqueCode), 
                       info.CpuPercent 
                       ); 
          } 
          MessageBox.Show(message); 
      } 
      } 
      catch (System.Exception ex) 
      { 
        MessageBox.Show(ex.Message); 
      } 
    }

Plus tard, après avoir recherché des informations, il y a eu un article mentionnant que C# est une mémoire gérée. Maintenant, le tableau de structure doit être transmis, qui est attribut mémoire non gérée, qui doit être transmis. être précisé avec un espace Marsh avant de passer. Modifiez donc la structure comme suit.

StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] 
   public struct Info 
   { 
     public int OrderNO; 
 
     [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] 
     public byte[] UniqueCode; 
 
     public float CpuPercent;          
 
   };

Cependant, après de telles améliorations, les résultats de course ne sont toujours pas idéaux et les valeurs sont soit fausses, soit inchangées. Quelle en est la raison ? Après avoir constamment recherché des informations, j'ai finalement trouvé un article qui mentionnait le transfert de structures. Certaines peuvent être effectuées comme ci-dessus, mais d'autres ne le peuvent pas, surtout lorsque le paramètre est un pointeur de structure ou un pointeur de tableau de structures en C++. être utilisé pour correspondre à l'endroit où C# est appelé, et le code suivant sera amélioré ultérieurement.

[DllImport("workStation.dll")] 
  private static extern bool fetchInfos(IntPtr infosIntPtr); 
  [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] 
  public struct Info 
  { 
    public int OrderNO; 
 
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] 
    public byte[] UniqueCode; 
 
    public float CpuPercent; 
 
  }; 
  private void buttonTest_Click(object sender, EventArgs e) 
  { 
    try 
    { 
      int workStationCount = 128; 
      int size = Marshal.SizeOf(typeof(Info)); 
      IntPtr infosIntptr = Marshal.AllocHGlobal(size * workStationCount); 
      Info[] infos = new Info[workStationCount]; 
      if (fetchInfos(infosIntptr)) 
      { 
        MessageBox.Show("Fail"); 
        return; 
      } 
      for (int inkIndex = 0; inkIndex < workStationCount; inkIndex++) 
      { 
        IntPtr ptr = (IntPtr)((UInt32)infosIntptr + inkIndex * size); 
        infos[inkIndex] = (Info)Marshal.PtrToStructure(ptr, typeof(Info)); 
      } 
 
      Marshal.FreeHGlobal(infosIntptr); 
 
      string message = ""; 
      foreach (Info info in infos) 
      { 
        message += string.Format("OrderNO={0}\r\nUniqueCode={1}\r\nCpu={2}", 
                    info.OrderNO, 
                    Encoding.UTF8.GetString(info.UniqueCode), 
                    info.CpuPercent 
                    ); 
      } 
      MessageBox.Show(message); 
 
    } 
    catch (System.Exception ex) 
    { 
      MessageBox.Show(ex.Message); 
    } 
  }

Il est à noter qu'à ce moment, l'interface a été modifiée en IntPtr. Grâce à la méthode ci-dessus, le tableau de structure est finalement transmis. Cependant, une chose à noter ici est que différents compilateurs ne seront pas sûrs de la taille de la structure. Par exemple, si la structure ci-dessus

n'est pas alignée en octets dans BCB, elle sera parfois plus grande. que la structure normale. La taille du corps est de 2 octets de plus. Parce que BCB utilise par défaut un tri sur 2 octets et VC utilise par défaut un tri sur 1 octet. Pour résoudre ce problème, ajoutez l'alignement des octets à la structure BCB ou ouvrez deux octets supplémentaires (s'il y en a plus) en C#. Le code d'alignement d'octets est le suivant.

#pragma pack(push,1) 
  struct Info 
{ 
  int OrderNO; 
       
  char UniqueCode[32]; 
 
  float CpuPercent; 
}; 
#pragma pack(pop)

Utilisez Marsh.AllocHGlobal pour ouvrir de l'espace mémoire pour les pointeurs de structure. Le but est de le convertir en mémoire non gérée. Donc, si Marsh.AllocHGlobal n'est pas utilisé, existe-t-il un autre moyen ?

En fait, qu'il s'agisse d'un pointeur ou d'un tableau en C++, il est finalement stocké en mémoire un par un. Autrement dit, il est finalement affiché sous la forme d'un one-. tableau d'octets dimensionnel, donc si nous ouvrons un tableau unidimensionnel de taille égale, est-ce que ça va ? La réponse est oui, la mise en œuvre est donnée ci-dessous.

[DllImport("workStation.dll")] 
    private static extern bool fetchInfos(IntPtr infosIntPtr); 
    [DllImport("workStation.dll")] 
    private static extern bool fetchInfos(byte[] infos); 
    [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] 
    public struct Info 
    { 
      public int OrderNO; 
 
      [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] 
      public byte[] UniqueCode; 
 
      public float CpuPercent; 
 
    }; 
 
   
    private void buttonTest_Click(object sender, EventArgs e) 
    { 
      try 
      { 
        int count = 128; 
        int size = Marshal.SizeOf(typeof(Info)); 
        byte[] inkInfosBytes = new byte[count * size];         
        if (fetchInfos(inkInfosBytes)) 
        { 
          MessageBox.Show("Fail"); 
          return; 
        } 
        Info[] infos = new Info[count]; 
        for (int inkIndex = 0; inkIndex < count; inkIndex++) 
        { 
          byte[] inkInfoBytes = new byte[size]; 
          Array.Copy(inkInfosBytes, inkIndex * size, inkInfoBytes, 0, size); 
          infos[inkIndex] = (Info)bytesToStruct(inkInfoBytes, typeof(Info)); 
        } 
 
        string message = ""; 
        foreach (Info info in infos) 
        { 
          message += string.Format("OrderNO={0}\r\nUniqueCode={1}\r\nCpu={2}", 
                      info.OrderNO, 
                      Encoding.UTF8.GetString(info.UniqueCode), 
                      info.CpuPercent 
                      ); 
        } 
        MessageBox.Show(message); 
 
      } 
      catch (System.Exception ex) 
      { 
        MessageBox.Show(ex.Message); 
      } 
    } 
 
    #region bytesToStruct 
    /// <summary> 
    /// Byte array to struct or classs. 
    /// </summary> 
    /// <param name=”bytes”>Byte array</param> 
    /// <param name=”type”>Struct type or class type. 
    /// Egg:class Human{...}; 
    /// Human human=new Human(); 
    /// Type type=human.GetType();</param> 
    /// <returns>Destination struct or class.</returns> 
    public static object bytesToStruct(byte[] bytes, Type type) 
    { 
 
      int size = Marshal.SizeOf(type);//Get size of the struct or class.      
      if (bytes.Length < size) 
      { 
        return null; 
      } 
      IntPtr structPtr = Marshal.AllocHGlobal(size);//Allocate memory space of the struct or class.  
      Marshal.Copy(bytes, 0, structPtr, size);//Copy byte array to the memory space. 
      object obj = Marshal.PtrToStructure(structPtr, type);//Convert memory space to destination struct or class.      
      Marshal.FreeHGlobal(structPtr);//Release memory space.   
      return obj; 
    } 
    #endregion

Lorsque vous ne savez vraiment pas comment transmettre des données, vous pouvez envisager de les transmettre sous forme de tableau d'octets (même un entier suffit, à condition qu'il ouvre 4 octets (sous 32 bits )), tant qu'il est ouvert. Correspondant à la longueur, après avoir obtenu les données, elles doivent être converties en données requises selon les règles de type. Généralement, l'objectif peut être atteint.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn