[VB.NET] C++ DLL aanroepen vanuit VB

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • klein is fijn
  • Registratie: Mei 2005
  • Laatst online: 07-01-2022
Goedemorgen,

Ik probeer een stukje code werkende te krijgen wat gebruikt maakt van een DLL. De functies zijn gelukkig gedocumenteert. Het probleem is echter dat het C++ functies zijn, en ik ze in VB.NET aan wil roepen. Dat lukt aardig, maar ik loop vast op wat types variabelen.

C:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int HLP_GetNumDevices ()
//Use this function to request the number of connected photometers.

//Parameters:   none
//Return value:     number of connected photometers

WORD HLP_GetDeviceNames (LPSTR *DevNames, WORD Max_Len, WORD Max_Num)
//Use this function to get the name list of the instruments.

//Parameters:
//DevNames  pointer to an array of char pointers
//Max_Len   Length of strings, should be set to 256
//Max_Num   Number of string pointers in the array
//Return value:     number of actually committed names

DWORD HLP_OpenDevName (LPSTR DevName)
//A connection to a specific instrument will be established. You can communicate only with one instrument simultaneously.

//Parameters:
//DevName   pointer to a null terminated char, the name of one of the instruments connected to the computer
//Return value: error code, on success 0


In een goede poging om dat naar VB.NET om te zetten ben ik op dit gekomen:
Visual Basic .NET:
1
2
3
4
5
6
7
8
  <DllImport("UsbMeasLib.dll")> Public Shared Function HLP_GetNumDevices() As Integer
  End Function

  <DllImport("UsbMeasLib.dll")> Public Shared Function HLP_GetDeviceNames(ByRef DevNames As String, ByVal Max_Len As Integer, ByVal Max_Num As Integer) As Integer
  End Function

  <DllImport("UsbMeasLib.dll")> Public Shared Function HLP_OpenDevName(ByRef DevName As String) As Integer
  End Function

De eerste werkt opzich prima. De tweede al minder, en de derde helemaal niet. Het probleem zit in de data types die ik met de functies meegeef. Ik heb geen idee hoe ik een pointer naar een string array moet meegeven op een manier dat die DLL het begrijpt. En de WORD variabele komt terug in complete onzin, ook bitwise gezien. Waarschijnlijk omdat de WORD variablen die ik meestuur ook als complete onzin aankomen.

De vergelijking die ik tot nu toe heb:
int = Integer
LPSTR = String
LPSTR * = pointer naar string array = ByRef StringArray() -> geeft memory corruption?
WORD = 4 byte signed of unsiged var = Integer / UInteger -> beide geven onzin terug
DWORD = 8 byte signed of unsiged var = Long / ULong?

Wie kan me helpen dit lijstje aan te vullen?

Acties:
  • 0 Henk 'm!

  • Mijzelf
  • Registratie: September 2004
  • Niet online
LPSTR = pointer naar char (1 byte) array
LPSTR * = pointer naar pointer naar char (1 byte) array
WORD = unsigned 2 bytes
DWORD = unsigned 4 bytes
int = signed 4 bytes

Acties:
  • 0 Henk 'm!

  • MLM
  • Registratie: Juli 2004
  • Laatst online: 12-03-2023

MLM

aka Zolo

LPSTR is een pointer naar een char (1 byte, ANSI)
.NET String is een wrapper om een array van .NET Char (2 byte, UTF16LE)

