Check alle échte Black Friday-deals Ook zo moe van nepaanbiedingen? Wij laten alleen échte deals zien

[C#] unsafe fixed arrays

Pagina: 1
Acties:

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 18-11 13:07
Hoi,

Ik ben bezig met een programma dat moet communiceren met een spel. Het spel schrijft data naar een Memory Mapped File, welke ik met mijn C# programma uitlees:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
_mmf = MemoryMappedFile.OpenExisting("Local\\acpmf_physics");

using (var stream = _mmf.CreateViewStream())
{
    using (var reader = new BinaryReader(stream))
    {
        var size = Marshal.SizeOf(typeof(Physics));
        var bytes = reader.ReadBytes(size);
        var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);

        var data = (Physics)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(Physics));
        handle.Free();
        return data;
    }
}


De Physics struct is een struct die nauw samen hangt met de struct zoals hij in C++ gedefinieerd is, waar het op neer komt is dat ik moet instellen wat de 'packsize' is en dat ik alle arrays fixed-length moet maken:
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
    [StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Ansi)]
    public unsafe struct Physics
    {
        public int PacketId;
        public float Gas;
        public float Brake;
        public float Fuel;
        public int Gear;
        public int Rpms;
        public float SteerAngle;
        public float SpeedKmh;
        public fixed float Velocity[3];
        public fixed float AccG[3];
        public fixed float WheelSlip[4];
        public fixed float WheelLoad[4];
        public fixed float WheelsPressure[4];
        public fixed float WheelAngularSpeed[4];
        public fixed float TyreWear[4];
        public fixed float TyreDirtyLevel[4];
        public fixed float TyreCoreTemperature[4];
        public fixed float CamberRad[4];
        public fixed float SuspensionTravel[4];
        public fixed float CarDamage[5];
        public int NumberOfTyresOut;
    }


Om dit te doen moet ik de struct dus unsafe maken. Nu heb ik daar absoluut geen ervaring mee en hoe meer ik er over lees hoe minder ik dit wil doen... Blijkbaar moet ik nu zelf gaan memory management gaan doen en dat kan enorm mis gaan, zo niet kwetsbaar voor exploits zijn...?? Dit klinkt allemaal "beangstigend", is dat inderdaad zo of valt dat in de praktijk wel mee? Wat houdt dit voor mijn programma in, moet ik verder iets speciaals doen om hier mee om te gaan?

Als ik het hele unsafe en fixed gebeuren weg laat (en alle arrays als normale arrays definieer) gaat het mis (een error wat erop neerkomt dat hij beveiligde memory wil lezen), uiteraard omdat de grootte van de arrays nu niet bekend zijn en het mis gaat bij het overkopieren van de pointer. Ik weet geen andere manier om de lengte van de arrays aan te geven (is die er?).


Verder heb ik hier nog een probleem mee, mocht ik hiermee verder gaan. Hoe kan ik de waarden uit de arrays nu uitlezen? Ik ging er eigenlijk stiekem vanuit dat ik gewoon normale int[] en float[] arrays terug kreeg, maar helaas... ik krijg natuurlijk pointers terug. Daar heb ik dus geen kaas van gegeten, en ik moet dus de waarde van die pointer gaan opvragen - en daar heb ik dus weer unsafe code voor nodig.... Dat wil ik helemaal niet!

Kan ik hier op een of andere manier omheen of kan ik er niet onderuit om unsafe code te gaan gebruiken?

Mijn iRacing profiel


  • BasieP
  • Registratie: Oktober 2000
  • Laatst online: 19-10 08:18
unsafe is niet persee unsafe. Als jij netjes programmeert is het prima.
Je mist alleen de standaard controle die .NET voor je doet. Je moet het dus zelf doen.

Over memory management. Dat valt mee. Je bent namelijk geen creator van een object, je schrijft er niet in, je leest er alleen uit.
Je hoeft het dus niet op te ruimen.

over pointers is wel het e.e.a. te vinden.
't komt er kortgezegt op neer dat je met een * een pointer kunt volgen. Dus zet een * voor je variabele, en dan ga je het geheugenadres volgen en krijg je de waarde terug ipv de pointer naar de waarde.
zie ook:
http://www.tutorialspoint.com/csharp/csharp_unsafe_codes.htm

grappig dat als ik google naar 'c# pointers tutorial' ik als eerste hit iets krijg over 'unsafe programming'
https://www.google.nl/sea...spv=210&es_sm=93&ie=UTF-8

