[.NET] Van unmanaged naar managed type?

Pagina: 1
Acties:

  • MrBucket
  • Registratie: Juli 2003
  • Laatst online: 29-10-2022
De titel is een beetje cryptisch, maar het komt erop neer dat ik probeer in managed C++ (.NET) een plugin te schrijven voor het programma MilkShape3D.

Nu is het zo dat een Milkshape plugin een Win32 DLL moet zijn dat de volgende functie moet implementeren:
C++:
1
cMsPlugIn *CreatePlugIn ();


waarbij cMsPlugIn (ongeveer) als volgt is gedefinieerd:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct msModel{
    int         nNumMeshes;
    int         nNumAllocedMeshes;
    //...en nog meer...
};

class cMsPlugIn
{
public:
    cMsPlugIn () {};
    virtual ~cMsPlugIn () {};

    virtual int             GetType () = 0;
    virtual const char *    GetTitle () = 0;
    virtual int             Execute (msModel* pModel) = 0;
};
Kortom, cMsPlugIn specificeert een abstract base class (het C++-equivalent van een interface) en CreatePlugIn() moet een implementatie retourneren van deze interface, zodat MilkShape3D de functies van mijn plugin kan aanroepen.

Nu, het lijkt me dat de class die cMsPlugin implementeert ook unmanaged moet zijn, omdat een managed class waarschijnlijk niet dezelfde vtable-layout heeft en zijn functies dan dus niet aangeroepen kunnen worden. Maar hoe maak ik dan de overstap van unmanaged naar managed code?

Als ik bijvoorbeeld de Execute()-methode wil implementeren:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
class cMsPluginImpl: cMsPlugIn {
    //...
};

__gc class ManagedModel{
public:
   int nNumMeshes; 
   int nNumAllocedMeshes; 
};

int cMsPluginImpl::Execute (msModel* pModel){
   //Hoe maak ik van pModel nu een instantie van ManagedModel?
}
Het probleem is dat ik in de declaraties van cMsPlugIn (en dus ook van cMsPlugInImpl) geen managed types mag gebruiken, en ik zie dan ook geen mogelijkheid om het [MarshalAs]-attribuut te gebruiken. Direct casten van een unmanaged naar een managed type kan ook niet, kortom, ik zit een beetje vast.

Wie kan me de goede richting ophelpen?

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 10-12-2025
Dat wordt dus kopieren. Dat is niet erg; het zijn maar twee ints.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • __fred__
  • Registratie: November 2001
  • Laatst online: 18:50
Handmatig marshallen via Marshal.PtrToStructure? let wel op alignment-problemen die ongetwijfeld optreden als je packed structures zou gebruiken.

[ Voor 50% gewijzigd door __fred__ op 26-08-2006 13:17 ]


  • MTWZZ
  • Registratie: Mei 2000
  • Laatst online: 13-08-2021

MTWZZ

One life, live it!

__fred__ schreef op zaterdag 26 augustus 2006 @ 13:15:
Handmatig marshallen via Marshal.PtrToStructure? let wel op alignment-problemen die ongetwijfeld optreden als je packed structures zou gebruiken.
Met __fred__ ^^
PtrToStructure is je vriend. Je kunt ook nog even kijken naar het StructLayout attribute en het FieldOffset attribute om de alignment/offsets te fixen.

Zie ook [.NET] CLR gebruiken vanuit Win32? (Inverse P/Invoke)

Nu met Land Rover Series 3 en Defender 90


  • MrBucket
  • Registratie: Juli 2003
  • Laatst online: 29-10-2022
MSalters schreef op zaterdag 26 augustus 2006 @ 12:56:
Dat wordt dus kopieren. Dat is niet erg; het zijn maar twee ints.
Hehe, niet helemaal. De volledige definitie is:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/* msModel */
typedef struct msModel
{
    int         nNumMeshes;
    int         nNumAllocedMeshes;
    msMesh*     pMeshes;

    int         nNumMaterials;
    int         nNumAllocedMaterials;
    msMaterial* pMaterials;

    int         nNumBones;
    int         nNumAllocedBones;
    msBone*     pBones;

    int         nFrame;
    int         nTotalFrames;

    msVec3      Position;         //msVec3 = float[3]
    msVec3      Rotation;

    msVec3      CameraPosition;
    msVec2      CameraRotationXY; //msVec2 = float[2]

    char*       pszComment;
} msModel;
__fred__ schreef op zaterdag 26 augustus 2006 @ 13:15:
Handmatig marshallen via Marshal.PtrToStructure? let wel op alignment-problemen die ongetwijfeld optreden als je packed structures zou gebruiken.
Wow, goeie tip van die Marshal class. Daar zitten veel handige methoden tussen :)

Maar ik begrijp dat ik een managed type moet maken met dezelfde layout als het unmanaged type dat gedecoreerd is met het StructLayoutAttribute, en dan kan ik Marshal.PtrToStructure gebruiken om de data vanuit het unmanaged type te laten kopieren naar het managed type?

Maar hoe zou ik bijv. de pMeshes member dan moeten representeren in het managed type? Ik kan er geen Array van maken lijkt me, want de CLR kan niet bepalen hoeveel elementen er in pMeshes zitten. Wordt dat dan een IntPtr waarde, die ik naderhand zelf moet omzetten naar een array?

