[C#] Aanspreken methode in Win32/C DLL met char* parameter

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • mahi
  • Registratie: Juni 2001
  • Laatst online: 22-09-2020

mahi

God bless GoT

Topicstarter
Ik wil vanuit een C#-applicatie een methode aanspreken in een Win32/C DLL. De C-header definieert de methode als volgt:
C:
1
void FAR PASCAL getText(unsigned int handle, char far *buffer);

De documentatie bevat C++ voorbeeldcode voor die method:
C++:
1
2
static char buffer[64];
getText(handle, buffer);
"getText" neemt de char "buffer" van 64 posities als parameter. Na het uitvoeren van "getText" bevat "buffer" een ASCII tekst.

Dit kun je natuurlijk niet 1:1 overnemen in C# want die kent het char datatype niet (toch niet in dezelfde context). Nu heb ik na wat opzoekwerk drie werkende oplossingen gevonden, maar ik zou graag weten welke de beste oplossing is.

Methode 1:

char* bestaat niet in C#, maar voor zover mij bekend is sbyte[] het meest aanverwante type uit C#. Dus:

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[DllImport("api.dll", CharSet = CharSet.Ansi)]
public static extern void getText(IntPtr handle, sbyte[] buffer);

...

sbyte[] buffer = new sbyte[64];
getText(handle, buffer);

// Omzetting sbyte[] array naar byte[] array
byte[] bytes = new byte[buffer.Length];
Buffer.BlockCopy(buffer, 0, bytes, 0, buffer.Length);
// Omzetting byte[] naar string
System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
string text = encoding.GetString(bytes);


Het werkt trouwens ook met byte[] in plaats van sbyte[] en dat bespaart de omzetting van sbyte naar byte, maar ik weet niet of dat altijd goed zal gaan. Tenslotte is char (C) signed en byte (C#) unsigned. Als dit de beste oplossing is, weet iemand dan of het veilig is om byte[] te gebruiken in plaats van sbyte[]?

Methode 2:

In principe is de buffer parameter een string, maar hij moet een gedefinieerde capaciteit hebben en by reference gepasseerd worden. Met het string-type lukt dat niet, maar wel met StringBuilder:

C#:
1
2
3
4
5
6
7
8
9
10
[DllImport("api.dll", CharSet = CharSet.Ansi)]
public static extern void getText(IntPtr handle, StringBuilder buffer);

...

StringBuilder buffer = new StringBuilder(64);
getText(handle, buffer);

// Omzetting StringBuilder naar string
string text = buffer.ToString();


Een pak korter en leesbaarder, maar het voelt enigszins raar aan om hiervoor een StringBuilder object te gebruiken. Ik heb geen deftige tegenargumentatie - enkel een onderbuikgevoel...

Methode 3:

Als laatste methode doen we de marshalling zelf. De code lijkt op het eerste zicht wat complexer, maar het aanspreken van de methode is in principe wel eenvoudiger:

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[DllImport("api.dll", CharSet = CharSet.Ansi)]
public static extern void getText(IntPtr handle, IntPtr buffer);

public static string getText(IntPtr handle)
{
    var pointer = Marshal.AllocHGlobal(64);
    try
    {
        getText(handle, pointer);
        return Marshal.PtrToStringAnsi(pointer);
    }
    finally
    {
        Marshal.FreeHGlobal(pointer);
    }
    return null;
}

...

string text = getText(handle);


Alle drie deze methodes werken perfect voor mij, maar wat is nu de beste methode? Welke oplossing zou een professioneel programmeur kiezen? Of is er misschien een andere, betere oplossing die ik (nog) niet ken? Alvast bedankt voor alle advies!

A bus station is where a bus stops. A train station is where a train stops... On my desk I have a workstation.


Acties:
  • 0 Henk 'm!

  • mahi
  • Registratie: Juni 2001
  • Laatst online: 22-09-2020

mahi

God bless GoT

Topicstarter
Niemand?

A bus station is where a bus stops. A train station is where a train stops... On my desk I have a workstation.


Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 16-09 22:43
Als het werkt in jouw situatie zou ik voor methode 1 gaan, simpelweg om dat die de minste "ruis" heeft. Hierbij maakt het voor de aangeroepen functie trouwens niets uit of je signed of unsigned gebruikt bij de call, als de grootte maar klopt. ( Het maakt natuurlijk wel uit hoe jij de resultaten van die aangeroepen functie interpreteert )

De crux hier is , wie wil je opzadelen met de verantwoordelijkheid van het alloceren ( en opruimen ) van de buffer? Wie heeft er voldoende informatie om te bepalen dat die buffer 64 bytes groot moet zijn?

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.


Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Laatst online: 11:56
mahi schreef op woensdag 02 november 2011 @ 17:34:

Methode 2:

In principe is de buffer parameter een string, maar hij moet een gedefinieerde capaciteit hebben en by reference gepasseerd worden. Met het string-type lukt dat niet, maar wel met StringBuilder:

[code=c#][DllImport("api.dll", CharSet = CharSet.Ansi)]
public static extern void getText(IntPtr handle, StringBuilder buffer);
Die StringBuilder geef je ook niet door 'by reference'. Je geeft enkel de referentie naar die StringBuilder door, en dat is by value.

Werkt dit niet:
code:
1
public static extern void getText(IntPtr handle, ref string buffer);


of

code:
1
public static extern void getText(IntPtr handle, char[] buffer);



?

[ Voor 6% gewijzigd door whoami op 06-11-2011 18:44 ]

https://fgheysels.github.io/


Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
Lijkt me niet, hoe moet c# dan gaan raden hoe groot buffer moet zijn, en wie allocatie doet?

Ik zou persoonlijk gewoon voor de ingebouwde magie van de StringBuilder gaan, c# heeft die ingebouwde magie niet voor niks, en het is gewoon gedocumenteerd ;) Het enigste gekke aan deze functie is dat je nergens de capaciteit van je char* mee hoeft te geven (stringbuilder's Capacity), wat wel gebruikelijk is.

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

  • mahi
  • Registratie: Juni 2001
  • Laatst online: 22-09-2020

mahi

God bless GoT

Topicstarter
Eerst en vooral bedankt iedereen voor jullie antwoorden!
farlane schreef op zondag 06 november 2011 @ 10:48:Hierbij maakt het voor de aangeroepen functie trouwens niets uit of je signed of unsigned gebruikt bij de call, als de grootte maar klopt. ( Het maakt natuurlijk wel uit hoe jij de resultaten van die aangeroepen functie interpreteert )
Dat is interessant om te weten. Bedankt!
De crux hier is , wie wil je opzadelen met de verantwoordelijkheid van het alloceren ( en opruimen ) van de buffer? Wie heeft er voldoende informatie om te bepalen dat die buffer 64 bytes groot moet zijn?
Ik heb documentatie over de in- en uitvoervariabelen van alle methodes in de DLL. Ook de groottes van de buffers staan daarin gedocumenteerd. Dat zal ik dan wel per methode hardcoded moeten implementeren, maar dat is slechts een kleine moeite. De applicatie waarvan ik de DLL wil aanspreken wordt al zeker 10 jaar niet meer ontwikkeld, dus over veranderingen in de API hoef ik niet wakker te liggen.
whoami schreef op zondag 06 november 2011 @ 18:39:Werkt dit niet:
code:
1
public static extern void getText(IntPtr handle, ref string buffer);

of
code:
1
public static extern void getText(IntPtr handle, char[] buffer);
Nee (ik heb ze ook geprobeerd). In het eerste geval kun je geen vastgelegde bufferlengte definiëren en in het tweede geval zit je met het probleem dat het .NET char-type niet hetzelfde is als het C char-type (16-bit unicode vs 8 bit ASCII).
pedorus schreef op zondag 06 november 2011 @ 18:52:
Ik zou persoonlijk gewoon voor de ingebouwde magie van de StringBuilder gaan, c# heeft die ingebouwde magie niet voor niks, en het is gewoon gedocumenteerd ;)
Dit MSDN voorbeeld maakt de keuze natuurlijk wel makkelijker. Bedankt!
Het enigste gekke aan deze functie is dat je nergens de capaciteit van je char* mee hoeft te geven (stringbuilder's Capacity), wat wel gebruikelijk is.
Misschien begrijp ik je verkeerd, maar je moet toch wel degelijk de capaciteit van de StringBuilder opgeven. Wanneer ik in het voorbeeld StringBuilder(64) gebruik werkt het geheel normaal, geef ik gewoon StringBuilder() mee dan krijg ik de foutmelding "Warning: A StringBuilder buffer has been overflowed by unmanaged code. The process may become unstable. Insufficient capacity allocated to the StringBuilder before marshaling it.". Ook het MSDN voorbeeld gaat uit van een opgegeven capaciteit.

Hoe dan ook, ik ga verder werken met de StringBuilder-methode. Ze werkt, levert leesbare code op en wordt ook voorgesteld op MSDN. Wat meer kan ik wensen? :)

A bus station is where a bus stops. A train station is where a train stops... On my desk I have a workstation.

Pagina: 1