verder heb je in c# het unsafe statement, en de unsafe keyword.
zie: MSDN: unsafe (C# Reference)
Die had je vast al gezien

[ Voor 13% gewijzigd door BasieP op 09-12-2013 20:38 ]

This message was sent on 100% recyclable electrons.


  • NickThissen
  • Registratie: November 2007
  • Laatst online: 18-11 13:07
Ok, dat klinkt al wat beter, bedankt.


Over het uitlezen van de pointers heb ik dan wel nog een vraagje. Uit een vaag C++ verleden kon ik me nog herinneren dat dat met * ging inderdaad, maar in plaats van een array krijg ik dan een enkele waarde terug. Bijvoorbeeld de Velocity; dat zou een array van 3 floats moeten zijn, maar Intellisense zegt dat het een "float*" is, oftewel een pointer naar een enkele float? Ik kan daarvan dan de waarde ophalen met:
code:
1
2
            float* pointer = physics.Velocity;
            float value = *pointer;

maar het is geen float :S

Wat gaat hier mis? Ik heb deze code nog niet kunnen draaien (over een uurtje ongeveer pas weer tijd voor) maar ik zou niet weten wat ik hier uit moet verwachten..?

Mijn iRacing profiel


  • farlane
  • Registratie: Maart 2000
  • Laatst online: 13:12
Als die floats zicht in de struct bevinden is het geen "extern" array ( een array dat via een pointer gedereferenced moet worden omdat het ergens anders in het geheugen leeft ) en zou je ze volgens mij gewoon als array moeten kunnen indexeren.

Zie ook hier: MSDN: Fixed Size Buffers (C# Programming Guide)

[ Voor 21% gewijzigd door farlane op 09-12-2013 21:08 ]

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


  • BasieP
  • Registratie: Oktober 2000
  • Laatst online: 19-10 08:18
C#:
1
float* pointer = physics.Velocity;

Die regel gaat ook fout omdat je hem buiten het 'fixed' statement uitvoert.

zou dus iets moeten zijn als
C#:
1
2
3
4
5
fixed (float* pointer = physics.Velocity)
{
    //en hier kan je dan wat doen met je 'pointer'
    float value = *pointer;
}

This message was sent on 100% recyclable electrons.


  • NickThissen
  • Registratie: November 2007
  • Laatst online: 18-11 13:07
Dit is in een functie die unsafe aangeduidt is dus dat ging wel goed.

Maar het lijkt er inderdaad op dat ik helemaal niks met die pointer zooi hoef te doen, ik krijg ook een float als ik gewoon
code:
1
physics.Velocity[0]

aanroep.... Vraag me af waarom dat net een error gaf. Straks even proberen!

Mijn iRacing profiel


  • NickThissen
  • Registratie: November 2007
  • Laatst online: 18-11 13:07
Ok, het lijkt te werken...

Dit werkt niet:
C#:
1
2
3
            var vx = e.Physics.Velocity[0];
            var vy = e.Physics.Velocity[1];
            var vz = e.Physics.Velocity[2];

(Fixed size buffers can only be accessed through locals or fields)


Dit werkt wel:
C#:
1
2
3
4
5
            var physics = e.Physics;

            var vx = physics.Velocity[0];
            var vy = physics.Velocity[1];
            var vz = physics.Velocity[2];


Ik zou nog steeds graag zonder unsafe code werken maar het lijkt nu in ieder geval te werken, thanks!

Mijn iRacing profiel


  • farlane
  • Registratie: Maart 2000
  • Laatst online: 13:12
Je laat je bang maken door die .NET stemmingmakerij van VM's, JIT, unsafe en weet ik wat. Dit is zo rechtoerechtaan als je het krijgen kunt. Niet helemaal maargoed.

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


  • pedorus
  • Registratie: Januari 2008
  • Niet online
Sinds wanneer heb je unsafe code nodig voor wat in essentie neerkomt op het lezen van een binair bestand? Dat kan natuurlijk ook gewoon met de methodes van de BinaryReader (ReadInt32() / ReadFloat()). Zolang je niet het laatste beetje performance er perse uit wil persen.

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


  • NickThissen
  • Registratie: November 2007
  • Laatst online: 18-11 13:07
pedorus schreef op maandag 09 december 2013 @ 23:11:
Sinds wanneer heb je unsafe code nodig voor wat in essentie neerkomt op het lezen van een binair bestand? Dat kan natuurlijk ook gewoon met de methodes van de BinaryReader (ReadInt32() / ReadFloat()). Zolang je niet het laatste beetje performance er perse uit wil persen.
De reden voor unsafe code is dus dat ik de fixed arrays gebruik, zodat de grootte van de struct klopt. Is er een betere manier om de grootte van elke field aan te geven zodat ik niet fixed arrays hoef te gebruiken? Het enige wat ik kon vinden is elke field een FieldOffset attribuut te geven en dan handmatig te gaan lezen en die offset te gebruiken om te bepalen hoe veel bytes ik moet lezen. Maar dan moet ik dus elk veld handmatig gaan assignen lijkt me? En wat als de struct (in de mem mapped file) veranderd, dan moet ik dus niet alleen mijn struct aanpassen maar ook de code om hem in te lezen, dat lijkt me niet handig. Of zie ik nou iets over het hoofd?

Daarnaast is performance in dit geval wel belangrijk, deze file wordt in principe zo snel mogelijk uitgelezen (als men dat zou willen om zo snel mogelijke physics uit te lezen). Of dat in dit geval relevant is weet ik niet, dat zou ik moeten nameten, maar wie weet.

[ Voor 11% gewijzigd door NickThissen op 09-12-2013 23:54 ]

Mijn iRacing profiel


  • pedorus
  • Registratie: Januari 2008
  • Niet online
Het voordeel van 'elk veld' handmatig assignen is juist dat je eigen representatie los kan staan van hoe het geheugen wordt aangeleverd, en dat je in de rest van de code ook niet met vreemde unsafe arrays zit opgescheept. Qua snelheid kopieer je nu sowieso toch al de data (en nog traag ook), dus dat maakt al weinig uit. Anders had je bijvoorbeeld SafeMemoryMappedViewHandle.AcquirePointer moeten gebruiken. (Of wellicht MemoryMappedViewAccessor.Read<T> of zoiets voor een kopie. Of gewoon de boel in c++ of assembler maken.)

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


  • epic007
  • Registratie: Februari 2004
  • Laatst online: 17-11 15:31
Werkt deze constructie niet?
C#:
1
2
3
4
5
6
7
8
9
public struct Physics
{
    ...
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
    public float[] Velocity;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
    public float[] AccG;
    ...
}

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 18-11 13:07
pedorus schreef op dinsdag 10 december 2013 @ 00:45:
Het voordeel van 'elk veld' handmatig assignen is juist dat je eigen representatie los kan staan van hoe het geheugen wordt aangeleverd, en dat je in de rest van de code ook niet met vreemde unsafe arrays zit opgescheept. Qua snelheid kopieer je nu sowieso toch al de data (en nog traag ook), dus dat maakt al weinig uit. Anders had je bijvoorbeeld SafeMemoryMappedViewHandle.AcquirePointer moeten gebruiken. (Of wellicht MemoryMappedViewAccessor.Read<T> of zoiets voor een kopie. Of gewoon de boel in c++ of assembler maken.)
Ik begrijp misschien niet helemaal wat je bedoelt. Bedoel je dat ik zoiets moet gaan doen:

C#:
1
2
3
4
5
var physics = new Physics();
physics.PacketId = reader.ReadInt();
physics.Gas = reader.ReadSingle();
physics.Brake = reader.ReadSingle();
// etc...


Dat zou misschien wel werken, maar is dit echt nodig? Dat lijkt me enorm veel werk. Nu valt het in dit geval nog wel mee maar wat als je struct honderden fields heeft of nog meer. En wat als hij verandert? De game in kwestie is nog in 'beta' fase dus dit gaat waarschijnlijk elke twee weken veranderen, dan kan ik elke twee weken niet alleen de struct gaan aanpassen maar ook nog de code om het uit te lezen.

Verder; hoe zou ik een array uitlezen, werkt dat gewoon door drie keer een float te lezen en die handmatig in een array te stoppen?
epic007 schreef op dinsdag 10 december 2013 @ 08:54:
Werkt deze constructie niet?
C#:
1
2
3
4
5
6
7
8
9
public struct Physics
{
    ...
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
    public float[] Velocity;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
    public float[] AccG;
    ...
}
Ik zal het binnenkort eens proberen, dat zou wel handig zijn...

Mijn iRacing profiel


  • NickThissen
  • Registratie: November 2007
  • Laatst online: 18-11 13:07
epic007 schreef op dinsdag 10 december 2013 @ 08:54:
Werkt deze constructie niet?
C#:
1
2
3
4
5
6
7
8
9
public struct Physics
{
    ...
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
    public float[] Velocity;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
    public float[] AccG;
    ...
}
Bedankt, dat werkt inderdaad ook! Dat is veel gemakkelijker, nu hoef ik niks meer met unsafe code te doen.

Overigens kan ik hiermee ook snel strings lezen (in plaats van char arrays):
C#:
1
2
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
        public string CurrentTime;


Of dit nou de snelste manier is betwijfel ik. Ik zou het graag zo snel mogelijk willen doen maar het moet wel (1) in C# blijven en (2) een beetje begrijpelijk voor mezelf zijn, dus geen zooi met pointers wat ik niet begrijp. Momenteel ben ik tevreden met de snelheid, mocht het niet snel genoeg zijn dan kan ik nog altijd kijken hoe het beter kan.

Bedankt!

Mijn iRacing profiel

Pagina: 1