Avatar billede mikkelbm Nybegynder
13. marts 2009 - 22:04 Der er 10 kommentarer og
1 løsning

C++ struct til C#

Hej,

Jeg har følgende struct fra C++:

typedef struct
{
    char                    Name[ TB_UNIT_NAME_MAX ];
    int                     SwitchAddress;
    int                     PlugNo;                                    // 0..5
    TTB_UNIT_TYPE UnitType;
    bool                    Online;
    bool                    OpenClosePending;                // Command sent to unit, but no confirmed yet.
    bool                    Open;

    union
    {
        struct
        {
            float                    EnergyKWh;          // Since last reset
            float                    LastResetEnergyKWh;    // Total energy until last reset
            TTB_DATE_TIME    LastResetDateTime;    // Timestamp of last reset
        } EnergyB;                                                    // Switch B2 & B6

        struct
        {
            float                    EnergyKWh;          // KWh (updated for each 0,1 KWh)
            int                        WaterUnits;
            int                        SequenceNo;                    // Incremented for each new payment (0..255)
            TTB_PAY_TYPE    PayType;
            unsigned long    CardSerialNumber;        // 32bit card serial number
            int                        CardID;
            int                        UnitID;
            float                    UserValue;
        } EnergyTCM;                                                // TCM+

        char                Buffer[ 40 ];                        // Reserve space for up to 40 bytes
    }                            u;
} TTB_UNIT_DATA;

Som jeg prøver at konvertere til C#. Jeg bøvler lidt med hvordan jeg skal få placeret Bufferen der er inde i union'en.

Jeg har følgende som dog giver en fejl:

[StructLayout(LayoutKind.Sequential, Pack = 2)]
public struct TBUnitData
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 31)]
    public sbyte[] UnitName;

    public int SwitchAddress;
    public int PlugNo;                                    // 0..5
    public TBUnitType UnitType;
    public bool Online;
    public bool OpenClosePending;                // Command sent to unit, but no confirmed yet.
    public bool Open;

    public Energy Energy;
}

[StructLayout(LayoutKind.Explicit, Pack = 2)]
public struct Energy
{
    [FieldOffset(0)]
    public EnergyB EnergyB;
    [FieldOffset(0)]
    public EnergyTCM EnergyTCM;
    [FieldOffset(0)]
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 40)]
    public sbyte[] Buffer;
}

[StructLayout(LayoutKind.Sequential, Pack = 2)]
public struct EnergyB
{
    public float EnergyKWh;          // Since last reset
    public float LastResetEnergyKWh;    // Total energy until last reset
    public TBDateTime LastResetDateTime;    // Timestamp of last reset
}                                                    // Switch B2 & B6

[StructLayout(LayoutKind.Sequential, Pack = 2)]
public struct EnergyTCM
{
    public float EnergyKWh;          // KWh (updated for each 0,1 KWh)
    public int WaterUnits;
    public int SequenceNo;                      // Incremented for each new payment (0..255)
    public TBPayType PayType;
    public uint CardSerialNumber;        // 32bit card serial number
    public int CardID;
    public int UnitID;
    public float UserValue;
}

Fejlen ligger i, at jeg har placeret Buffer'en inde i min Energy struct, men jeg kan ikke gennemskue hvor den ellers skal placeres.

Nogle gode bud?

På forhånd tak!
Avatar billede mikkelbm Nybegynder
13. marts 2009 - 22:07 #1
Jeg er derudover også lidt i tvivl om hvorvidt min Pack værdi er korrekt. C++ koden er kompileret med Double-word aligment, men jeg ved ikke om Pack=2 er det korrekte?
Avatar billede arne_v Ekspert
13. marts 2009 - 22:49 #2
DWORD alignment svarer til Pack=4
Avatar billede arne_v Ekspert
13. marts 2009 - 22:53 #3
Jeg antager at du skal kalde noget C++ med en sådan struct som enten input eller output.

