Parallelisierung mit MS-Access VBA

flybyray

Vice Admiral Special
Mitglied seit
06.06.2008
Beiträge
509
Renomée
9
Standort
München
Ist Parallelisierung in MS-Access VBA mit Hilfe von Threads durch COM+ möglich?
Die meisten Stimmen im Internet sagen generell, dass Parallelisierung unmöglich ist und wenn man mein Lösungen zu finden dann stellt sich heraus das die Lösung externe Prozesse sind. Externe Prozesse sind aber keine wirkliche Lösung, da man ja hier IPC (Interprocess Kommunikation) betreiben muss um Daten auszutauschen oder Berechnungen im Kontext eines bestehenden Prozesses durchführen zu können.

Daher hier meine Fragen:
Wird der MS-Access VBA Code selbst in der Runtime in einem sogenannten "Single-Threaded Apartments" (STA) Modell ausgeführt?

Wenn ja:
Könnte man mit Hilfe CoMarshalInterThreadInterfaceInStream , CoGetInterfaceAndReleaseStream (siehe Abschnitt "Marshaling Interface Pointers Between Apartments" InsideCOM+) COM Komponenten (plural) schreiben als Erweiterung um mit Hilfe der Interfaces Berechnungen im VBA Kontext anzustoßen und zwar in eigenen Threads?

Hoffentlich ist mein Anliegen verständlich. Ich selbst bin mehr Java Entwickler, aber habe immer Interesse auch mal am "Tellerand" auftrende Probleme zu lösen. Nur bei der Sache wäre es mir echt recht wenn da jemand mit Erfahrung und Ahnung vielleicht mal was sagen könnte oder hinweise geben könnte. Vielen Dank
 
Was genau soll den parallelisiert werden? Wäre es nicht vielleicht sinnvoller aus der Access-Anwendung eine .NET Anwendung zu machen?
 
Ok es braucht noch mehr Background Infos.
Die Anwendung ist schon ziemlich alt und hat eine enorme Quellcode-Basis, die man nicht einfach in absehbarer Zeit umstellen kann.
Der Anwendungskern basiert auf Access 2003 VBA Code. Ein schöne alte Welt von "vor...vorgestern" also. ;D
 
In Sachen MultiThreading besteht bei VB und vermutlich auch bei VBA ein Problem darin, die Runtime für den neuen Thread zu initialisieren. VBA kann man nicht zu nativem Code kompilieren, es ist immer interpretiert, richtig? Gerade dann dürfte die Initialisierung der Runtime schwierig werden, da sie ja als Interpreter bei jedem einzelnen Befehl benötigt wird.
Was hingegen problemlos geht: Aus VB (und sicher auch VBA) heraus COM-Komponenten verwenden, die z.B. in C++ entwickelt wurden und die intern MultiThreading nutzen.

Was wollt ihr denn parallelisieren? Wenn es reines Numbercrunching ist, könnte eine separate C++-Komponente tatsächlich eine Lösung sein. Wenn der parallelisierte Code auf das Access-Objektmodell zugreifen soll, sollte man sich aber auf jeden Fall erstmal schlau machen, ob dieses Objektmodell threadsafe ist. Nicht dass das intern auf die GUI zugreift und deshalb immer im Kontext des Hauptthreads laufen muss...
 
TiKU vielen dank für die Nachfrage.
VBA kann man nicht zu nativem Code kompilieren, es ist immer interpretiert, richtig?
Also es gibt eine kompilieren Funktion. Aber es ist wohl richtig, dass es sich dennoch nur um interpretierten Code handelt. Ich denke, dass VBA intern ähnlich wie bei Python die Quellen (*.py) zu binär Code (*.pyc) umwandelt.

Gerade dann dürfte die Initialisierung der Runtime schwierig werden, da sie ja als Interpreter bei jedem einzelnen Befehl benötigt wird.
Ja das mag ein Problem sein ich habe, dabei auf die Funktion CoInitialize gehofft.
Code:
Declare Function CoInitialize Lib "ole32.dll" (ByVal pvReserved As Long) As Long
...
	res = CoInitialize(0)
siehe: The CreateThread API Revisited

Was hingegen problemlos geht: Aus VB (und sicher auch VBA) heraus COM-Komponenten verwenden, die z.B. in C++ entwickelt wurden und die intern MultiThreading nutzen.
Ja ich denke das ist machbar nur wird es eben nicht viel bringen, da der auszuführende Code tatsächlich in VBA vorliegt.

Was wollt ihr denn parallelisieren? Wenn es reines Numbercrunching ist, könnte eine separate C++-Komponente tatsächlich eine Lösung sein. Wenn der parallelisierte Code auf das Access-Objektmodell zugreifen soll, sollte man sich aber auf jeden Fall erstmal schlau machen, ob dieses Objektmodell threadsafe ist. Nicht dass das intern auf die GUI zugreift und deshalb immer im Kontext des Hauptthreads laufen muss...
Es sind tatsächlich eher Rechenroutinen, also Zeitreihen und Prognosen und so Zeug. Teilweise müssen diese sicherlich bereinigt werden. Es ist uralter Code der aber nach wie vor das Berechnet was er soll.

Ich habe mal auf dem Code vom Appleman herumgehackt und diesen in eine Access-VBA Umgebung verwendet:
Module1:
Code:
Option Compare Database
Option Explicit

' Structure to hold IDispatch GUID
Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(7) As Byte
End Type
Public IID_IDispatch As GUID

Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) 'For 32 Bit Systems

