2015-05-13 13 views
7

Sto provando a costruire un sistema di calcolo distributivo che utilizza i file di mappatura della memoria per coordinare il lavoro tra diversi PC in rete tramite VBA. In altre parole, voglio che un gruppo di computer in rete esegua il lavoro contemporaneamente in modo coordinato su un singolo progetto che può essere facilmente diviso in parti diverse. Un PC impiega più di 13 ore per completare il progetto, il che non è pratico per il mio cliente.Come archiviare i dati e recuperare i dati dai file di mappatura della memoria utilizzando CopyMemory in VBA?

Desidero memorizzare le informazioni nei file di mappatura della memoria che aiuteranno i PC a lavorare sul progetto in modo coordinato (cioè senza duplicazioni di lavoro, evitare problemi di razza, ecc.). Ho provato ad usare altri tipi di file per realizzare questo e causa problemi di file race o ci vuole troppo tempo. Quindi, come suggerito su questo forum, sto provando i file di mappatura della memoria.

Sono nuovo di zecca per i file di mappatura della memoria e il calcolo distributivo. Deve essere fatto in VBA. Per quanto ne so, devo specificare che il file deve essere salvato su una directory sulla nostra rete (qui l'unità Z) a cui tutti i PC hanno accesso. Ho messo insieme un po 'di codice da vari luoghi:

Option Explicit 

Private Const PAGE_READWRITE As Long = &H4 
Private Const FILE_MAP_WRITE As Long = &H2 
Private Const GENERIC_READ = &H80000000 
Private Const GENERIC_WRITE = &H40000000 
Private Const OPEN_ALWAYS = 4 
Private Const FILE_ATTRIBUTE_NORMAL = &H80 

Private Declare Function CreateFile Lib "kernel32" Alias "CreateFileA" (ByVal lpFileName As String, _ 
             ByVal dwDesiredAccess As Long, ByVal dwShareMode As Long, _ 
             ByVal lpSecurityAttributes As Long, ByVal dwCreationDisposition As Long, _ 
             ByVal dwFlagsAndAttributes As Long, ByVal hTemplateFile As Long) As Long 

Private Declare Function CreateFileMapping Lib "kernel32.dll" Alias "CreateFileMappingA" (_ 
    ByVal hFile As Long, _ 
    ByVal lpFileMappigAttributes As Long, _ 
    ByVal flProtect As Long, _ 
    ByVal dwMaximumSizeHigh As Long, _ 
    ByVal dwMaximumSizeLow As Long, _ 
    ByVal lpName As String) As Long 

Private Declare Function MapViewOfFile Lib "kernel32.dll" (_ 
    ByVal hFileMappingObject As Long, _ 
    ByVal dwDesiredAccess As Long, _ 
    ByVal dwFileOffsetHigh As Long, _ 
    ByVal dwFileOffsetLow As Long, _ 
    ByVal dwNumberOfBytesToMap As Long) As Long 

#If VBA7 Then 
Public Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias _ 
    "RtlMoveMemory" (destination As Any, source As Any, _ 
    ByVal length As Long) 
#Else 
Public Declare Sub CopyMemory Lib "kernel32" Alias _ 
    "RtlMoveMemory" (destination As Any, source As Any, _ 
    ByVal length As Long) 
    #End If 

Private Declare Function UnmapViewOfFile Lib "kernel32.dll" (_ 
    ByRef lpBaseAddress As Any) As Long 

Private Declare Function CloseHandle Lib "kernel32.dll" (_ 
    ByVal hObject As Long) As Long 

Private hMMF As Long 
Private pMemFile As Long 

Sub IntoMemoryFileOutOfMemoryFile() 

    Dim sFile As String 
    Dim hFile As Long 

    sFile = "Z:\path\test1.txt" 

    hFile = CreateFile(sFile, GENERIC_READ Or GENERIC_WRITE, 0, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0) 
    hMMF = CreateFileMapping(hFile, 0, PAGE_READWRITE, 0, 1000000, "MyMemoryMappedFile") 

    pMemFile = MapViewOfFile(hMMF, FILE_MAP_WRITE, 0, 0, 0) 

    Dim buffer As String 

    buffer = "testing1" 
    CopyMemory pMemFile, ByVal buffer, 128 

    hMMF = CreateFileMapping(-1, 0, PAGE_READWRITE, 0, 1000000, "MyMemoryMappedFile") 
    pMemFile = MapViewOfFile(hMMF, FILE_MAP_WRITE, 0, 0, 0) 

    Dim buffer2 As String 

    buffer2 = String$(128, vbNullChar) 

    CopyMemory ByVal buffer2, pMemFile, 128 

    MsgBox buffer2 & " < - it worked?" 

    UnmapViewOfFile pMemFile 
    CloseHandle hMMF 
End Sub 

Come un piccolo esempio, il codice di cui sopra cerca di mettere la stringa "testing1" nel file test1.txt quindi recuperare quella stringa e memorizzarlo in buffer2 variabile e, infine, mostra quella stringa tramite un msgbox. Super semplice. Tuttavia, non ho idea di cosa sto facendo.

Tutti i nostri PC sono a 64 bit, Windows 7, Office/Excel 2013.

Problemi/domande:

  1. MsgBox è vuoto quando si esegue IntoMemoryFileOutOfMemoryFile
  2. Dopo che il sub è completa I apri test1.txt e ottengo: "Il processo non può accedere al file perché è utilizzato da un altro processo." Il che mi dice che non sto usando UnmapViewOfFile e/o CloseHandle correttamente.
  3. Mi piacerebbe rendere persistenti questi file di memoria, quindi se tutti i PC sono interrotti posso riavviare il processo e riprendere da dove ho lasciato.

Ecco alcuni dei link che ho usato per arrivare dove sono ora:

Informazioni interessanti ma non importanti: Il "progetto" è per un cliente di hedge fund. Sono un ragazzo delle finanze diventato fondamentale. Stiamo analizzando oltre 2000 azioni più su base giornaliera oltre 1250 campi dati per fare segnali/previsioni macroeconomici per comprare e vendere azioni, futures e opzioni.

UPDATE: Se cambio le due linee CopyMemory come questa (passare pMemFile per valore) rispettivamente:

CopyMemory ByVal pMemFile, buffer, 128 

e ...

CopyMemory buffer2, ByVal pMemFile, 128 

Ottengo un sacco di personaggi pazzi nel file test1.txt ed Excel si blocca.

+3

Perché questo deve essere VBA? Per quanto ami VBA, sembra un po 'come usare un martello quando quello che ti serve è un cacciavite. – RubberDuck

+1

@RubberDuck Potrebbero essere i requisiti del cliente (ho avuto lo stesso problema per uno strumento molto più semplice) che hanno qualcuno che è in grado di mantenere in VBA ma non in altre lingue. Passa inosservato, ma interessante, ci penserò, ma non posso fare promesse! ;) – R3uK