Je wilt denk ik sowieso een [MarshalAs(UnmanagedType.LPStr] attribuut wilt gebruiken op de string (C# syntax, in VB.NET is het volgens mij met <> ipv [], ik gebruik nooit VB.NET, sorry) bij de derde functie.

De 2e functie is tricky, want de functie verwacht een array van pointers (array-grootte Max_Num), waarbij elke pointer in de array verwijst naar een array van chars (1 byte, ANSI, array-grootte Max_Len).

In C# zou je eventueel gewoon een byte array kunnen maken, en met iets als fixed(byte *mem = arr) kunnen spelen om dat te laten werken, maar in VB.NET kan dat niet (geen pointers), ik denk niet dat je die functie dus kan callen vanuit VB.NET :) (disclaimer, ben geen VB.NET guru)

Je zou een C++/CLR wrapper DLL kunnen bouwen, maar dat is ook niet supermakkelijk als je niet handig bent met C++ :)

[ Voor 6% gewijzigd door MLM op 19-09-2011 12:15 ]

-niks-


Acties:
  • 0 Henk 'm!

  • klein is fijn
  • Registratie: Mei 2005
  • Laatst online: 07-01-2022
Mijzelf schreef op maandag 19 september 2011 @ 11:53:
WORD = unsigned 2 bytes
DWORD = unsigned 4 bytes
Ah, hiermee wordt een WORD dus een UShort, en een DWORD een UInteger. De LPSTR kan ik zonder problemen rechstreeks naar een String vertalen. Dat lijkt goed te werken, ik krijg zinnige data terug als ik een device probeer te openen, en de foutmeldingen maken ook nog sense.
MLM schreef op maandag 19 september 2011 @ 12:13:
Je wilt denk ik sowieso een [MarshalAs(UnmanagedType.LPStr] attribuut wilt gebruiken op de string (C# syntax, in VB.NET is het volgens mij met <> ipv [], ik gebruik nooit VB.NET, sorry) bij de derde functie.
Die ga ik even opzoeken..
De 2e functie is tricky, want de functie verwacht een array van pointers (array-grootte Max_Num), waarbij elke pointer in de array verwijst naar een array van chars (1 byte, ANSI, array-grootte Max_Len).
Dat blijkt dus minder tricky dan verwacht, er hangt altijd maar 1 apparaat aan de PC. Daarmee is het aantal elementen in de array 1, en het geeft geen problemen als ik daar gewoon een normale string voor gebruik. Indien er meer dan 1 apparaat is aangesloten kan ik dat eerder al afvangen, er hoort namelijk maar 1 apparaat aan de PC te hangen.
Je zou een C++/CLR wrapper DLL kunnen bouwen, maar dat is ook niet supermakkelijk als je niet handig bent met C++ :)
Heh, dat lijkt me inderdaad een slecht plan. :)

Acties:
  • 0 Henk 'm!

  • klein is fijn
  • Registratie: Mei 2005
  • Laatst online: 07-01-2022
De eerste en derde functie werken ondertussen probleemloos. Het lijstje tot nu toe:
int = Integer
WORD = UShort
DWORD = UInteger

De tweede functie echter nog niet echt. In princiepe werkt het wel als ik een string als ByRef meegeef, maar het geeft wel de helft van de tijd memory errors. Niet echt geweldig dus.

Ik heb verschillende manieren geprobeert voor een 'pointer to an array of char pointers', waaronder array's van chars, array doorsturen, alleen eerste waarde doorsturen, ByRef, ByVal, etc. Ook de marshalling attributen willen niet baten.

Acties:
  • 0 Henk 'm!

  • RobIII
  • Registratie: December 2001
  • Niet online

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

(overleden)
klein is fijn schreef op dinsdag 20 september 2011 @ 14:54:
De tweede functie echter nog niet echt. In princiepe werkt het wel als ik een string als ByRef meegeef, maar het geeft wel de helft van de tijd memory errors. Niet echt geweldig dus.
Die string; is die wel al 'gevuld' met spaties (of sterretjes of whatever) voordat je die API call doet? Want die API zal er, vermoedelijk althans, van uit gaan dat er ruimte gereserveerd is. Je passed namelijk feitelijk een pointer naar (het begin van) die string; als het geheugen vanaf dat adres toevallig niet in gebruik is zal 't wel goed gaan; in alle andere gevallen ga je god-knows-what overschrijven (en dan is een "memory error" nog beschaafd :P ).

tl;dr: Als je de string (bijv.) eens vult met 255 spaties ofzo voordat je 'm passed, werkt 't dan?
Dan krijg je dus zoiets:
Visual Basic .NET:
1
2
3
Dim MyString e As String = New String(" "c, 255)

API_CALL(ByRef MyString, ..., ...)


/edit: Oh, wacht, de string parameter is geen "return parameter", wel?

[ Voor 16% gewijzigd door RobIII op 20-09-2011 15:09 ]

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


  • __fred__
  • Registratie: November 2001
  • Laatst online: 08:43
C:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int HLP_GetNumDevices ()
//Use this function to request the number of connected photometers.

//Parameters:   none
//Return value:     number of connected photometers

WORD HLP_GetDeviceNames (LPSTR *DevNames, WORD Max_Len, WORD Max_Num)
//Use this function to get the name list of the instruments.

//Parameters:
//DevNames  pointer to an array of char pointers
//Max_Len   Length of strings, should be set to 256
//Max_Num   Number of string pointers in the array
//Return value:     number of actually committed names

DWORD HLP_OpenDevName (LPSTR DevName)
//A connection to a specific instrument will be established. You can communicate only with one instrument simultaneously.

//Parameters:
//DevName   pointer to a null terminated char, the name of one of the instruments connected to the computer
//Return value: error code, on success 0


De eerste functie HLP_GetNumDevices gebruik je om het aantal devices uit te lezen. De LPSTR * in de tweede functie HLP_GetDeviceNames is een pointer naar een array met strings. Dat wordt dus een string array in VB.NET.

Reserveer dus een string array met aantal devices * lengte per string. Dit geef je vervolgens door in HLP_GetDeviceNames als eerste argument.

C#:
1
2
3
4
5
6
String[] s = new string[numDevices];

for(int i = 0; i < s.Length; i++)
{
    s[ i ] = new string(new char[255]);
}


in argument twee geef je de lengte per string op, argument 3 is het aantal strings dat je hebt gereserveerd (return waarde van functie 1). HLP_GetDeviceNames retourneert het aantal daadwerkelijk weggeschreven devices, indien correct aangeroepen.

Als attribuut (sorry c#) voor argument 1 van HLP_GetDeviceNames zal je dan ongeveer op zoiets uitkomen.

C#:
1
[MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStr)] string[]

[ Voor 4% gewijzigd door __fred__ op 21-09-2011 14:47 . Reden: typo ]


  • klein is fijn
  • Registratie: Mei 2005
  • Laatst online: 07-01-2022
RobIII schreef op dinsdag 20 september 2011 @ 15:06:
Die string; is die wel al 'gevuld' met spaties (of sterretjes of whatever) voordat je die API call doet? Want die API zal er, vermoedelijk althans, van uit gaan dat er ruimte gereserveerd is. Je passed namelijk feitelijk een pointer naar (het begin van) die string; als het geheugen vanaf dat adres toevallig niet in gebruik is zal 't wel goed gaan; in alle andere gevallen ga je god-knows-what overschrijven (en dan is een "memory error" nog beschaafd :P ).
Heh, das waar ja. Ik stuur gewoon een lege string.
Na het vrijmaken van wat geheugen geeft de functie inderdaad geen fouten meer.
Visual Basic .NET:
1
2
3
4
5
6
7
    Dim NumberOfDevices = HLP_GetNumDevices() ' get the number of devices
    If (NumberOfDevices <> 1) Then GoTo DeviceDetectionError ' if more or les then 1 device found, goto errorhandling

    Dim DeviceName As String = Space(255)
    Call HLP_GetDeviceNames(DeviceName, 255, NumberOfDevices) ' get devicenames

    HLP_ShowError(HLP_OpenDevName(DeviceName)) ' open connection to the first device

Het meesturen van een array geeft echter nog wel memory errors, zelfs na het vullen met strings van 255 karakters lang. Met een normale string werkt het echter wel, en gezien er ook maar 1 device aan de computer kan en mag hangen is het makkelijker om daar op te checken.

Ik pruts nog even verder met een vierde functie, met een beetje geluk krijg ik die met de opgedane kennis (waarvoor dank :)) ook aan de praat, zoniet zien jullie me wel terug hier.

  • __fred__
  • Registratie: November 2001
  • Laatst online: 08:43
