none
Datenaustausch zwischen VB6 und VB.Net über ein MemoryMappedFile RRS feed

  • Frage

  • Hallo,

    wie der Titel schon sagt, möchte ich IPC von VB6 nach VB.Net und zurück realisieren. Dafür erstelle ich in dem Programm welches als erstes startet ein MemoryMappedFile. Unter VB6 funktioniert alles wie es soll: Ich verwende ein UDT (Struktur) welches den Speicheraufbau des MemoryMappedFiles darstellt. Damit initialisiere ich ein Array(0) und binde dieses mit RtlMoveMemory an das MemoryMappedFile (hier im ersten Step noch ohne Mutex oder andere Sperrmechanismen).

    Damit kann ich bereits beliebig viele VB6-Anwendungen starten und untereinander Daten austauschen. Ich muss sozusagen nur die Daten in meines Struktur-Arrays ändern und auf der anderen Seite wieder auslesen:

    Public Type TMapArray Settings(0 To 127) As Long UserName As String * 2 End Type MapArray(0).Settings(i) = 123 MapArray(0).UserName = "WW"

    'im zweiten Programm die Daten auslesen:
    settings(i) = MapArray(0).Settings(i)
    userName = MapArray(0).UserName

    Entweder ich stelle mich aus Unwissen zu dumm an, oder die Sache ist in .Net nicht ganz so komfortabel. Ich komme zwar an die Daten heran, schön ist aber anders. Ich verwende dort die IO.MemoryMappedFiles.MemoryMappedFile.CreateOrOpen-Methode um das MappedFile zu öffnen. Mit einem CreateViewAccessor kann ich auf die Daten zugreifen. Die Methoden dieses Accessors sind aber sehr umständlich:

    Using mMMVA mMMVA = mMMF.CreateViewAccessor() Debug.Print(mMMVA.ReadInt32(OFFSET_S1)) Debug.Print(mMMVA.ReadInt32(OFFSET_S2))
    'Debug.Print(mMMVA.ReadString(OFFSET_USERNAME,SIZE_USERNAME)) 'gibt es nicht
    'hingegen würde so was funktionieren:
    mMMVA = mMMF.CreateViewAccessor(OFFSET_USERNAME,SIZE_USERNAME)
    Debug.Print(mMMVA.????) End Using

    Da gibt es zwar auch eine Read<T>(Int64, T) Methode, mit der bin ich aber bisher nicht klar gekommen. Und für das zurückschreiben von Werten habe ich noch gar keinen Plan. Wünschenswert wäre halt eine Klasse, die genau den Aufbau meiner Struktur abbildet und ich dort über die Properties die Werte in das MappedFile schreibe und genauso vor dort lese, halt so wie in VB6 auch. Man könnte vermutlich den VB6 Code auch migrieren, was aber dann Unmanaged-APIs verwendet. Das wäre aber sozusagen die Rolle rückwärts. Vielleicht verwende ich aber auch nur die falschen Klassen, vielleicht ist dieser ViewAccessor nicht das richtige Werkzeug.

    Schönen Gruß
    W. Wolf

    Mittwoch, 23. Oktober 2019 08:41

Antworten

  • Hallo,

    bin jetzt vorerst bei folgender Lösung gelandet:

    Public Sub New()
            mMMF = IO.MemoryMappedFiles.MemoryMappedFile.CreateOrOpen(MAP_NAME, MAP_CAPACITY)
            mMMVA = mMMF.CreateViewAccessor
        End Sub
    
        Public Property UserName() As String
            Get
                Dim puffer(SIZE_USERNAME - 1) As Byte
                mMMVA.ReadArray(Of Byte)(OFFSET_USERNAME, puffer, 0, SIZE_USERNAME)
                Return System.Text.Encoding.Unicode.GetString(puffer).TrimEnd()
            End Get
            Set(value As String)
                Dim puffer() As Byte
                puffer = System.Text.Encoding.Unicode.GetBytes(value)
                If puffer.Length < SIZE_USERNAME Then ReDim Preserve puffer(SIZE_USERNAME - 1)
                mMMVA.WriteArray(Of Byte)(OFFSET_USERNAME, puffer, 0, SIZE_USERNAME)
            End Set
        End Property

    Damit funktioniert vorerst alles. Ich muss halt für jedes einzelne Feld eine eigene Property anlegen und genau darauf achten, dass Offset und Size stimmen. Den Komfort meiner VB6 Lösung konnte ich leider nicht erreichen - immerhin funktioniert alles.

    Danke für die Tipps. Die haben schon geholfen, bzw. mich auf den richtigen Weg gebracht. Wenn hier jemand was ergänzen will, bitte, bitte macht das. Gerne auch Kritik an meiner Lösung.

    Schönen Gruß

    W. Wolf

    Freitag, 25. Oktober 2019 11:16