+1

@RubberDuck - Non è la mia scelta neanche. – mountainclimber

risposta

3

Per il tuo primo numero (non l'ho esplorato troppo), questo è legato al modo in cui stai cercando di passare il tuo buffer a RtlMoveMemory. Si aspetta un puntatore, ma lo stai passando una copia di uno BSTR. Ricorda inoltre che una stringa in VBA è Unicode, quindi otterrai caratteri nulli intrecciati. Io di solito uso sia gli array di Byte o le varianti (che verranno scartati in un CSTR).

Per il secondo problema, il file viene bloccato perché non si rilascia l'handle su hFile. Infatti, non appena lo si passa a CreateFileMappingA, è possibile chiamare CloseHandle su hFile.

Per il terzo problema, si sovrascrive il proprio handle hMMF e il puntatore pMemFile quando si effettua la seconda chiamata. In teoria, dovrebbero restituire lo stesso handle e il medesimo puntatore del tuo stesso processo, ma questo in realtà non verifica se hai ottenuto la visualizzazione della mappa.

Per quanto riguarda l'accesso alla memoria, probabilmente raccomanderei di avvolgere il tutto in una classe e di mappare il puntatore a qualcosa di più utile delle chiamate a RtlMoveMemory. Ho adattato il mio codice è stato collegato per la questione in una classe che dovrebbe rendere un po 'più sicuro e più affidabile e facile da usare (anche se deve ancora essere concretizzati con il controllo degli errori):

'Class MemoryMap 
Option Explicit 

Private Type SafeBound 
    cElements As Long 
    lLbound As Long 
End Type 

Private Type SafeArray 
    cDim As Integer 
    fFeature As Integer 
    cbElements As Long 
    cLocks As Long 
    pvData As Long 
    rgsabound As SafeBound 
End Type 

Private Const VT_BY_REF = &H4000& 
Private Const FILE_ATTRIBUTE_NORMAL = &H80 
Private Const OPEN_ALWAYS = &H4 
Private Const GENERIC_READ = &H80000000 
Private Const GENERIC_WRITE = &H40000000 
Private Const PAGE_READWRITE = &H4 
Private Const FILE_MAP_WRITE = &H2 
Private Const FADF_FIXEDSIZE = &H10 

Private cached As SafeArray 
Private buffer() As Byte 
Private hFileMap As Long 
Private hMM As Long 
Private mapped_file As String 
Private bound As Long 

Public Property Get FileName() As String 
    FileName = mapped_file 
End Property 

Public Property Get length() As Long 
    length = bound 
End Property 

Public Sub WriteData(inVal As String, offset As Long) 
    Dim temp() As Byte 
    temp = StrConv(inVal, vbFromUnicode) 

    Dim index As Integer 
    For index = 0 To UBound(temp) 
     buffer(index + offset) = temp(index) 
    Next index 
End Sub 

Public Function ReadData(offset, length) As String 
    Dim temp() As Byte 
    ReDim temp(length) 

    Dim index As Integer 
    For index = 0 To length - 1 
     temp(index) = buffer(index + offset) 
    Next index 

    ReadData = StrConv(temp, vbUnicode) 
End Function 

Public Function OpenMapView(file_path As String, size As Long, mapName As String) As Boolean 
    bound = size 
    mapped_file = file_path 

    Dim hFile As Long 
    hFile = CreateFile(file_path, GENERIC_READ Or GENERIC_WRITE, 0, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0) 
    hFileMap = CreateFileMapping(hFile, 0, PAGE_READWRITE, 0, size, mapName) 
    CloseHandle hFile 
    hMM = MapViewOfFile(hFileMap, FILE_MAP_WRITE, 0, 0, 0) 

    ReDim buffer(2) 
    'Cache the original SafeArray structure to allow re-mapping for garbage collection. 
    If Not ReadSafeArrayInfo(buffer, cached) Then 
     'Something's wrong, close our handles. 
     CloseOpenHandles 
     Exit Function 
    End If 

    Dim temp As SafeArray 
    If ReadSafeArrayInfo(buffer, temp) Then 
     temp.cbElements = 1 
     temp.rgsabound.cElements = size 
     temp.fFeature = temp.fFeature And FADF_FIXEDSIZE 
     temp.pvData = hMM 
     OpenMapView = SwapArrayInfo(buffer, temp) 
    End If  
End Function 

Private Sub Class_Terminate() 
    'Point the member array back to its own data for garbage collection. 
    If UBound(buffer) = 2 Then 
     SwapArrayInfo buffer, cached 
    End If 
    SwapArrayInfo buffer, cached 
    CloseOpenHandles 
End Sub 

Private Sub CloseOpenHandles() 
    If hMM > 0 Then UnmapViewOfFile hMM 
    If hFileMap > 0 Then CloseHandle hFileMap 
End Sub 

Private Function GetBaseAddress(vb_array As Variant) As Long 
    Dim vtype As Integer 
    'First 2 bytes are the VARENUM. 
    CopyMemory vtype, vb_array, 2 
    Dim lp As Long 
    'Get the data pointer. 
    CopyMemory lp, ByVal VarPtr(vb_array) + 8, 4 
    'Make sure the VARENUM is a pointer. 
    If (vtype And VT_BY_REF) <> 0 Then 
     'Dereference it for the actual data address. 
     CopyMemory lp, ByVal lp, 4 
     GetBaseAddress = lp 
    End If 
End Function 

Private Function ReadSafeArrayInfo(vb_array As Variant, com_array As SafeArray) As Boolean 
    If Not IsArray(vb_array) Then Exit Function 

    Dim lp As Long 
    lp = GetBaseAddress(vb_array) 
    If lp > 0 Then 
     With com_array 
      'Copy it over the passed structure 
      CopyMemory .cDim, ByVal lp, 16 
      'Currently doesn't support multi-dimensional arrays. 
      If .cDim = 1 Then 
       CopyMemory .rgsabound, ByVal lp + 16, LenB(.rgsabound) 
       ReadSafeArrayInfo = True 
      End If 
     End With 
    End If 
End Function 

Private Function SwapArrayInfo(vb_array As Variant, com_array As SafeArray) As Boolean 
    If Not IsArray(vb_array) Then Exit Function 
    Dim lp As Long 
    lp = GetBaseAddress(vb_array) 

    With com_array 
     'Overwrite the passed array with the SafeArray structure. 
     CopyMemory ByVal lp, .cDim, 16 
     If .cDim = 1 Then 
      CopyMemory ByVal lp + 16, .rgsabound, LenB(.rgsabound) 
      SwapArrayInfo = True 
     End If 
    End With  
End Function 

Usage è come questo:

Private Sub MMTest() 
    Dim mm As MemoryMap 

    Set mm = New MemoryMap 
    If mm.OpenMapView("C:\Dev\test.txt", 1000, "TestMM") Then 
     mm.WriteData "testing1", 0 
     Debug.Print mm.ReadData(0, 8) 
    End If 

    Set mm = Nothing 
End Sub 

Avrete anche bisogno dei seguenti dichiarazioni da qualche parte:

Public Declare Function MapViewOfFile Lib "kernel32.dll" (_ 
    ByVal hFileMappingObject As Long, _ 
    ByVal dwDesiredAccess As Long, _ 
    ByVal dwFileOffsetHigh As Long, _ 
    ByVal dwFileOffsetLow As Long, _ 
    ByVal dwNumberOfBytesToMap As Long) As Long 

Public Declare Sub CopyMemory Lib "kernel32" Alias _ 
    "RtlMoveMemory" (Destination As Any, Source As Any, _ 
    ByVal length As Long) 

Public Declare Function CloseHandle Lib "kernel32.dll" (_ 
    ByVal hObject As Long) As Long 

Public Declare Function UnmapViewOfFile Lib "kernel32.dll" (_ 
    ByVal lpBaseAddress As Any) As Long 

Un'altra cosa da tenere a mente - dal momento che si sta utilizzando un'unità di rete, si wan t per assicurarsi che i meccanismi di caching non interferiscano con gli accessi al file. In particolare, ti consigliamo di assicurarti che tutti i client abbiano disattivato il caching dei file di rete. Si potrebbe anche voler svuotare la mappa della memoria in modo deterministico invece di fare affidamento sul sistema operativo (vedere FlushViewOfFile).

+0

Grazie, ma cosa mi manca? Non vedo come otterresti i dati con la tua classe MemoryMap. Ex. mm.ReadData – mountainclimber

+0

Inoltre, come si scrive una stringa di prova come "testing1" nella domanda originale? – mountainclimber

+1

@mountainclimber - L'implementazione della classe non è completa. L'array di byte all'interno della classe fondamentalmente * è * il file mappato in memoria, quindi qualsiasi metodo di accesso dovrebbe leggere e scrivere da 'buffer'. Il modo in cui lo usi è specifico dell'effetto, ma ho modificato il codice nella risposta per leggere e scrivere stringhe. – Comintern

Problemi correlati