Declare Function DllGetClassObject Lib "ole32.dll" (riid As GUID, rclsid As Long) As Long
Declare Function CoGetPSClsid Lib "ole32.dll" (riid As GUID, pClsid As Long) As Long
Declare Function CoRegisterPSClsid Lib "ole32.dll" (riid As GUID, rclsid As Long) As Long
Declare Function CoMarshalInterThreadInterfaceInStream Lib "ole32.dll" (riid As GUID, ByVal pUnk As IUnknown, ppStm As Long) As Long
Declare Function CoGetInterfaceAndReleaseStream Lib "ole32.dll" (ByVal pStm As Long, riid As GUID, pUnk As IUnknown) As Long
Declare Function CoInitialize Lib "ole32.dll" (ByVal pvReserved As Long) As Long
Declare Sub CoUninitialize Lib "ole32.dll" ()
Declare Function CreateThread Lib "kernel32" (ByVal lpSecurityAttributes As Long, ByVal dwStackSize As Long, ByVal lpStartAddress As Long, ByVal lpParameter As Long, ByVal dwCreationFlags As Long, lpThreadId As Long) As Long
Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long

Public Function test()
    Dim a As clsBackground
    Set a = New clsBackground

    StartBackgroundThreadApt a
End Function

' Start the background thread for this object
' using the apartment model
' Returns zero on error
Public Function StartBackgroundThreadApt(ByVal qobj As clsBackground)
    Dim threadid As Long
    Dim hnd&, res&
    Dim threadparam As Long
    Dim tobj As ISimpleObj
    
    Set tobj = qobj
    ' Proper marshaled approach
    InitializeIID
    res = CoMarshalInterThreadInterfaceInStream(IID_IDispatch, qobj, threadparam)
    If res <> 0 Then
    StartBackgroundThreadApt = 0
    Exit Function
    End If
    hnd = CreateThread(0, 2000, AddressOf BackgroundFuncApt, threadparam, 0, threadid)
    If hnd = 0 Then
    ' Return with zero (error)
    Exit Function
    End If
    ' We don't need the thread handle
    CloseHandle hnd
    StartBackgroundThreadApt = threadid
End Function

' Initialize the GUID structure
Private Sub InitializeIID()
    Static Initialized As Boolean
    If Initialized Then Exit Sub
    With IID_IDispatch
        .Data1 = &H20424
        .Data2 = 0
        .Data3 = 0
        .Data4(0) = &HC0
        .Data4(7) = &H46
    End With
    Initialized = True
End Sub


' A correctly marshaled apartment model callback.
' This is the correct approach, though slower.
Public Function BackgroundFuncApt(ByVal param As Long) As Long
    Dim qobj As Object
    Dim qobj2 As clsBackground
    Dim res&
    ' This new thread is a new apartment, we must
    ' initialize OLE for this apartment
    ' (VB doesn't seem to do it)
    res = CoInitialize(0)
    ' Proper apartment modeled approach
    res = CoGetInterfaceAndReleaseStream(param, IID_IDispatch, qobj)
    Set qobj2 = qobj
    'Do While Not qobj2.ISimpleObj_huba
    'Loop
'    qobj2.ShowAForm
    ' Alternatively, you can put a wait function here,
    ' then call the qobj function when the wait is satisfied
    ' All calls to CoInitialize must be balanced
    CoUninitialize
End Function
Klassenmodul "clsBackground":
Code:
' Class clsBackground
' MTDemo 3 Multithreading example
' Copyright © 1997 by Desaware Inc. All Rights Reserved
Implements ISimpleObj
Option Explicit
Event DoneCounting()
Dim l As Long

Public Sub ISimpleObj_huba()
    MsgBox "huba"
End Sub

Public Function ISimpleObj_DoTheCount(finalval As Long) As Boolean

    Dim s As String
    
    Debug.Print finalval
    
    If l = 0 Then
'        s$ = "In Thread " & App.threadid
'        Call MessageBox(0, s$, "", 0)
    End If
    l = l + 1
    If l >= finalval Then
        l = 0
        ISimpleObj_DoTheCount = True
        Call MsgBox(0, "Done with counting", "", 0)
        RaiseEvent DoneCounting
    End If
End Function

Da man bei der Initialisierung des Threads in VB nur eine COM Componente mit GUID also hier die ProxyStub dll angeben darf. Habe ich noch ein einfaches C++ ATL Projekt mit einem SimpleObj ATL-Objekt erzeugt, das eben ein Interface ISimpleObj deklariert.

Meine Idee war, vielleicht dieses Interface mit VBA Code (Implements ISimpleObj) zu implementieren und dann mit qobj die VBA implementierung für die Thread Initialisierung zu verwenden.
Code:
    res = CoMarshalInterThreadInterfaceInStream(IID_IDispatch, qobj, threadparam)
Leider gibt es dann für res (HRESULT) den Return 0x800A0062 bzw. -2146828190 . Ich vermute das liegt daran, dass die VBA Implementierung Code für Single Appartment Threading enthält und sich so einfach nicht Initialisieren lassen will. Die Bedeutung des Fehlercode ist etwas unbrauchbar an dieser Stelle.

Eine weitere Idee wäre es, falls das überhaupt geht tatsächlich eine C++ COM Komponente zu haben die eine Funktion enthält welche eine Referenz auf ein Interface bekommt. Aber das ist nur herumstochern ohne wirklich Ahnung zu haben was da wirklich vor sich geht wenn man das ganze aus VBA verwenden will. Wäre schön wenn sich das irgendwie debuggen lassen würde.

Möglicherweise muss man sich einfach darauf einlassen mit Teile und Herrsche den alten Code irgendwie zu zerlegen und wichtige Teile auszulagern. Ist nur fraglich wie man das am besten anstellt.
 
Zurück
Oben Unten