@ MTWZZ: Raad eens wie de TS van dat topic is... :P

  • MTWZZ
  • Registratie: Mei 2000
  • Laatst online: 13-08-2021

MTWZZ

One life, live it!

pMeshes moet je dus ook definieeren aan je managed kant. Ik wet niet wat pMeshes voor een ding is (struct oid? lijkt me wel) maar een array is het niet anders had er denk ik wel dit gestaan:
C++:
1
msMesh** pMeshes;


Ik heb nog even gekeken naar hoe je een struct vanuit unmanaged geheugen omzet naar je managed equivalent:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public object Convert(byte[] rawData, int size, Type targetType)
{
    if(size != rawData.Length) {
        throw new InvalidOperationException("Size of targetType does not match the raw data");
    }

    IntPtr buff = Marshal.AllocHGlobal(size);

    Marshal.Copy(rawData, 0, buff, size);

    object retval = Marshal.PtrToStructure(buff, targetType);

    Marshal.FreeHGlobal(buff);

    return retval;
}

Dit is overigens wel een conversie van een struct uit een data-file dus je zult nog een beetje moeten tweaken maar ik denk dat je die IntPtr al hebt dus dat zou geen probleem moeten zijn.

Suc6 iig.

edit:

Owja let er nog even op dat je na de declaratie van pMeshes in je managed type bij de volgende member de goeie FieldOffset specificeert (sizeof(pMeshes)) zeg maar. Anders ga je problemen krijgen met door elkaar lopend geheugen :X

[ Voor 11% gewijzigd door MTWZZ op 26-08-2006 14:36 ]

Nu met Land Rover Series 3 en Defender 90


  • MrBucket
  • Registratie: Juli 2003
  • Laatst online: 29-10-2022
MTWZZ schreef op zaterdag 26 augustus 2006 @ 14:35:
pMeshes moet je dus ook definieeren aan je managed kant. Ik wet niet wat pMeshes voor een ding is (struct oid? lijkt me wel) maar een array is het niet anders had er denk ik wel dit gestaan:
C++:
1
msMesh** pMeshes;
Een msMesh is een struct, dus msMesh* pMeshes wijst naar een geheugenlocatie waar 1 of meerdere msMesh-instanties achter elkaar zijn geplaatst. Een array dus :)
Ik heb nog even gekeken naar hoe je een struct vanuit unmanaged geheugen omzet naar je managed equivalent:
(...)
Dank voor je code. Ik ga er eens mee aan de slag, kijken hoever ik kom :)

  • MrBucket
  • Registratie: Juli 2003
  • Laatst online: 29-10-2022
__fred__ schreef op zaterdag 26 augustus 2006 @ 13:15:
Handmatig marshallen via Marshal.PtrToStructure? let wel op alignment-problemen die ongetwijfeld optreden als je packed structures zou gebruiken.
Volgens mij ben ik nu zo'n alignment-probleem tegengekomen... ;(
C#:
1
2
3
4
5
6
7
8
9
10
11
12
[StructLayout(LayoutKind.Explicit)]
public class Mesh
{
    [FieldOffset(0)]
    public byte m_flags;

    //char szName[32];
    [FieldOffset(1), MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string m_name;

    //...
}

Als ik de corresponderende C-struct laat unmarshallen door Marshal.PtrToStructure(), dan krijg ik System.TypeLoadException met de melding "Could not load class MyPlugin.Mesh' because it contains an object field at offset 1 that is incorrectly aligned or overlapped by a non-object field"

Echter, als ik de m_flags member weglaat en m_name op offset 0 laat beginnen, dan unmarshalt 'ie 'm wel goed. Wat ook goed gaat is in plaats van de m_name member 4 ulongs vanaf offset 1 definieren, en deze d.m.v. bit shifts handmatig converteren naar een string.

Ik maak uit de foutmelding op dat alle data die je niet naar een value-type unmarshalt, op een dword boundary moet beginnen? Zo ja, wat is dan de beste manier om een dergelijke string te unmarshallen?

--edit--
Misschien handig om erbij te vermelden: ik heb gecontroleerd dat het om een packed struct gaat, d.w.z. dat de szName member echt op offset 1 begint.

[ Voor 5% gewijzigd door MrBucket op 28-08-2006 21:06 ]


  • __fred__
  • Registratie: November 2001
  • Laatst online: 18:50
let je ook even op, of de char array unicode of ansi is? zie System.Runtime.InteropServices.CharSet

Default (auto) gaat .NET al je strings unicode marshallen op een NT/XP/2003/Vista machine.

  • MTWZZ
  • Registratie: Mei 2000
  • Laatst online: 13-08-2021

MTWZZ

One life, live it!

Ik heb zelf ook al eens gemerkt dat Marshalling niet lekker gaat met array's. Voor mij was het makkelijk opgelost omdat het maar een array van 4 posities was (dus 4 afzonderlijke members).

Misschien (aangezien het een packed struct is) kun je nog iets met de Pack parameter van het StructLayout attribute:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
[StructLayout(LayoutKind.Explicit, Pack=4)] 
public class Mesh 
{ 
    [FieldOffset(0)] 
    public byte m_flags; 

    //char szName[32]; 
    [FieldOffset(1), MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] 
    public string m_name; 

    //... 
}

Nu met Land Rover Series 3 en Defender 90


Verwijderd

Misschien een domme opmerking, waarom schrijf je gewoon niet de hele dll in Unmanaged code?
Pagina: 1