Alle Antworten

  • Hallo W. Wolf,

    Wie schreibst Du die Daten in die Speicherabbilddatei? Hast Du es mit WriteArray<byte> bzw. ReadArray<byte> und mit einer Konvertierung der Struktur in ein Bytearray probiert?

    Gruß,
    Dimitar


    Bitte haben Sie Verständnis dafür, dass im Rahmen dieses Forums, welches auf dem Community-Prinzip „IT-Pros helfen IT-Pros“ beruht, kein technischer Support geleistet werden kann oder sonst welche garantierten Maßnahmen seitens Microsoft zugesichert werden können.

    Donnerstag, 24. Oktober 2019 08:38
    Moderator
  • Hallo W. Wolf,

    Ich verwende ein UDT (Struktur) welches den Speicheraufbau des MemoryMappedFiles darstellt.
    Public Type TMapArray
       Settings(0 To 127) As Long
       UserName As String * 2
    End Type

    Sieh Dir mal den unten angegebenen Beispielcode, der das bereits angesprochene Verfahren veranschaulicht. Für die Konvertierung einer Struktur in ein Bytearray und umgekehrt verweise ich Dich auf diesen aufschlussreichen Thread, dem ich die zwei Funktionen entnommen habe. Ich kenne mich mit Visual Basic 6 nicht aus und kann nur vermuten, dass TMapArray eine Struktur mit zwei Feldern (eine Zeichenfolge und ein Array) ist. Selbst wenn dies nicht der Fall ist, hoffe ich, dass der Code ohne großen Aufwand angepasst werden kann.

    Imports System.Runtime.InteropServices Imports System.IO.MemoryMappedFiles Module Module1 Public Structure Struktur Public Settings As Long() Public UserName As String Public Sub New(ByVal s As String) UserName = s Settings = New Long(127) {} End Sub End Structure Public Function Bytesabrufen(ByVal mStruktur As Struktur) As Byte() Dim Groesse As Integer = Marshal.SizeOf(mStruktur) Dim Puffer As Byte() = New Byte(Groesse - 1) {} Dim Zeiger As IntPtr = Marshal.AllocHGlobal(Groesse) Marshal.StructureToPtr(mStruktur, Zeiger, False) Marshal.Copy(Zeiger, Puffer, 0, Groesse) Marshal.FreeHGlobal(Zeiger) Return Puffer End Function Public Function Strukturabrufen(ByVal Puffer As Byte()) As Struktur Dim nStruktur As Struktur = New Struktur() Dim Groesse As Integer = Marshal.SizeOf(nStruktur) Dim Zeiger As IntPtr = Marshal.AllocHGlobal(Groesse) Marshal.Copy(Puffer, 0, Zeiger, Groesse) nStruktur = CType(Marshal.PtrToStructure(Zeiger, GetType(Struktur)), Struktur) Marshal.FreeHGlobal(Zeiger) Return nStruktur End Function Sub Main() Dim alteStruktur As Struktur = New Struktur("Benutzername") For i As Integer = 0 To 128 - 1 alteStruktur.Settings(i) = i Next Dim neueStruktur As Struktur Dim Puffer As Byte() = Bytesabrufen(alteStruktur) Dim Groesse As Integer = Marshal.SizeOf(alteStruktur) Using Speicherabbilddatei As MemoryMappedFile = MemoryMappedFile.CreateFromFile("<Pfad zur Textdatei>", System.IO.FileMode.Create, "Datei", Groesse) Dim gelesen As Byte() = New Byte(Groesse - 1) {} Dim Zugriff As MemoryMappedViewAccessor = Speicherabbilddatei.CreateViewAccessor() Zugriff.WriteArray(Of Byte)(0, Puffer, 0, Groesse) Zugriff.ReadArray(Of Byte)(0, gelesen, 0, gelesen.Length) neueStruktur = Strukturabrufen(gelesen) End Using For i As Integer = 0 To alteStruktur.Settings.Length - 1 Console.Write(alteStruktur.Settings(i).ToString() & ",") Next Console.WriteLine(alteStruktur.UserName) For i As Integer = 0 To neueStruktur.Settings.Length - 1 Console.Write(neueStruktur.Settings(i).ToString() & ",") Next Console.WriteLine(neueStruktur.UserName) Console.ReadKey(True) End Sub End Module

    Gruß,
    Dimitar


    Bitte haben Sie Verständnis dafür, dass im Rahmen dieses Forums, welches auf dem Community-Prinzip „IT-Pros helfen IT-Pros“ beruht, kein technischer Support geleistet werden kann oder sonst welche garantierten Maßnahmen seitens Microsoft zugesichert werden können.

    Donnerstag, 24. Oktober 2019 10:19
    Moderator
  • Sieh Dir mal den unten angegebenen Beispielcode, der das bereits angesprochene Verfahren veranschaulicht. Für die Konvertierung einer Struktur in ein Bytearray und umgekehrt verweise ich Dich auf diesen aufschlussreichen Thread, dem ich die zwei Funktionen entnommen habe. Ich kenne mich mit Visual Basic 6 nicht aus und kann nur vermuten, dass TMapArray eine Struktur mit zwei Feldern (eine Zeichenfolge und ein Array) ist. Selbst wenn dies nicht der Fall ist, hoffe ich, dass der Code ohne großen Aufwand angepasst werden kann.

    Da hast Du natürlich recht, der VB6 Quellcode ist sehr kompakt, ich hätte ihn gleich mit posten können. Habe deinen Beispielcode noch nicht adaptiert, .Net füllt sich für mich noch immer fremdartig an, werde mich aber bald hineinarbeiten und hier berichten. Unabhängig davon poste ich nachträglich den VB-Code auch, s.u.

    So wie ich deinen Beispielcode interpretiere, ist das ein Mix aus den beiden von mir angesprochenen Methoden: einmal das was ich schon erreicht habe und danach eine unmanaged-Bindung auf eine Struktur. Im meinem VB-Code werden sozusagen nur Zeiger umgebogen. Ich schreibe in ein Struktur und die Daten landen ohne Umweg im MappedFile. Beim Lesen ist es umgekehrt: ich lese die Daten aus der Struktur-Variable, die intern auf das MappedFile zeigt. Für mein Programm verhält sich das voll transparent, so als ob die Daten quasi in einer globalen Struktur-Variable im Speicher meiner Anwendung wären. Das macht die Arbeit entsprechend komfortabel.

    Zunächst mal, vielen Dank für den Code. Falls sich daraus eine brauchbare Lösung ergibt, werde ich die hier auch bereitstellen, sozusagen als Danke an die Community.

    Hier noch der VB6-Code:

    Public Type SAFEARRAY1D
       cDims As Integer
       fFeatures As Integer
       cbElements As Long
       cLocks As Long
       pvData As Long
       cElements1D As Long
       lLbound1D As Long
    End Type
    
    
    Declare Function CreateFileMappingW& Lib "kernel32" (ByVal hFile&, ByVal secAttr&, ByVal Protect&, ByVal SizeHigh&, ByVal SizeLow As Long, ByVal MapName As Long)
    Declare Function MapViewOfFile& Lib "kernel32" (ByVal hFile&, ByVal DesAccess&, ByVal OffsHigh&, ByVal OffsLow&, ByVal NumberOfBytesToMap As Long)
    Declare Function UnmapViewOfFile& Lib "kernel32" (ByVal lpBaseAddress As Long)
    Declare Function CloseHandle& Lib "kernel32" (ByVal hObj As Long)
    Declare Sub BindMapp Lib "kernel32" Alias "RtlMoveMemory" (PArr() As Any, pSrc&, Optional ByVal CB& = 4)
    Declare Sub ReleaseMapp Lib "kernel32" Alias "RtlMoveMemory" (PArr() As Any, Optional pSrc& = 0, Optional ByVal CB& = 4)
    Private Const FILE_MAP_ALL_ACCESS& = &HF001F
    
    Public Type TMapArray
       Settings(0 To 127) As Long
       UserName As String * 2
    End Type
    
    Private Const MAP_KEY As String = "FHGR-DGJRGHUZ-TTHJ"
    
    Public MapArray() As TMapArray
    Private saMap As SAFEARRAY1D
    Private pMapAddr As Long
    Private hMapFile As Long
    
    Public Function InitMapping() As Boolean
    Dim mapSize As Long
    
       CloseMapping
       mapSize = LenB(MapArray(0))
       hMapFile = CreateFileMappingW(-1, 0, 4, 0, mapSize, StrPtr(MAP_KEY))
       If hMapFile = 0 Then Exit Function
    
       pMapAddr = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0)
       If pMapAddr = 0 Then CloseHandle hMapFile: Exit Function
    
       'Descriptor initialisieren
       With saMap
        .cDims = 1
        .cbElements = LenB(MapArray(0))
        .cElements1D = 1
        .pvData = pMapAddr
       End With
    
       'SafeArray-Descriptor an MapArray-Array binden
       BindMapp MapArray, VarPtr(saMap)
    
       InitMapping = True
    End Function
    
    Public Sub CloseMapping()
       ReleaseMapp MapArray
       If pMapAddr Then UnmapViewOfFile pMapAddr: pMapAddr = 0
       If hMapFile Then CloseHandle hMapFile:     hMapFile = 0
    End Sub

    Schönen Gruß
    W. Wolf

    Donnerstag, 24. Oktober 2019 13:59
  • Hallo,

    hier mal eine Zwischenbilanz:

    Vom VB6-Komfort bin ich noch Meilen entfernt. Folgende Probleme gibt es bei der Umsetzung:

    Bytesabrufen schreibt nicht die Daten in den Puffer, sondern nur irgendwelche Zeiger. Gut erkennbar an Marshal.SizeOf(mStruktur) = 8. Damit wäre eine IPC nicht möglich, weil in der Mapp Daten landen mit denen ein anderer Prozess nichts anfangen kann. Ich sehe da auch eine gewisse Schwierigkeit in der Umsetzung. VB6 verwendet in der Struktur Strings mit fester Länge. Das braucht man auch, sonnst würde sich der Mapp-Aufbau ständig verändern. In der .Net-Struktur darf man also erst gar nicht den UserName als String deklarieren. Die Funktion Bytesabrufen wird immer ein gewisses Problem haben die Bytes an die richtige Stelle im Array zu schreiben. Vermutlich muss ich in der Struktur statt den Strings weitere ByteArrays verwenden, in denen ich die Unicodes des Strings ablege (in der echten Struktur gibt es mehrere Member, ich habe hier die Struktur vereinfacht).

    Momentan gefällt mir auch nicht, dass ich bei jedem Schreib- und Lese-Vorgang den ganzen Puffer bewegen muss. Also zuerst Struktur in den Puffer überführen, Puffer in die Mapp schreiben, Puffer wieder aus der Mapp lesen und zurück in die Struktur konvertieren. Ich glaube da war mein ursprünglicher Ansatz mit CreateViewAccessor(OFFSET_USERNAME,SIZE_USERNAME) fast besser. Allerdings erzeuge ich hier jedes mal einen neuen ViewAccessor, das kostet sicher auch Zeit. Wahrscheinlich sollte ich den ViewAccessor unr einmal erstellen und mittels WriteArray / ReadArray und Offset/Size arbeiten. Offset und Size müsste ich dann in Konstanten bereitstellen, was die Verwaltung etwas erschwert.

    Jetzt würde ich nur gerne wissen, ob meine Überlegungen richtig sind. Eine zu VB6 kompatible Lösung scheint momentan weit entfernt, oder?

    Schönen Gruß
    W. Wolf

    Freitag, 25. Oktober 2019 10:19
  • Hallo,

    bin jetzt vorerst bei folgender Lösung gelandet:

    Public Sub New()
            mMMF = IO.MemoryMappedFiles.MemoryMappedFile.CreateOrOpen(MAP_NAME, MAP_CAPACITY)
            mMMVA = mMMF.CreateViewAccessor
        End Sub
    
        Public Property UserName() As String
            Get
                Dim puffer(SIZE_USERNAME - 1) As Byte
                mMMVA.ReadArray(Of Byte)(OFFSET_USERNAME, puffer, 0, SIZE_USERNAME)
                Return System.Text.Encoding.Unicode.GetString(puffer).TrimEnd()
            End Get
            Set(value As String)
                Dim puffer() As Byte
                puffer = System.Text.Encoding.Unicode.GetBytes(value)
                If puffer.Length < SIZE_USERNAME Then ReDim Preserve puffer(SIZE_USERNAME - 1)
                mMMVA.WriteArray(Of Byte)(OFFSET_USERNAME, puffer, 0, SIZE_USERNAME)
            End Set
        End Property

    Damit funktioniert vorerst alles. Ich muss halt für jedes einzelne Feld eine eigene Property anlegen und genau darauf achten, dass Offset und Size stimmen. Den Komfort meiner VB6 Lösung konnte ich leider nicht erreichen - immerhin funktioniert alles.

    Danke für die Tipps. Die haben schon geholfen, bzw. mich auf den richtigen Weg gebracht. Wenn hier jemand was ergänzen will, bitte, bitte macht das. Gerne auch Kritik an meiner Lösung.

    Schönen Gruß

    W. Wolf

    Freitag, 25. Oktober 2019 11:16