klein is fijn schreef op woensdag 21 september 2011 @ 15:18:
[...]
Heh, das waar ja. Ik stuur gewoon een lege string.
Na het vrijmaken van wat geheugen geeft de functie inderdaad geen fouten meer.
Visual Basic .NET:
1
2
3
4
5
6
7
    Dim NumberOfDevices = HLP_GetNumDevices() ' get the number of devices
    If (NumberOfDevices <> 1) Then GoTo DeviceDetectionError ' if more or les then 1 device found, goto errorhandling

    Dim DeviceName As String = Space(255)
    Call HLP_GetDeviceNames(DeviceName, 255, NumberOfDevices) ' get devicenames

    HLP_ShowError(HLP_OpenDevName(DeviceName)) ' open connection to the first device

Het meesturen van een array geeft echter nog wel memory errors, zelfs na het vullen met strings van 255 karakters lang. Met een normale string werkt het echter wel, en gezien er ook maar 1 device aan de computer kan en mag hangen is het makkelijker om daar op te checken.

Ik pruts nog even verder met een vierde functie, met een beetje geluk krijg ik die met de opgedane kennis (waarvoor dank :)) ook aan de praat, zoniet zien jullie me wel terug hier.
Als je nou de lengte van je string 255 * NumberOfDevices maakt en zelf het resultaat opdeelt in strings van 255 characters, dan ben je er ook natuurlijk. Scheelt je ook een eventuele access violation.
Pagina: 1