Results 1 to 3 of 3

Thread: [VB6] - Multithreading in VB6 part 1

Threaded View

  1. #1

    Thread Starter
    Frenzied Member
    Join Date
    Feb 2015

    [VB6] - Multithreading in VB6 part 1

    The most recent solution.

    Hello everyone! Many people wonder multithreaded programs written in VB6. Write multithreaded programs in VB6 quite real, I have many examples that I also published in my blog, but there are restrictions, one way or another can be circumvented. I consider this question in this post will not, and will consider more correct (in terms of programming in VB6) method of of multithreading - using objects. In this method, there are no restrictions, unlike threading Standart EXE, and has all the advantages of OOP. Also, I hasten to note that the IDE is not intended for debugging multithreaded programs, so to debug such programs in the IDE will not work. For debugging I use another debugger. You can also debug streams separately, and then collect the EXE.
    Using multiple threads, we have the ability to call methods asynchronously while maintaining synchronicity; ie we can call methods as well as in a separate thread, and in his. For example methods require large computational load should cause asynchronously and receive, at the end of the notice in the form of events. Such methods (properties) that work fast, you can call synchronously.
    One of the problems create a thread on VB6 in Standart EXE, is the inability to use WinAPI calls functions through Declare. Unlike the functions declared in a type library and entering the import, Declared-function after each call to set the properties of the object variable Err.LastDllError. This is done by calling the function __vbaSetSystemError of MSVBVM. Object Err, is thread-dependent, and the reference to it is in the thread local storage (TLS). For each thread must create its own object Err, otherwise the function call __vbaSetSystemError, runtime inquiry link from the storage, and we have it is not there (or rather there is 0) and will read the wrong address, as a consequence of crash.
    To prevent this behavior, you can declare a function in tlb, then the function will not be called __vbaSetSystemError. You can also initialize the Err object, create an object instance of the DLL in the new thread, then the runtime initializes the object itself. But to create a new object, you must first initialize the thread to work with COM, it needs to call CoInitialize (Ex), but we can not call functions. It is possible to declare a tlb (it only one), then all is fair; it can also be called from assembler code or in any other way. I always go to another. Why do I LastDllError? I can just simply call GetLastError himself when I need to. So I just find the address of the function __vbaSetSystemError and write the first instruction output from the procedure (ret). This is certainly not so nice, but reliably and quickly. You can have only one function CoInitialize, and then restore __vbaSetSystemError.
    Now we can call Declared-function in a new thread, which gives us endless possibilities. After creating the object (CreateObject), we can call its methods, properties, events receive from him, etc., but just a link between streams can not be passed because errors can occur because of concurrent access to data, etc. To send a link exists between threads marshaling. We will use the universal marshaller, because we ActiveX DLL has a type library. The principle of work, I will not describe in detail, it has a lot of articles online. The general sense is that instead of a direct call to the object, the RPC request to another computer / process / thread. For processing queries need to use the message loop, and once it happened, then the communication between threads is done through the posts.
    To test, I wrote a simple ActiveX DLL that lets you download a file from a network that has several methods and generates events.
    ' Класс MultithreadDownloader - класс загрузчика
    '  Кривоус Анатолий Анатольевич (The trick), 2014
    Option Explicit
    Public Enum ErrorCodes
    End Enum
    Private Declare Function InternetCloseHandle Lib "wininet" (ByRef hInternet As Long) As Boolean
    Private Declare Function InternetOpen Lib "wininet" Alias "InternetOpenW" (ByVal lpszAgent As Long, ByVal dwAccessType As Long, ByVal lpszProxy As Long, ByVal lpszProxyBypass As Long, ByVal dwFlags As Long) As Long
    Private Declare Function InternetOpenUrl Lib "wininet" Alias "InternetOpenUrlW" (ByVal hInternet As Long, ByVal lpszUrl As Long, ByVal lpszHeaders As Long, ByVal dwHeadersLength As Long, ByVal dwFlags As Long, ByRef dwContext As Long) As Long
    Private Declare Function InternetReadFile Lib "wininet" (ByVal hFile As Long, lpBuffer As Any, ByVal dwNumberOfBytesToRead As Long, ByRef lpdwNumberOfBytesRead As Long) As Integer
    Private Declare Function HttpQueryInfo Lib "wininet" Alias "HttpQueryInfoW" (ByVal hRequest As Long, ByVal dwInfoLevel As Long, lpBuffer As Any, ByRef lpdwBufferLength As Long, ByRef lpdwIndex As Long) As Long
    Private Declare Function CreateFile Lib "kernel32" Alias "CreateFileW" (ByVal lpFileName As Long, ByVal dwDesiredAccess As Long, ByVal dwShareMode As Long, lpSecurityAttributes As Any, ByVal dwCreationDisposition As Long, ByVal dwFlagsAndAttributes As Long, ByVal hTemplateFile As Long) As Long
    Private Declare Function WriteFile Lib "kernel32" (ByVal hFile As Long, lpBuffer As Any, ByVal nNumberOfBytesToWrite As Long, lpNumberOfBytesWritten As Long, lpOverlapped As Any) As Long
    Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
    Private Const INTERNET_OPEN_TYPE_PRECONFIG  As Long = 0
    Private Const INTERNET_FLAG_RELOAD          As Long = &H80000000
    Private Const HTTP_QUERY_CONTENT_LENGTH     As Long = 5
    Private Const HTTP_QUERY_FLAG_NUMBER        As Long = &H20000000
    Private Const CREATE_ALWAYS                 As Long = 2
    Private Const FILE_ATTRIBUTE_NORMAL         As Long = &H80
    Private Const INVALID_HANDLE_VALUE          As Long = -1
    Private Const GENERIC_WRITE                 As Long = &H40000000
    Public Event Complete()
    Public Event Error(ByVal Code As Long)
    Public Event Progress(ByVal Size As Currency, ByVal TotalSize As Currency, cancel As Boolean)
    Private mBufferSize As Long
    Private mError      As ErrorCodes
    Dim hInternet   As Long
    Public Property Get ErrorCode() As ErrorCodes
        ErrorCode = mError
    End Property
    Public Property Get BufferSize() As Long
        BufferSize = mBufferSize
    End Property
    Public Property Let BufferSize(ByVal Value As Long)
        If Value > &H1000000 Or Value < &H400 Then Err.Raise vbObjectError, "MultithreadDownloader", "Wrong buffer size": Exit Property
        mBufferSize = Value
    End Property
    Public Sub Download(URL As String, Filename As String)
        Dim hFile   As Long
        Dim hDst    As Long
        Dim fSize   As Currency
        Dim total   As Long
        Dim prgSize As Currency
        Dim cancel  As Boolean
        Dim buf()   As Byte
        If hInternet = 0 Then mError = NOT_INITIALIZE: RaiseEvent Error(mError): Exit Sub
        hFile = InternetOpenUrl(hInternet, StrPtr(URL), 0, 0, INTERNET_FLAG_RELOAD, 0)
        If hFile = 0 Then mError = Err.LastDllError: RaiseEvent Error(mError): Exit Sub
        If HttpQueryInfo(hFile, HTTP_QUERY_CONTENT_LENGTH Or HTTP_QUERY_FLAG_NUMBER, fSize, 8, 0) Then
            hDst = CreateFile(StrPtr(Filename), GENERIC_WRITE, 0, ByVal 0&, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0)
            If hDst = INVALID_HANDLE_VALUE Then mError = ERROR_CREATING_DST_FILE: RaiseEvent Error(mError): Exit Sub
            ReDim buf(mBufferSize - 1)
                If InternetReadFile(hFile, buf(0), mBufferSize, total) = 0 Then
                    mError = Err.LastDllError
                    RaiseEvent Error(mError)
                    InternetCloseHandle hFile
                    Exit Sub
                End If
                WriteFile hDst, buf(0), total, 0, ByVal 0&
                prgSize = prgSize + CCur(total) / 10000@
                RaiseEvent Progress(prgSize, fSize, cancel)
            Loop While (total = mBufferSize) And Not cancel
            CloseHandle hDst
            RaiseEvent Complete
            mError = Err.LastDllError
            RaiseEvent Error(mError)
        End If
        InternetCloseHandle hFile
        mError = OK
    End Sub
    Private Sub Class_Initialize()
        ' Инициализация WinInet
        hInternet = InternetOpen(StrPtr(App.ProductName), INTERNET_OPEN_TYPE_PRECONFIG, 0, 0, 0)
        mBufferSize = &H10000
    End Sub
    Private Sub Class_Terminate()
        ' Деинициализация
        If hInternet Then InternetCloseHandle hInternet
    End Sub
    The code basically simple, if you read the description of the API functions. When calling the method "Download", starts will download from time to time (depending on the size of the buffer) event is generated Progress. If an error occurs, an event "Error", and at the end of the "Complete". "BufferSize" - sets the size of the buffer, which is generated when filling event. Demo code and contains bugs.*
    Class I named "MultithreadDownloader", and the library "MTDownloader", respectively ProgID of the object - "MTDownloader.MultithreadDownloader". After compiling obtain a description of the interfaces through OleView, PEExplorer etc. In my example, CLSID = {20FAEF52-0D1D-444B-BBAE-21240219905B}, IID = {DF3BDB52-3380-4B78-B691-4138300DD304}. I also put a check "RemoteServerFiles" to get the output type library for our DLL, and will connect it instead of DLL for guaranteed start of the application.
    Last edited by The trick; May 4th, 2019 at 05:18 AM.

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts


Click Here to Expand Forum to Full Width