Page 2 of 2 FirstFirst 12
Results 41 to 62 of 62

Thread: Instantiate internal class object with name in string

  1. #41
    PowerPoster
    Join Date
    Jan 2020
    Posts
    5,541

    Re: Instantiate internal class object with name in string

    Quote Originally Posted by firehacker View Post

    Yesterday I enabled registration on VBStreets (without email confirmation)! Only 1 real human have registered so far, and zero(!) spammers. Apparently spammers excluded our forums from their lists. But they can strike back :-)

    So, hurry up! Registration on VBStreets is now opened!
    http://bbs.vbstreets.ru/ucp.php?mode=register

    I can disable it at any moment when spammers get clue that registration is open again. And it will remain disabled until I fix emailing subsystem and apply some modern antispam solution. If you going to register, please also take care to remember your password, since password recovery won't work (except for manual recovery by PMing or emailing me)!
    i'm chinese,I just registered as a member of this website. Let us learn together. Thank you for maintaining the forum.
    your website htm is error for language :
    <html xmlns="http://www.w3.org/1999/xhtml" dir="ltr" lang="en-gb" xml:lang="en-gb">

    <meta http-equiv="content-language" content="en-gb" />

    you need change to :
    <html dir="ltr" lang="ru-RU">
    <meta name="author" content="Government.ru">

    when i open registered url,it's not english,need a image button like "english version"
    and change sumbit button
    (now is :Reset Submit,need submit ,reset)

    ===================
    I was infected with the LOCKBIT virus in April 2020. The VB6 test code and some project files I wrote before were encrypted. Knowing that the virus has whitelisted Russia, Ukraine and other countries, China has been ignored. I really hate viruses and hackers. I wonder if there is any way to help me retrieve the encrypted files. Time has passed so long, the link domain name of the ransomware virus cannot be opened. Is there a way?
    Last edited by xiaoyao; Feb 14th, 2021 at 01:16 PM.

  2. #42
    Frenzied Member
    Join Date
    Aug 2020
    Posts
    1,869

    Re: Instantiate internal class object with name in string

    Hi firehacker, The trick and other vb experts,

    I'd like to know if it is possible to disguise a module as a class/object to use. Because I want to use CallByName in the module, for example:

    CallByName(Module1, sFuncName, VbMethod). Thanks!

  3. #43
    PowerPoster
    Join Date
    Feb 2017
    Posts
    5,722

    Re: Instantiate internal class object with name in string

    AFAIK procedure names in bas modules don't get compiled. So you can't call them by name.

  4. #44

  5. #45
    Frenzied Member
    Join Date
    Aug 2020
    Posts
    1,869

    Re: Instantiate internal class object with name in string

    Quote Originally Posted by Eduardo- View Post
    AFAIK procedure names in bas modules don't get compiled. So you can't call them by name.
    Hi Eduardo, thank you for the information.

    Since firehacker can instantiate a class by name string, then he should have a way to use CallByName in vb-modules.

  6. #46
    Frenzied Member
    Join Date
    Aug 2020
    Posts
    1,869

    Re: Instantiate internal class object with name in string

    Quote Originally Posted by The trick View Post
    Only in IDE.
    Hi The trick, do you mean that it is impossible to use CallByName in a module in IDE?

  7. #47
    PowerPoster
    Join Date
    Feb 2017
    Posts
    5,722

    Re: Instantiate internal class object with name in string

    Quote Originally Posted by SearchingDataOnly View Post
    Hi The trick, do you mean that it is impossible to use CallByName in a module in IDE?
    It is impossible compiled. Because the module's procedure names are not saved.

  8. #48
    PowerPoster Ellis Dee's Avatar
    Join Date
    Mar 2007
    Location
    New England
    Posts
    3,530

    Re: Instantiate internal class object with name in string

    You can reference controls by name. For example, a textbox named Text1 can be referenced in code by name like this:

    FormName("Text1").Text = "Hello, world!"

    Not sure if that helps or not.

  9. #49
    PowerPoster
    Join Date
    Jan 2020
    Posts
    5,541

    Re: Instantiate internal class object with name in string

    Quote Originally Posted by Elroy View Post
    If you "think through" the code below, it becomes clear that the secret to instantiating an object with a string in an executable is all wrapped up in the "__vbaNew" call found in the msvbvm60.dll.


    And Voila, reflection for classes fully implemented in VB6.

    Should I post this in the CodeBank?

    Enjoy,
    Elroy

    EDIT1: It turns out the lpObjectInfo I had wouldn't work because it changes value when things are moved into memory. Therefore, all of these procedure listed above are needed to instantiate an object from one of your internal classes from a string. Also, just FYI, once instantiated, TypeName works as expected.
    IdeCreateInstance2 like IdeCreateInstance?

    [CODE
    ]Dim TempObj As IUnknown
    Private Function IdeCreateInstance2(ByVal Class As String) As IUnknown
    ' Only for IDE. '
    EbExecuteLine StrPtr("NameBasedObjectFactory.OneCellQueue2 New " & Class), 0, 0, 0
    '
    Set IdeCreateInstance2 = TempObj
    Set TempObj = Nothing
    If IdeCreateInstance2 Is Nothing Then
    Err.Raise 8, , "Specified class '" + Class + "' is not defined."
    Exit Function
    End If
    End Function
    Private Sub OneCellQueue2(ByVal refIn As IUnknown)
    Set TempObj = refIn
    End Sub[/CODE]

  10. #50

    Re: Instantiate internal class object with name in string

    Quote Originally Posted by SearchingDataOnly View Post
    if it is possible to disguise a module as a class/object to use. Because I want to use CallByName in the module, for example:
    It is possible with some notices/preconditions.

    Generally you can implement an equivalent of CallByName for plain procedures if you have a TLB describing these precedures. I should mention again, that TLB can not only describe COM interfaces and their methods, COM classes (co-classes) and their implemented interfaces, dispinterfaces, enums and constants, but plain modules and their members (procedures) can be described by TLB as well. Having combination of information about number and type of arguments, return type and procedure calling convertion is enough to implement a mechanism that can construct appropriate stack frame from original VARIANTs, pass executing to a procedure being called and clean-up stack afterwards.

    Actually, we already have such mechanism within MSVBVM60.DLL as part of a so-called expression server. But it's major disadvantage that I discovered during reverse-engineering is that it limits itself to use only the TLB that is built into MSVBVM60.DLL itself. As a result, you can only call functions for MSVBVM: MsgBox, Beep, Abs, DoEvents, InputBox and any of others. You cannot call procedures from arbitrary executable modules (this limitation seems a bit artificial).

    So you have to either implement such mechanism yourself (not so hard), or hack an existing expression server facility by applyting patches (and taking care to respect all the existing versions of MSVBVM60 and their differences).

    For practical use, if you are going to call procedures not from some other DLLs (equipped with a TLB describing all public proecdures), but from your own project, in addition to implementing such mechanism you need to do two extra things:

    1. Make a TLB describing all your modules and their public procedures. You can use MIDL and compile TLBs manually, or you can make an Add-in that will construct TLBs using special CreateTypeLIb + ICreateTypeLib.
    2. Force VB to put these procedures into export table. Again, you can do it manually using those undocumented switches that I discovered ([VBCompiler] and LinkSwitches) or do it as part of your add-in.


    Like it was done in my CreateInstanceByClassName, you'll likely need to make special handling for the case when your project is trying to call a procedure from the same project (or other project from the current project group) rather than from compiled executable.

  11. #51
    Frenzied Member
    Join Date
    Aug 2020
    Posts
    1,869

    Re: Instantiate internal class object with name in string

    Very valuable knowledge, thank you very much, firehacker.

  12. #52
    PowerPoster wqweto's Avatar
    Join Date
    May 2011
    Location
    Sofia, Bulgaria
    Posts
    6,203

    Re: Instantiate internal class object with name in string

    Quote Originally Posted by SearchingDataOnly View Post
    Very valuable knowledge, thank you very much, firehacker.
    Valuable but not actionable IMO.

    If you are going to produce a type library with so much intricate info at run-time then you might as well just fill a Collection with keys on procedure names and values a pfn's (AddressOf MyProc) so you can just use DispInvoke API to emulate CallByName on a module.

    cheers,
    </wqw>

  13. #53
    Frenzied Member
    Join Date
    Aug 2020
    Posts
    1,869

    Re: Instantiate internal class object with name in string

    Quote Originally Posted by wqweto View Post
    Valuable but not actionable IMO.

    If you are going to produce a type library with so much intricate info at run-time then you might as well just fill a Collection with keys on procedure names and values a pfn's (AddressOf MyProc) so you can just use DispInvoke API to emulate CallByName on a module.

    cheers,
    </wqw>
    Ok, I'll try your method. Thank you, wqweto.

  14. #54
    PowerPoster
    Join Date
    Jan 2020
    Posts
    5,541

    Re: Instantiate internal class object with name in string

    How to use code to dynamically add a new class or add several interfaces to the class? Or replace the original class, the OCX interface, with a function address in a module, or replace it with an interface in the Class1 class?

  15. #55
    Addicted Member
    Join Date
    Dec 2021
    Posts
    144

    Re: Instantiate internal class object with name in string

    Quote Originally Posted by firehacker View Post
    Thank you for your wellcome.

    During last ten years I spend thousands of hours reverse-engineering classic VB. I have so much unique information about its internals revealed during reverse-engineering...

    I am in the middle of starting a new crowd-sourced project: it will be very similar to what ReactOS people are trying to do with Windows NT. It's not just a number of ephemeral wishes of reviving or recreating classic VB. There are some strong ideology, a methodology of work, specially designed toolkit and a toolchain behind my initiative.

    I know there are some initiatives to re-make classic VB: people are starting from scratch and making their own IDEs and compilers utilizing completely different architectures and principles. For me such initiatives look like someone's attempt to create completely new operating system: you'll have no software for your new OS, no drivers, no community. The only reason why ReactOS has a chance to have any decent success is that they claim that software and drivers made for Windows NT will work (or have to work) under ReactOS and vise versa. They claim some degree of binary compatibility.

    Likewise, I am targeted at recreating source files of original classic VB. Having such sources-pack will open a way of making improvements and releasing new versions of classic VB, and it will be higly compatible. All the existing VBP-projects will be compatible with hypothetical new IDE. All the existing binaries will be binary compatible with new recompiled msvbvm60.dll (with a number of improvements).

    When it comes to reverse-engineering and ensuring high degree of compatibility, one would ask you: how can you surely tell that you reverse-engineered some product correctly and compiling your code (results of reverse-engineering) won't break compatibility? How can you guarantee that your half of a million lines of source code will behave exactly the same way as original product is supposed to work? What if you misunderstood some algorithm and your implementation is seems to be similar, but is not strictly identical?

    The key aspect of my initiative is a special methodology that ensures that reverse-engineered source code is 100% equivalent to
    the original source code (which Microsoft will never give us). I am talking about functional equivalence, I don't mean having exactly same name for procedures and variables (however, presence of debug symbols for VB6.EXE and MSVBVM60.DLL allows to achieve partial variable/functions/methods/classes/types match too!).

    It isn't just a methodology: I have developed some unique tools that are used as part of the workflow by this methodology. The key utility is called ANTILEGO, but that requires an additional article on what the LEGO and LEGOfication process is (in a few words: it is a secret build stage that was used inside Microsoft when building all their binaries, it was a predecessor of PGO/LTCG in modern Microsoft's linkers, but it isn't the same as PGO/LTCG).

    You probably heard of TDD as a development methodology. TDD stands for Test-Driven Development. In my case an important part of the declared methodology can be described as TDRE (Test-Driven Reverse Engineering). And, as I said, it's not just an ideology of applying some tests after each commit: I have a number of special utilities which form a toolchain which is a workhorse of the whole TDRE approach. These includes custom disassembling and sophisticated machine code analysis engines which can tell you if the machine code compiled from your recreated source file is functionally or even byte-to-byte equivalent of the original binary, released by Microsoft.

    It is planned that the number of enthusiasts will work in a distributed manner (using Git), reversing the product part-by-part and proposing their results via pull-request mechanism. A special web site is going to be a coordination platform, showing the overall progress, a map of RCE coverage, having tons of documentation and serving as a task-tracker/bug-tracker as well as a hosting for Git repository.

    The repository itself exists for a long period of time, but it isn't hosted anywhere in public repository hosting currently:


    First commits were done 6 years ago (but the whole work started years before than, I just didn't use git):


    Statistics and progress are displayed by local utilities and scripts:

    (This is an old picture, BTW, but it shows how I am planning to display coverage map on the projects main page in the future)

    Lots of functions from MSVBVM60.DLL, VB6.EXE and VBA6.DLL are reversed back to C/C++ code. Here is example of VBACollection::Remove (which is implementation of Remove method of Collection class in VB/VBA):

    It is not just reversed, it is reversed the way that it's not simply functional equivalent of original code, but if you try to compile it with proper version of CL.EXE (Microsoft C/C++ compiler) with a proper set of command like switches, it gives a sequence of machine commands byte-to-byte identical to the sequence command that you can find in the binary released by Microsoft. And lots of functions and members are reverse-engineering this way, and my goal is to cover everything (but that is not a mandatory requirement if you're going to recompile runtime, for example).

    Thousands of "anonymous" functions from VBA6.DLL that are not covered by any debug symbols and whose names and activity were absolutely unknown, are discovered and reverse-engineered, including lots of functions responsible for source code parsing, compilation:

    Many of them are described this way (prototype, multiversionality info, short description, pseudocode):

    (I am showing simplest one, because otherwise it wouldn't fit one screen. Also, it is in Russian, as you can see, but about half of docs are written in English)

    Lots of internal structures are reversed and described:



    Tons of documents describing how various internal components of Visual Basic work and are organized:




    I am going to make all this information publicly available as part of VB-RCE project. However, I don't want to do that until it reaches some critical mass, which, I believe, it still hasn't reached. Not all tools are 100% polished. Not all parts of my TDRE workflow is automated or works as I wish.

    However, over the years of reverse-engineering I made a number of interesting discoveries.
    • Did you know about undocumented keys in VBP-file: C2Switches and LinkSwitches under [VBCompiler] section? You probably did, because I was the first who described it here in November 2011 and now you can google many mentions of VBCompiler section, but back in the days this was unique information.
    • Did you know that one bit separates you from this kind of syntax?

    • Did you know that there is hidden analogue of CallByName but for plain functions/API instead of object methods in VB? As well as a hidden basis for expression evaluation engine?
    • Did you know that that there is undocumented mechanism allowing connecting from external software to VB IDE or to compiled VB project and tracing events (including step-by-step tracing, which, however, works only in IDE mode)?
    • Do you know that a number of attempts to rename any variable (or any entity) is limited to 32K times due to the nature of how code entities are represented internally?


    I have started a series of articles describing my explorations and reverse-engineering results. Unfortunately for English-speaking people, these articles are in Russian. It's called VB Internals and is published on VBStreets.

    Currently there are only two ready articles:


    The first one covers very important topic that Visual Basic is actually a hybrid of two products (Ruby and EB) glued together. It is very important to understand what Ruby and EB are, how they were born and where the boundaries between them lay prior to diving into reverse-engineering or any explorations. It should be considered an introduction article for the whole series.

    The second one tries to evaluate number of C/C++/assembler source files (sources of VB6 itself) and the overall number of lines in these files basing on the information that can be obtained by extraction raw data from DBG/PDB files and consequent sophisticated postprocessing.

    The third is upcoming: it will bring more details on how sources are organized and how various components of IDE/runtime is distributed across source files.

    Here is the preliminary list of topics that I plan to highlight in the series. (Google translate)
    Hello firehacker,

    Can you provide me a working c/c++ sample project for vb6 multithreading like you have have shown CreateBasicThread() in vbstreets.ru .

    Thanks

  16. #56

    Thread Starter
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    10,918

    Re: Instantiate internal class object with name in string

    Hey smkperu, how about starting your own thread instead of hijacking mine from 4+ years ago???

    Also, I'm not sure Firehacker spends much time around here. You'd probably be better off targeting your questions like this to The Trick (in your own thread).
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  17. #57
    Addicted Member
    Join Date
    Dec 2021
    Posts
    144

    Re: Instantiate internal class object with name in string


  18. #58

    Re: Instantiate internal class object with name in string

    Quote Originally Posted by Elroy View Post
    Also, I'm not sure Firehacker spends much time around here. You'd probably be better off targeting your questions like this to The Trick (in your own thread).
    No, I get notifications via email, so I can react quickly on questions addresses to me asked in this thread. By the way, it's better to target questions relating to something previously posted on bbs.vbstreets.ru right there on bbs.vbstreets.ru Even if it is russian-speaking forum, it is absolutely okay if you write your post in English. Registration is still opened. Feel free to contact me if you have any problems with switching VBStreets UI from Russian to English. Someone have mentioned he had some issues with that.

    Quote Originally Posted by smkperu View Post
    Hello firehacker,
    Can you provide me a working c/c++ sample project for vb6 multithreading like you have have shown CreateBasicThread() in vbstreets.ru .
    I don't understand clearly what do you mean by "sample project". Ten years ago I announced that contrary to the popular opinion that VB6 does not support multithreading at all (since using CreateThread() usually causes new thread to crash the whole process) under the hood MSVBVM is thread-safe and multithreading-aware at every step and it's only a question of how to create and initialize a new thread propeprly — and I discovered a way of proper thread creation and initialization.

    On VBStreets we use the term "brick" to call ready-to-use module (or bunch of modules) which we post on VBStreets (in specific forum aka "Brick Factory") and which can be downloaded by people and plugged into some VB-project without any tweaks and modifications. Initially, I was planning to make a brick which brings support of multithreading into VB. However, I changed my mind very: implementing such thing purely in form of VB code would be rather hard and tricky. I decided to write CreateBasicThread in C/C++ in form of a separate DLL rather then writing it in VB as a simple VB module.

    So, I announced that I am going to develop this DLL and publish it. This was called "Stable Multithreading Brick", even though it wasn't a "brick" in its initial sense anymore. I haven't planned to make this DLL open-source: not that I wanted to conceal a number of secrets and "know hows" (even though there was some), but to prevent a number of incompatible forks from 3rd party enthusiasts to appear.

    At some point I make difficult decision to stop development of stable multithreading brick aka vbthrds.dll. The main and the only reason for that is the following fact:

    To make things work properly my DLL needed to work with significant number of functions/variables/methods from MSVBVM which are neither exported nor live at constant locations (i.e. their addresses change with each new version of MSVBVM60.DLL). Functions and variables were living at arbitrary addresses (version-dependent), the machine code of that functions was changing slightly from version to version which was making it harder to find necessary functions by doing a so-called "signature search". Sizes of structure and offsets of fields of classes/structs were also changing from version to version. I had to write code that locate each function, each variable, determines version of MSVBVMxx.DLL and guess size of structs and offsets of fields accordingly.

    I wasn't satisfied with the idea that my vbthrds.dll will come with "to use this library you must use that version of MSVBVM60.DLL and only that exactly version" warning. I was going to support ALL versions (builds, releases) of MSVBVM60.DLL that exist (so I collected all existing versions and examined them all). Furthermore, I was going to support MSVBM50.DLL for those who use VB5.

    Some people love to use packers which embed MSVBVMxx.DLL into main EXE, and some people prefer to rename MSVBVMxx.DLL into something obscure and modify import table of the main EXE/DLL accordingly (to hide the fact they are using VB or I don't know what is their goal exactly). Given that facts, you should be prepared for the situation when multiple DIFFERENT versions of MSVBVM co-exist in the address space of our process. Even without these rare cases with packers or renaming of MSVBVM, you can simply get MSVBVM60.DLL and MSVBVM50.DLL both loaded into address space of your process if your main EXE (compiled using VB6) is using, for example, an OCX-control that is written in VB to (but compiled using VB5). Both may invoke CreateBasicThread: they are doing that independently, but from our point of view (vbthrds.dll) they use different runtimes (MSVBVM libraries). So, to be more robust, you can't just find all required internal entities. As an author of vbthrds.dll I had to write a mechanism which keeps track of all runtimes concurrently loaded into address space of our process and for each module (Windows terminology, not "module" in VB terminology) we need to locate all of required internal (not exported) entities and calculate all sizes and offsets.

    Even with all that complex stuff, we still had a chance of Microsoft releasing a new version of MSVBVM60.DLL which my "multithreading brick" won't deal with. Because some minor but still significant change can broke my mechanisms responsible for locating variables/functions/fields offsets.

    This work together with another initiative (known as "msvbvm61.dll" — an idea of patching original MSVBVM60.DLL and embedding new functions/classes/controls and adding support of Unicode for all embedded controls/functions) led me to the conclusion that all such things require referencing LOTS of internal MSVBVM's functions and vars (which are not explorted, again) and sophistiacted mechanisms required to locate all of them are so hard and exhausting to make and support (they need to be prepared to work with any of existing versions of MSVBVM, and there were more than 10 of builds) comparing to how simple it would be if you have original MSVBVM60.DLL sources and you can just link to any of that internal functions or add any field to any structure or just remove any line of code instad of patching/intercepting machine code.

    So I decided to focus on a project which I referenced above as "VB-RCE". All the wishes and desires that was there around VB6 for years (full Unicode support, multithreading, new functions and embedded classes (like Collection class)) becomes way easy to make if you doing it at the level of source code rather than by patching/intercepting compiled binaries. Even if reverse engineering of MSVBVM60 seems to be extremely hard task, it opens up huge perspectives. And, yet again, you haven't to reverse/decompile 100% of code before you can do any (even simpliest) modification.

    That doesn't cancels the fact that vbthrds.dll was good idea and I had working prototypes. I just stopped its development in the name of switching to more comprehensive approach.

    But vbthrds.dll wasn't open-source and I haven't been finished. I can't remenber if I event posted semi-functional (alpha) builds of vbthrds.dll.

    And you asking me for a sample C/C++ project. What do you mean? Do you want me to publish the whole source code of incomplete vbthrds.dll? Do you wan't me to post source code of CreateBasicThread function?

    Here is source code of CreateBasicThread as it is with all its debug/temporary/raw snippets:
    Code:
    HANDLE WINAPI CreateBasicThread(LPSECURITY_ATTRIBUTES   lpThreadAttr, 
                                    LPTHREAD_START_ROUTINE  lpStartProc,
                                    LPVOID                  lpAuxParam)
    {
        HMODULE                     hProjModule;
        SMT_VBPROJECT_DESCRIPTOR*   pProjDescriptor = NULL;
        SMT_VBRUNTIME_DESCRIPTOR*   pRtDescriptor   = NULL;
    
        //
        // Определяем модуль проекта, которому принадлежит процедура.
        //
    
        hProjModule = PeFindContainerOfSymbol(lpStartProc);
    
    
        if(hProjModule == NULL)
        {
            // TODO: Set Error code
            return NULL;
        }
    
        //
        // Просим менеджер кеша предоставить нам дескриптор для данного проекта.
        // Менеджер кеша либо создаст новый дескриптор, либо вернёт уже существующий.
        //
        
        SmtCmProvideCacheEntriesForHmod(hProjModule,
                                       &pProjDescriptor,
                                       &pRtDescriptor);
    
        if(pProjDescriptor == NULL)
        {
            // TODO: Set error code
            return NULL;
        }
    
        //
        // DBG: Патчим проджект, TODO: fix it
        //
    
        SMT_CTHREADPOOL_SFTABLE &pctp = pRtDescriptor->Internals.SFTables.CCThreadPool;
        Project*        pProj;
        CThreadPool*    pRby_ThreadPool = pRtDescriptor->Internals.Rby_ThreadPool;
    
        pRby_ThreadPool; 
        pProj = (pRby_ThreadPool->*pctp.FindCachePepiProj)(pProjDescriptor->pProjInfo);
        
        ((DWORD*)pProj)[37] = 0;
    
        
        SMT_START_PARAMETERS*   pStartParams;   
        DWORD                   fdwSystemFlags;
        HANDLE                  hNewThread;
        DWORD                   iNewThreadId;
        CONTEXT                 ntctx;
    
        //
        // Мы всегда создаём новый поток в замороженном состоянии, чтобы сформировать 
        // структуру SMT_START_PARAMETERS в его стеке. Это единственный способ передать 
        // потоку структуру так, чтобы потом не заботиться об её уничтожении.
        //
        // Мы не можем переложить ответственность за уничтожение этой структуры на новый
        // поток, потому что нет гарантии, что его вообще когда-нибудь разморозят.
        // Мы не можем сами заботиться об её уничтожении, потому что для этого потребовалось
        // бы отслеживать терминацию всех потоков и держать большую таблицу указателей на 
        // ждущие уничтожения структуры.
        // 
    
        fdwSystemFlags = CREATE_SUSPENDED; /* TODO: fix it */
        
        hNewThread = CreateThread(lpThreadAttr,
                                  0, /* TODO: Fix it */
                                  SmtIntermThreadProc,
                                  (LPVOID)SMT_SPB_FAULT_MARKER,
                                  fdwSystemFlags,
                                  &iNewThreadId);
    
        if(!hNewThread) return hNewThread;
    
        //
        // Получаем контекст нового потока. Затем сдвигаем указатель стека так, 
        // чтобы там можно было разместить структуру. И заменяем контекст потока
        // новым, изменённым.
        // 
    
        ntctx.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER;
        
        if(!GetThreadContext(hNewThread, &ntctx))
        {
            TerminateThread(hNewThread, 0);
            CloseHandle(hNewThread);
            return NULL;
        }
    
        ntctx.Esp -= sizeof(SMT_START_PARAMETERS);
        pStartParams = (SMT_START_PARAMETERS*)ntctx.Esp;
        
    
        //
        // Win32 API — тупейшая обёртка над Native API, которая убивает кучу востребованных 
        // возможностей Native API. В частности, мы не можем передать создаваемому потоку 
        // структуру большого размера, мы можем только передать указатель на структуру, 
        // но саму структуру мы никак не можем «прикрепить» к потоку. Если бы CreateThread 
        // позволяла создать в новом стеке некий room, с заведомо известным адресом, который 
        // бы можно было передать в CreateThread в качестве lpParam... А так нам приходится 
        // искусственно воссоздавать такую функциональность. Методика основана на знании 
        // недокументированных особенностей функционирования ядра Windows, а именно функции 
        // BaseInitializeContext. Если она когда-нибудь изменится, то трюк не сработает, 
        // поэтому нужно проверить, работает ли BaseInitializeContext так же, как ожидается.
        //
    
        if(ntctx.Eax == (DWORD)SmtIntermThreadProc &&
           ntctx.Ebx == SMT_SPB_FAULT_MARKER)
        {
            // BaseInitializeContext работает как ожидалось.
    
            ntctx.Ebx = (DWORD)pStartParams;
        }
        else
        {
            //
            // BaseInitializeContext работает иначе, а значит новому потоку самому придётся 
            // искать в своём стеке структуру SMT_START_PARAMETERS, потому что её адрес потоку 
            // передать никак не удалось. Формируем маркер для поиска...
            //
    
            SMT_START_PARAMETERS_MARKER* pSpbMarker;
    
            ntctx.Esp -= sizeof(SMT_START_PARAMETERS_MARKER);
            pSpbMarker = (SMT_START_PARAMETERS_MARKER*)ntctx.Esp;
            *pSpbMarker = SmtStartParametersBlockMarker;
        }
    
        ntctx.Esp -= sizeof(DWORD) * 64; // Отступ ради безопасности
    
        if(!SetThreadContext(hNewThread, &ntctx))
        {
            TerminateThread(hNewThread, 0);
            CloseHandle(hNewThread);
            return NULL;
        }
        
        //
        // Теперь структура лежит на стеке созданного потока, и мы можем спокойно её 
        // заполнять стартовыми параметрами.
        //
    
        pStartParams->UserThreadProc = lpStartProc;
        pStartParams->UserThreadParam = lpAuxParam;
        pStartParams->Project = pProjDescriptor;
        pStartParams->Runtime = pRtDescriptor;
    //  pStartParams->Flags = 0; /* TODO: Fix it */
    
        BOOL bNeedTlsEmulation = TRUE;
    
    
        if(bNeedTlsEmulation)
        {
            //DebugBreak();
            LoadLibrary("vbthrds.dll");
            LoadLibrary("vbthrds.dll");
            LoadLibrary("vbthrds.dll");
            LoadLibrary("vbthrds.dll");
        
            EhbtlsAttachToThread(GetCurrentThread()); 
            EhbtlsEnableSection(pProjDescriptor->TlsEmuSection); 
    
            
        }
    
        //
        // Теперь наконец даём новому потоку работать.
        //
    
        
        
        ResumeThread(hNewThread); 
    
        return hNewThread;
    
    }
    Comments are in Russian, sorry. I presume Google Translate will give poor results as it always does with comments within code.

    Here is SmtIntermThreadProc which is a thunk to caller-supplied ThreadProc and is responsible for proper initialization of runtime library for a newly created thread from context of that new thread:
    Code:
    DWORD WINAPI SmtIntermThreadProc(LPVOID lpParameter)
    {
        DWORD                   ret;
        SMT_START_PARAMETERS*   psp;
        CThreadPool*            Rby_ThreadPool;
    
        if(lpParameter == (LPVOID)SMT_SPB_FAULT_MARKER)
        {
            //
            // Наш родитель не смог передать нам указатель на структуру со
            // стартовыми параметрами из-за изменений в BaseInitializeContext.
            // Ищем её в своём стеке вручную.
            //
    
            psp = SmtFindSpbOnThreadStack();
        }
        else
        {
            psp = (SMT_START_PARAMETERS*)lpParameter;
        }
    
    
        Rby_ThreadPool = psp->Runtime->Internals.Rby_ThreadPool;
        
        SMT_CTHREADPOOL_SFTABLE& ctpm = psp->Runtime->Internals.SFTables.CCThreadPool;
    
        BOOL                bNeedTlsEmulation = TRUE;
        EHBTLS_FRAME        TlsEmuFrame;
    
    
        if(bNeedTlsEmulation)
        {
            EhbtlsEnterThreadSafeRegionEx(&TlsEmuFrame, psp->Project->TlsEmuSection);
        }
        
        //
        // Вызываем RbyThreadPool.InitDllAccess для инициализации контекста 
        // данного проекта для нового потока.
        // 
    
        (Rby_ThreadPool->*ctpm.InitDllAccess)(psp->Project->pProjInfo, psp->Project->hModule);
    
    
    
        //
        // А теперь вызываем пользовательскую ThreadProc.
        //
        
        ret = psp->UserThreadProc(psp->UserThreadParam); 
    
    
        CThreadData*    pMyData;
        CMsoComponent*  pMsoComp;
        
        (Rby_ThreadPool->*ctpm.GetThreadData)(&pMyData);
    
        if(pMyData)
        {
            pMyData->ThreadAction->ThreadType = VbExeThreadWorker;
            *(HANDLE*)((PBYTE)pMyData->ThreadAction + 0x18) = CreateEvent(0, 0, 0, 0);
    #ifndef SMT_SUPPORT_OLD_RUNTIMES
            pMsoComp = pMyData->MsoComponent;
    #else
            pMsoComponent = *(CMsoComponent**)
                ((BYTE*)pMyData + psp->Runtime->Internals.offset_MsoCmpFldInThreadData); 
    #endif
    
            if(pMsoComp)
            {
                (pMsoComp->*psp->Project->Runtime->Internals.SFTables.CCMsoComponent.PushMsgLoop)(-1);
            }
        }   
        
        //
        // Деинициализируем контекст потока.
        //
    
        (Rby_ThreadPool->*ctpm.TerminateThread)();
        
        if(bNeedTlsEmulation)
        {
            EhbtlsLeaveThreadSafeRegion(&TlsEmuFrame);
        }
    
        ExitThread(ret);
        return ret; // Этого никогда не произойдёт
    }
    This two subroutines are only small part of the whole vbthrds.dll.

    One of the cruicial components of EHBTLS: exception-handling based TLS. Global variables of single-threaded application are allocated on data section of executable image. However, for multithreaded VB program it isn't acceptable: each thread must have its own copy of the set of all global variables. Since project types such as "ActiveX DLL", "ActiveX Control" and "ActiveX EXE" implies that VB-code can by run in context of multiple different threads, VB (from IDE/compiler point of view) offers "Threading model" option under "Project settings", which can be either "Single Threaded" or "Apartament threaded", and when project is configured to use "Apartament threaded", global variables are allocated either in data section (same as when compiling Single Threaded projects) for main thread of multithreaded project, or in per-thread block of memory for any thread other than main thread. To locate proper instance of global variable VB-code calls vbaAptOffset which returns offset from variable's address in data section to its actual location in per-thread memory block. For the main thread this offset is 0, but for other threads it is a non-zero value so that adding this offset to a address of the variable within data section gives an actual address of this global var instance for a particular thread.

    But VB6 IDE and compiler does not allow you to set "Apartament threading" model for projects of type "Standard EXE". Thus, trying to spawn multiple threads within Standard EXE project will lead to a situation where all global variables are shared between all threads. This is barely acceptable for Byte/Integer/Long variables, but is absolutely unacceptable for objects and Variants. To overcome this, my library used SEH/VEH to detect reads and writes from/to global variables and redirect it to proper per-thread memory block containing per-thread copies of all global variables of the project.

    Without this you need to be very careful and avoid using global variables. But you can't avoid it completely: App, Forms, Printer, Printers, Clipboard, Screen and so on are actually members of an app-global-object (you can find a class named "Global" in Object Browser) which is referenced via unnamed global variable. So need to emulate TLS for "Standard EXE" projects which might use native TLS facilities of VB but which are actually don't use it due to limitations of IDE/compiler.

    Yet another part of vbthds.dll is SMS: it has nothing to do with mobile phones, it stands for Sub Main suppression. If your project has "Startup Object" = "Sub Main" (project properties), Sub Main() gets called each time you create a new thread using my DLL, which is usually not what you want. You can't easily determine from the Sub Main() whether it was called because a program startup or it is called as a side-effect of spawning additional thread. So there was special trickery to suppress invocation of "Sub Main" when new threads were created and initialized via my DLL.

    Yet again, these two are great examples of how complex the simple problem becomes when you have to solve it on the wrong level. Having access to the source code of VB (the whole product including IDE/compiler and runtime) and unlocking "Theading Model" for "Standard EXE" or just avoiding unwanted invocation of Main() by simple if-block within MSVBVM code would be much simplier than emulating TLS for complete binary compiled without TLS support or suppressing a Sub Main invocation without patching MSVBVM project startup logic.

    It's like having a hand or any other organ: when you born you get your hands "for free" and you take it for granted. But if you loose your hand, it costs you enormous amount of effort and money and even the best of the best prosthetic arms which are very expensive can bring you only limited amount of what you lost.
    Last edited by firehacker; Jan 16th, 2022 at 05:18 AM.

  19. #59
    Addicted Member
    Join Date
    Dec 2021
    Posts
    144

    Re: Instantiate internal class object with name in string

    Quote Originally Posted by firehacker View Post
    No, I get notifications via email, so I can react quickly on questions addresses to me asked in this thread. By the way, it's better to target questions relating to something previously posted on bbs.vbstreets.ru right there on bbs.vbstreets.ru Even if it is russian-speaking forum, it is absolutely okay if you write your post in English. Registration is still opened. Feel free to contact me if you have any problems with switching VBStreets UI from Russian to English. Someone have mentioned he had some issues with that.



    I don't understand clearly what do you mean by "sample project". Ten years ago I announced that contrary to the popular opinion that VB6 does not support multithreading at all (since using CreateThread() usually causes new thread to crash the whole process) under the hood MSVBVM is thread-safe and multithreading-aware at every step and it's only a question of how to create and initialize a new thread propeprly — and I discovered a way of proper thread creation and initialization.

    On VBStreets we use the term "brick" to call ready-to-use module (or bunch of modules) which we post on VBStreets (in specific forum aka "Brick Factory") and which can be downloaded by people and plugged into some VB-project without any tweaks and modifications. Initially, I was planning to make a brick which brings support of multithreading into VB. However, I changed my mind very: implementing such thing purely in form of VB code would be rather hard and tricky. I decided to write CreateBasicThread in C/C++ in form of a separate DLL rather then writing it in VB as a simple VB module.

    So, I announced that I am going to develop this DLL and publish it. This was called "Stable Multithreading Brick", even though it wasn't a "brick" in its initial sense anymore. I haven't planned to make this DLL open-source: not that I wanted to conceal a number of secrets and "know hows" (even though there was some), but to prevent a number of incompatible forks from 3rd party enthusiasts to appear.

    At some point I make difficult decision to stop development of stable multithreading brick aka vbthrds.dll. The main and the only reason for that is the following fact:

    To make things work properly my DLL needed to work with significant number of functions/variables/methods from MSVBVM which are neither exported nor live at constant locations (i.e. their addresses change with each new version of MSVBVM60.DLL). Functions and variables were living at arbitrary addresses (version-dependent), the machine code of that functions was changing slightly from version to version which was making it harder to find necessary functions by doing a so-called "signature search". Sizes of structure and offsets of fields of classes/structs were also changing from version to version. I had to write code that locate each function, each variable, determines version of MSVBVMxx.DLL and guess size of structs and offsets of fields accordingly.

    I wasn't satisfied with the idea that my vbthrds.dll will come with "to use this library you must use that version of MSVBVM60.DLL and only that exactly version" warning. I was going to support ALL versions (builds, releases) of MSVBVM60.DLL that exist (so I collected all existing versions and examined them all). Furthermore, I was going to support MSVBM50.DLL for those who use VB5.

    Some people love to use packers which embed MSVBVMxx.DLL into main EXE, and some people prefer to rename MSVBVMxx.DLL into something obscure and modify import table of the main EXE/DLL accordingly (to hide the fact they are using VB or I don't know what is their goal exactly). Given that facts, you should be prepared for the situation when multiple DIFFERENT versions of MSVBVM co-exist in the address space of our process. Even without these rare cases with packers or renaming of MSVBVM, you can simply get MSVBVM60.DLL and MSVBVM50.DLL both loaded into address space of your process if your main EXE (compiled using VB6) is using, for example, an OCX-control that is written in VB to (but compiled using VB5). Both may invoke CreateBasicThread: they are doing that independently, but from our point of view (vbthrds.dll) they use different runtimes (MSVBVM libraries). So, to be more robust, you can't just find all required internal entities. As an author of vbthrds.dll I had to write a mechanism which keeps track of all runtimes concurrently loaded into address space of our process and for each module (Windows terminology, not "module" in VB terminology) we need to locate all of required internal (not exported) entities and calculate all sizes and offsets.

    Even with all that complex stuff, we still had a chance of Microsoft releasing a new version of MSVBVM60.DLL which my "multithreading brick" won't deal with. Because some minor but still significant change can broke my mechanisms responsible for locating variables/functions/fields offsets.

    This work together with another initiative (known as "msvbvm61.dll" — an idea of patching original MSVBVM60.DLL and embedding new functions/classes/controls and adding support of Unicode for all embedded controls/functions) led me to the conclusion that all such things require referencing LOTS of internal MSVBVM's functions and vars (which are not explorted, again) and sophistiacted mechanisms required to locate all of them are so hard and exhausting to make and support (they need to be prepared to work with any of existing versions of MSVBVM, and there were more than 10 of builds) comparing to how simple it would be if you have original MSVBVM60.DLL sources and you can just link to any of that internal functions or add any field to any structure or just remove any line of code instad of patching/intercepting machine code.

    So I decided to focus on a project which I referenced above as "VB-RCE". All the wishes and desires that was there around VB6 for years (full Unicode support, multithreading, new functions and embedded classes (like Collection class)) becomes way easy to make if you doing it at the level of source code rather than by patching/intercepting compiled binaries. Even if reverse engineering of MSVBVM60 seems to be extremely hard task, it opens up huge perspectives. And, yet again, you haven't to reverse/decompile 100% of code before you can do any (even simpliest) modification.

    That doesn't cancels the fact that vbthrds.dll was good idea and I had working prototypes. I just stopped its development in the name of switching to more comprehensive approach.

    But vbthrds.dll wasn't open-source and I haven't been finished. I can't remenber if I event posted semi-functional (alpha) builds of vbthrds.dll.

    And you asking me for a sample C/C++ project. What do you mean? Do you want me to publish the whole source code of incomplete vbthrds.dll? Do you wan't me to post source code of CreateBasicThread function?

    Here is source code of CreateBasicThread as it is with all its debug/temporary/raw snippets:
    Code:
    HANDLE WINAPI CreateBasicThread(LPSECURITY_ATTRIBUTES   lpThreadAttr, 
                                    LPTHREAD_START_ROUTINE  lpStartProc,
                                    LPVOID                  lpAuxParam)
    {
        HMODULE                     hProjModule;
        SMT_VBPROJECT_DESCRIPTOR*   pProjDescriptor = NULL;
        SMT_VBRUNTIME_DESCRIPTOR*   pRtDescriptor   = NULL;
    
        //
        // Определяем модуль проекта, которому принадлежит процедура.
        //
    
        hProjModule = PeFindContainerOfSymbol(lpStartProc);
    
    
        if(hProjModule == NULL)
        {
            // TODO: Set Error code
            return NULL;
        }
    
        //
        // Просим менеджер кеша предоставить нам дескриптор для данного проекта.
        // Менеджер кеша либо создаст новый дескриптор, либо вернёт уже существующий.
        //
        
        SmtCmProvideCacheEntriesForHmod(hProjModule,
                                       &pProjDescriptor,
                                       &pRtDescriptor);
    
        if(pProjDescriptor == NULL)
        {
            // TODO: Set error code
            return NULL;
        }
    
        //
        // DBG: Патчим проджект, TODO: fix it
        //
    
        SMT_CTHREADPOOL_SFTABLE &pctp = pRtDescriptor->Internals.SFTables.CCThreadPool;
        Project*        pProj;
        CThreadPool*    pRby_ThreadPool = pRtDescriptor->Internals.Rby_ThreadPool;
    
        pRby_ThreadPool; 
        pProj = (pRby_ThreadPool->*pctp.FindCachePepiProj)(pProjDescriptor->pProjInfo);
        
        ((DWORD*)pProj)[37] = 0;
    
        
        SMT_START_PARAMETERS*   pStartParams;   
        DWORD                   fdwSystemFlags;
        HANDLE                  hNewThread;
        DWORD                   iNewThreadId;
        CONTEXT                 ntctx;
    
        //
        // Мы всегда создаём новый поток в замороженном состоянии, чтобы сформировать 
        // структуру SMT_START_PARAMETERS в его стеке. Это единственный способ передать 
        // потоку структуру так, чтобы потом не заботиться об её уничтожении.
        //
        // Мы не можем переложить ответственность за уничтожение этой структуры на новый
        // поток, потому что нет гарантии, что его вообще когда-нибудь разморозят.
        // Мы не можем сами заботиться об её уничтожении, потому что для этого потребовалось
        // бы отслеживать терминацию всех потоков и держать большую таблицу указателей на 
        // ждущие уничтожения структуры.
        // 
    
        fdwSystemFlags = CREATE_SUSPENDED; /* TODO: fix it */
        
        hNewThread = CreateThread(lpThreadAttr,
                                  0, /* TODO: Fix it */
                                  SmtIntermThreadProc,
                                  (LPVOID)SMT_SPB_FAULT_MARKER,
                                  fdwSystemFlags,
                                  &iNewThreadId);
    
        if(!hNewThread) return hNewThread;
    
        //
        // Получаем контекст нового потока. Затем сдвигаем указатель стека так, 
        // чтобы там можно было разместить структуру. И заменяем контекст потока
        // новым, изменённым.
        // 
    
        ntctx.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER;
        
        if(!GetThreadContext(hNewThread, &ntctx))
        {
            TerminateThread(hNewThread, 0);
            CloseHandle(hNewThread);
            return NULL;
        }
    
        ntctx.Esp -= sizeof(SMT_START_PARAMETERS);
        pStartParams = (SMT_START_PARAMETERS*)ntctx.Esp;
        
    
        //
        // Win32 API — тупейшая обёртка над Native API, которая убивает кучу востребованных 
        // возможностей Native API. В частности, мы не можем передать создаваемому потоку 
        // структуру большого размера, мы можем только передать указатель на структуру, 
        // но саму структуру мы никак не можем «прикрепить» к потоку. Если бы CreateThread 
        // позволяла создать в новом стеке некий room, с заведомо известным адресом, который 
        // бы можно было передать в CreateThread в качестве lpParam... А так нам приходится 
        // искусственно воссоздавать такую функциональность. Методика основана на знании 
        // недокументированных особенностей функционирования ядра Windows, а именно функции 
        // BaseInitializeContext. Если она когда-нибудь изменится, то трюк не сработает, 
        // поэтому нужно проверить, работает ли BaseInitializeContext так же, как ожидается.
        //
    
        if(ntctx.Eax == (DWORD)SmtIntermThreadProc &&
           ntctx.Ebx == SMT_SPB_FAULT_MARKER)
        {
            // BaseInitializeContext работает как ожидалось.
    
            ntctx.Ebx = (DWORD)pStartParams;
        }
        else
        {
            //
            // BaseInitializeContext работает иначе, а значит новому потоку самому придётся 
            // искать в своём стеке структуру SMT_START_PARAMETERS, потому что её адрес потоку 
            // передать никак не удалось. Формируем маркер для поиска...
            //
    
            SMT_START_PARAMETERS_MARKER* pSpbMarker;
    
            ntctx.Esp -= sizeof(SMT_START_PARAMETERS_MARKER);
            pSpbMarker = (SMT_START_PARAMETERS_MARKER*)ntctx.Esp;
            *pSpbMarker = SmtStartParametersBlockMarker;
        }
    
        ntctx.Esp -= sizeof(DWORD) * 64; // Отступ ради безопасности
    
        if(!SetThreadContext(hNewThread, &ntctx))
        {
            TerminateThread(hNewThread, 0);
            CloseHandle(hNewThread);
            return NULL;
        }
        
        //
        // Теперь структура лежит на стеке созданного потока, и мы можем спокойно её 
        // заполнять стартовыми параметрами.
        //
    
        pStartParams->UserThreadProc = lpStartProc;
        pStartParams->UserThreadParam = lpAuxParam;
        pStartParams->Project = pProjDescriptor;
        pStartParams->Runtime = pRtDescriptor;
    //  pStartParams->Flags = 0; /* TODO: Fix it */
    
        BOOL bNeedTlsEmulation = TRUE;
    
    
        if(bNeedTlsEmulation)
        {
            //DebugBreak();
            LoadLibrary("vbthrds.dll");
            LoadLibrary("vbthrds.dll");
            LoadLibrary("vbthrds.dll");
            LoadLibrary("vbthrds.dll");
        
            EhbtlsAttachToThread(GetCurrentThread()); 
            EhbtlsEnableSection(pProjDescriptor->TlsEmuSection); 
    
            
        }
    
        //
        // Теперь наконец даём новому потоку работать.
        //
    
        
        
        ResumeThread(hNewThread); 
    
        return hNewThread;
    
    }
    Comments are in Russian, sorry. I presume Google Translate will give poor results as it always does with comments within code.

    Here is SmtIntermThreadProc which is a thunk to caller-supplied ThreadProc and is responsible for proper initialization of runtime library for a newly created thread from context of that new thread:
    Code:
    DWORD WINAPI SmtIntermThreadProc(LPVOID lpParameter)
    {
        DWORD                   ret;
        SMT_START_PARAMETERS*   psp;
        CThreadPool*            Rby_ThreadPool;
    
        if(lpParameter == (LPVOID)SMT_SPB_FAULT_MARKER)
        {
            //
            // Наш родитель не смог передать нам указатель на структуру со
            // стартовыми параметрами из-за изменений в BaseInitializeContext.
            // Ищем её в своём стеке вручную.
            //
    
            psp = SmtFindSpbOnThreadStack();
        }
        else
        {
            psp = (SMT_START_PARAMETERS*)lpParameter;
        }
    
    
        Rby_ThreadPool = psp->Runtime->Internals.Rby_ThreadPool;
        
        SMT_CTHREADPOOL_SFTABLE& ctpm = psp->Runtime->Internals.SFTables.CCThreadPool;
    
        BOOL                bNeedTlsEmulation = TRUE;
        EHBTLS_FRAME        TlsEmuFrame;
    
    
        if(bNeedTlsEmulation)
        {
            EhbtlsEnterThreadSafeRegionEx(&TlsEmuFrame, psp->Project->TlsEmuSection);
        }
        
        //
        // Вызываем RbyThreadPool.InitDllAccess для инициализации контекста 
        // данного проекта для нового потока.
        // 
    
        (Rby_ThreadPool->*ctpm.InitDllAccess)(psp->Project->pProjInfo, psp->Project->hModule);
    
    
    
        //
        // А теперь вызываем пользовательскую ThreadProc.
        //
        
        ret = psp->UserThreadProc(psp->UserThreadParam); 
    
    
        CThreadData*    pMyData;
        CMsoComponent*  pMsoComp;
        
        (Rby_ThreadPool->*ctpm.GetThreadData)(&pMyData);
    
        if(pMyData)
        {
            pMyData->ThreadAction->ThreadType = VbExeThreadWorker;
            *(HANDLE*)((PBYTE)pMyData->ThreadAction + 0x18) = CreateEvent(0, 0, 0, 0);
    #ifndef SMT_SUPPORT_OLD_RUNTIMES
            pMsoComp = pMyData->MsoComponent;
    #else
            pMsoComponent = *(CMsoComponent**)
                ((BYTE*)pMyData + psp->Runtime->Internals.offset_MsoCmpFldInThreadData); 
    #endif
    
            if(pMsoComp)
            {
                (pMsoComp->*psp->Project->Runtime->Internals.SFTables.CCMsoComponent.PushMsgLoop)(-1);
            }
        }   
        
        //
        // Деинициализируем контекст потока.
        //
    
        (Rby_ThreadPool->*ctpm.TerminateThread)();
        
        if(bNeedTlsEmulation)
        {
            EhbtlsLeaveThreadSafeRegion(&TlsEmuFrame);
        }
    
        ExitThread(ret);
        return ret; // Этого никогда не произойдёт
    }
    This two subroutines are only small part of the whole vbthrds.dll.

    One of the cruicial components of EHBTLS: exception-handling based TLS. Global variables of single-threaded application are allocated on data section of executable image. However, for multithreaded VB program it isn't acceptable: each thread must have its own copy of the set of all global variables. Since project types such as "ActiveX DLL", "ActiveX Control" and "ActiveX EXE" implies that VB-code can by run in context of multiple different threads, VB (from IDE/compiler point of view) offers "Threading model" option under "Project settings", which can be either "Single Threaded" or "Apartament threaded", and when project is configured to use "Apartament threaded", global variables are allocated either in data section (same as when compiling Single Threaded projects) for main thread of multithreaded project, or in per-thread block of memory for any thread other than main thread. To locate proper instance of global variable VB-code calls vbaAptOffset which returns offset from variable's address in data section to its actual location in per-thread memory block. For the main thread this offset is 0, but for other threads it is a non-zero value so that adding this offset to a address of the variable within data section gives an actual address of this global var instance for a particular thread.

    But VB6 IDE and compiler does not allow you to set "Apartament threading" model for projects of type "Standard EXE". Thus, trying to spawn multiple threads within Standard EXE project will lead to a situation where all global variables are shared between all threads. This is barely acceptable for Byte/Integer/Long variables, but is absolutely unacceptable for objects and Variants. To overcome this, my library used SEH/VEH to detect reads and writes from/to global variables and redirect it to proper per-thread memory block containing per-thread copies of all global variables of the project.

    Without this you need to be very careful and avoid using global variables. But you can't avoid it completely: App, Forms, Printer, Printers, Clipboard, Screen and so on are actually members of an app-global-object (you can find a class named "Global" in Object Browser) which is referenced via unnamed global variable. So need to emulate TLS for "Standard EXE" projects which might use native TLS facilities of VB but which are actually don't use it due to limitations of IDE/compiler.

    Yet another part of vbthds.dll is SMS: it has nothing to do with mobile phones, it stands for Sub Main suppression. If your project has "Startup Object" = "Sub Main" (project properties), Sub Main() gets called each time you create a new thread using my DLL, which is usually not what you want. You can't easily determine from the Sub Main() whether it was called because a program startup or it is called as a side-effect of spawning additional thread. So there was special trickery to suppress invocation of "Sub Main" when new threads were created and initialized via my DLL.

    Yet again, these two are great examples of how complex the simple problem becomes when you have to solve it on the wrong level. Having access to the source code of VB (the whole product including IDE/compiler and runtime) and unlocking "Theading Model" for "Standard EXE" or just avoiding unwanted invocation of Main() by simple if-block within MSVBVM code would be much simplier than emulating TLS for complete binary compiled without TLS support or suppressing a Sub Main invocation without patching MSVBVM project startup logic.

    It's like having a hand or any other organ: when you born you get your hands "for free" and you take it for granted. But if you loose your hand, it costs you enormous amount of effort and money and even the best of the best prosthetic arms which are very expensive can bring you only limited amount of what you lost.
    Hello FireHacker,

    Please find my reply at https://www.vbforums.com/showthread....91#post5552291 at #2

    Thanks

  20. #60
    PowerPoster
    Join Date
    Jan 2020
    Posts
    5,541

    Re: Instantiate internal class object with name in string

    It's ok
    Last edited by xiaoyao; Apr 16th, 2023 at 07:21 AM. Reason: PLEASE DELETE THIS

  21. #61
    PowerPoster
    Join Date
    Jan 2020
    Posts
    5,541

    Re: Instantiate internal class object with name in string

    get ALL class name string
    Code:
    Public Declare Function lstrcpynA Lib "kernel32" (ByVal lpString1 As String, ByVal lpString2 As Long, ByVal iMaxLength As Long) As Long
    Private Declare Function lstrlenA Lib "kernel32" (ByVal lpData As Long) As Long
    
    Function GetStrFromPtr(lpszName As Long)
    'GOOD
             Dim STR1 As String, Len1 As Long, R As Long, X As Long
             Len1 = lstrlenA(lpszName)
             STR1 = Space(Len1 + 1)
             R = lstrcpynA(STR1, lpszName, Len1 + 1)
             X = InStr(STR1, Chr(0))
             If X > 0 Then STR1 = Left(STR1, X - 1)
             GetStrFromPtr = STR1
    End Function
    Code:
    Function GetAllClassOnRun() As String()
        ' Only for Executable.
        '
        ' lpObjInfo is a returned argument.
        ' Function returns true if successful.
        Dim ClassList() As String
        Static Modules()        As NameBasedObjectFactory.MODDESCRTBL_ENTRY
        Static bModulesSet      As Boolean
        Dim i                   As Long
        '
        #If ALSO_USERCONTROLS Then
            Const mfBadFlags As Long = mfUserControl
        #Else
            Const mfBadFlags As Long = 0
        #End If
        '
        If Not bModulesSet Then
            ReDim Modules(0)
            If LoadDescriptorsTable(Modules) Then
                bModulesSet = True
            Else
                Exit Function
            End If
        End If
        '
        ' We are looking for a descriptor corresponding to the specified class.
        Dim ID As Long
        ReDim ClassList(UBound(Modules) - LBound(Modules))
        For i = LBound(Modules) To UBound(Modules)
            With Modules(i)
            ClassList(ID) = GetStrByPtr(.lpszName)
            End With
            ID = ID + 1
        Next i
        GetAllClassOnRun = ClassList()
    End Function
    Last edited by xiaoyao; Apr 16th, 2023 at 07:21 AM.

  22. #62
    PowerPoster
    Join Date
    Jan 2020
    Posts
    5,541

    Re: Instantiate internal class object with name in string

    Wrong

Page 2 of 2 FirstFirst 12

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