Har du overvejet alterantiver måder:
* send data som byte array og brug BinaryReader/BinaryWriter til at processe i C#
* lave et lille stykke C++ som konverterer mellem en .NET struct og en native struct
?
Avatar billede mikkelbm Nybegynder
13. marts 2009 - 22:58 #4
Det er en struct jeg modtager via et callback, og det er ikke mig der udvikler C++ koden.
Avatar billede mikkelbm Nybegynder
13. marts 2009 - 23:01 #5
Jeg kan nok godt påvirke udviklingen af C++ komponenten, men han skal nok have en meget god grund til det :)
Avatar billede arne_v Ekspert
14. marts 2009 - 03:48 #6
Du behøver ikke ændre i hans C++ kode.

Du kan selv lave en C++ wrapper.

Eksempel følger.
Avatar billede arne_v Ekspert
14. marts 2009 - 03:54 #7
Vi siger at vi har en thirdparty.h:

#ifndef THIRDPARTY_H
#define THIRDPARTY_H

struct foobar
{
    int iv;
    int crap1;
    double xv;
    int crap2;
    char *sv;
};

typedef struct foobar *pfoobar;
typedef void (*callback)(pfoobar);

void test(callback cb);

#endif

plus en Win32 DLL og/eller en .lib og dem vil vi nu gerne bruge fra C#.

En måde er at strikke en forfærdelig struct sammen hvilket:
1) er besværligt
2) giver to stykker kildekode som skal vedligeholdes

Et alternativ er at lave egen C++ wrapper som blander managed og native kode.

Først laver vi lige en Data.cs med det vi egentligt gerne vil have:

namespace E
{
    public struct FooBar
    {
        public int iv;
        public double xv;
        public string sv;
    }                                         
    public delegate void CallBack(FooBar fb);
}

En .NET struct med kun de felter som vi faktisk skal brug og en delegate.

Den bygges til Data.dll.

Så laver vi en C++ wrapper Wrapper.cpp:

#using <mscorlib.dll>

using namespace System;

#using "Data.dll"

extern "C"
{
#include "thirdparty.h"
}

namespace E
{
    public ref class Wrapper
    {
        private:
            static CallBack^ cb;
        public:
            static void Real(pfoobar unmgfb);
            static void Passthrough(CallBack^ cb);
    };
    void RealReal(pfoobar unmgfb)
    {
        Wrapper::Real(unmgfb);
    }
    void Wrapper::Passthrough(CallBack^ cb)
    {
        Wrapper::cb = cb;
        test(RealReal);
    }
    void Wrapper::Real(pfoobar unmgfb)
    {
        FooBar mgfb;
        mgfb.iv = unmgfb->iv;
        mgfb.xv = unmgfb->xv;
        mgfb.sv = gcnew String(unmgfb->sv);
        cb(mgfb);
    }
}

Som bygges til Wrapper.dll.

Nu kan det hele testes med Test.cs:

using System;

namespace E
{
    public class Test
    {
        public static void Demo(FooBar fb)
        {
            Console.WriteLine(fb.iv);
            Console.WriteLine(fb.xv);
            Console.WriteLine(fb.sv);
        }
        public static void Main(string[] args)
        {
            Wrapper.Passthrough(Demo);
        }
    }
}
Avatar billede arne_v Ekspert
14. marts 2009 - 03:54 #8
Tricket er at Wrapper.cpp kan både bruge thirdparty.h til native og Data.dll til managed.
Avatar billede arne_v Ekspert
14. marts 2009 - 03:56 #9
Min Wrapper.cpp forudsætter at der er en .lib at linke med, men ændringerne er ikke store for kun at bruge en Win32 DLL.
Avatar billede mikkelbm Nybegynder
15. marts 2009 - 09:45 #10
Jeg har fået løst mit problem. Grunden til at den buffer er der, er for at reservere plads op til de 40 bytes. Jeg undlader den bare i min struct - fortæller via StructLayout at den fylder 40 bytes - og så kører det fint. (efter jeg har ændret til Pack = 4)

Arne, smid et svar. Du hjalp med alignment.

Jeg er ikke skarp i C++, så det vil være for stor en fejlkilde, hvis jeg skulle til at skrive en wrapper.
Avatar billede arne_v Ekspert
15. marts 2009 - 17:43 #11
ok
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
IT-kurser om Microsoft 365, sikkerhed, personlig vækst, udvikling, digital markedsføring, grafisk design, SAP og forretningsanalyse.

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester