Results 1 to 32 of 32

Thread: Registry Free Object Instantiation using DirectCOM & RC6

  1. #1

    Thread Starter
    PowerPoster
    Join Date
    Aug 2010
    Location
    Canada
    Posts
    2,466

    Registry Free Object Instantiation using DirectCOM & RC6

    There are a few variations of this kind of reg-free "bootstrapping" module already circulating, but I wanted to write one that was highly commented in the hope that it will help some newcomers better understand how to write their VB6/RC6 apps in a way that works without requiring their DLLs to be registered, and also how to package their app & DLLs so that everything will work smoothly when deployed on a user's computer without registration.

    Source Code
    Now includes a demo App, DLL, and RPC DLL

    Latest Version Updated March 26, 2024 - Download here:

    Rc6RegFree4.zip

    NOTE: This is a work in progress - if there are any parts of the code or comments that you still find confusing, please ask! I'll try my best to clarify and update the comments so that we have a comprehensive document that will be easily understood by newcomers to RC6.

    Things are described in much more detail in the module comments, so I suggest you read them all. Here are the basics:

    THE PRIMARY GOAL OF THIS MODULE is to be universal and foundational code for VB6 apps using DirectCOM.dll and RC6.dll to instantiate ActiveX objects without requiring the use of regsvr32/installers on your application's end users' computers. This approach will be called "DirectCOM reg-free" or simply "reg-free" for the rest of this post.

    By "Universal" I mean that this module should be added to EVERY VB6/RC6 project.
    By "Foundational" I mean that this module should be the FIRST THING that you add to every project you create that will be using RC6/DirectCOM reg-free.

    TO USE THIS MODULE

    When you start writing a new app, add MRegFree.bas to your project via the Project > Add Module menu.
    Next, add a reference to RC6.dll via the Project > References menu.

    You will now have everything you need to start writing a reg-free VB6/RC6 application. The main thing to understand is that there are now 5 ways to instantiate new objects in your code instead of only the regular 2 ways (New keyword and CreateObject function). The choice of which method to use depends on the type of object you want to create.

    Creating Objects Using the New Keyword
    Use the built-in VB6 New keyword to instantiate objects that you know will be registered on the user's computer, or that are built-in to the VB6 runtime or STDOLE. For example, VB6 Collections, StdFont objects, StdPicture objects, etc... should always be instantiated via the New keyword.

    Creating Objects Using the CreateObject Method
    Use the built-in VB6 CreateObject method to instantiate objects that you know will be registered on the end user's computer late-bound. This would include things like Excel.Application, Word.Application, Shell objects, etc...

    Creating Objects Using the New_C Method
    Use the New_C method to instantiate all RC6 objects excluding Cairo objects. This instantiation will occur without touching the registry, so RC6.dll does not need to be registered on your end user's computer. Example:

    Code:
    Dim RS As RC6.cRecordset
    
    Set RS = New_C.Recordset    ' Creates an RC6.cRecordset object instance in the RS variable without touching the registry. Use this instead of the more familiar "Set RS = New Recordset" approach (which would require a trip to the registry).
    Creating Objects Using the Cairo Method
    Use the Cairo method to create new RC6 Cairo objects (as well as use all other Cairo features). Instantiating Cairo objects in this way will will not touch the registry, so RC6.dll does not need to be registered on yur end user's computer.

    For example, to create a new image surface to draw against:

    Code:
    Dim Srf As RC6.cCairoSurface
    
    Set Srf = Cairo.CreateSurface(100, 100)  ' Creates a ne RC6.cCairoSurface instance (100x100 pixels) in the Srf variable without touching the registry.
    Creating Objects Using the CreateObjectRegfree Method
    Use the CreateObjectRegfree method to create objects from DLLs that you will distribute with your application (that is, DLLs that aren't distributed by Microsoft with Windows), but that you don't want to register on the user's computer. For example, if you have created your own DLL called MyDll.dll with a class called MyClass, and a method call MySub, you can use it reg-free as follows (make sure you have added a reference to MyDll.dll in the VB6 Project > References menu):

    Code:
    Dim MC As MyDll.MyClass
    
    Set MC = CreateObjectRegfree("MyDll.dll", "MyClass") ' Creates an instance of MyDll.MyClass in the variable named MC without touching the registry.
    
    MC.MySub
    If you stick to the above rules when writing your code, you will be able to distribute your application and all related DLLs without registering the DLLs on your end user's computer. This makes it possible to distribute your app without an installer if you like (for example, in a ZIP archive).

    Packaging & Distributing Your Application

    This topic is also discussed in more detail in the source comments, but the basics are:

    Your main application folder should contain the following:

    • The EXE that your users will launch to use your app.
    • A folder called System.
    • Other folders that you want to include with your app, such as a Help (for your documentation).


    The System sub-folder should contain the following:

    • RC6.dll (available at www.vbrichclient.com)
    • cairo_sqlite.dll (available at www.vbrichclient.com)
    • DirectCOM.dll (available at www.vbrichclient.com)
    • RC6Widgets.dll (optional - only needed for apps that use RC6 Forms instead of VB6 Forms - available at www.vbrichclient.com).
    • Any of your own DLLs that your app references.
    • Any satellite/helper EXEs that your main app shells out to for any purpose.
    • Any third-party DLLs that your main app references, and that aren't already distributed by Microsoft with Windows. For example, Chilkat's DLLs.
    • A folder named RPCDlls - this is optional, and only needed for client-server applications that use remote procedure calls (RPC) with the RC6 cRpcListener and cRpcConnection classes.


    Your main application folder can then be compressed into a ZIP archive, or packaged into a self-extracting executable for distribution to end users. Users can extract the contents anywhere they like, and launch the main application EXE to start using your software immediately - no registration of components required.

    I hope this code proves useful to someone out there. Questions, comments, an criticisms are always welcome.
    Last edited by jpbro; Mar 26th, 2024 at 11:31 AM.

  2. #2

    Thread Starter
    PowerPoster
    Join Date
    Aug 2010
    Location
    Canada
    Posts
    2,466

    Re: Registry Free Object Instantiation using DirectCOM & RC6

    MRegFree.bas has the following 12 public methods that you will use to create objects without touching the registry, and to work with files stored in your app's folder and sub-folders.

    New_c

    Code:
    Public Function New_C() As RC6.cConstructor
    Returns an RC6.cConstructor object that is used for instantiating new non-Cairo RC6 objects without touching the registry.

    Cairo

    Code:
    Public Function Cairo() As RC6.cCairo
    Returns an RC6.cCairo object that can be used for constructing/instantiating new Cairo objects without touching the registry, and for accessing all other Cairo objects/methods.

    CreateObjectRegfree

    Code:
    Public Function CreateObjectRegfree(ByVal p_DllName As String, ByVal p_ClassName As String, Optional ByVal p_ProgId As String = vbNullString) As Object
    Returns a newly instated object created from any DLL in the App\System\ folder.

    • Pass the file name of the DLL that holds the class you want to create to tThe p_DllName parameter. For example, "MyDll.dll"
    • Pass the name of the class that you want to create to the p_ClassName parameter. For example, "cMyClass"
    • Optionally pass the ProgID of the class you want to create to the p_ProgId parameter. This is only needed for creating objects where the ProgID does not match the file name of the DLL (less the file extension) and the ClassName. For example, if you have a DLL call MyDllVersion5.dll and a class called cMyClass, but the ProgID for that class is MyDll.cMyClass (no version # in the ProgID), the you should pass "MyDll.cMyClass" to the p_ProgId parameter.


    GetOrCreateObjectRegfree

    Code:
    Public Function GetOrCreateObjectRegfree(ByVal p_DllName As String, ByVal p_ClassName As String, Optional ByVal p_ProgId As String = vbNullString, Optional ByVal p_OverrideCacheKey As String) As Object
    Returns a cached object instance (or creates a newly instated object and caches it for subsequent use) of any DLL in the App\System\ folder. This can improve performance if you have an class objects that you use throughout your project that you only need a single instance of. For example, a string helpers class, crypto class, or something along those lines. Can also be useful in situations where instantiation is "heavy" (classes that build lookup table on first access for example).

    ClearRegfreeObjectCache

    Frees all memory/objects held by the regfree objects cache used by GetOrCreateObjectRegfree.

    RemoveFromRegfreeObjectCache

    Code:
    Public Sub RemoveFromRegfreeObjectClass(ByVal p_DllName As String, ByVal p_ClassName As String, Optional ByVal p_OverrideCacheKey As String)
    Removes a single object from the regfree objects cache.

    PathApp

    Code:
    Public Function PathApp() As String
    Returns the full path to your application's root folder. For example, "C:\Users\Me\Documents\MyApp"

    Unlike VB6's built-in App.Path method, this method always returns the main application root folder even when called from a DLL or EXE in a sub-folder of the main app root folder. For example, if you have a DLL in "C:\Users\Me\Documents\MyApp\System", PathApp will return "C:\Users\Me\Documents\MyApp" when called from code in the DLL. App.Path would return the DLL's path (e.g. ""C:\Users\Me\Documents\MyApp\System")

    Also unlike VB6's built-in App.Path method, this method always includes a trailing backslash.

    This method works with paths with Unicode characters.

    PathAppSystem

    Code:
    Public Function PathAppSystem() As String
    This method returns the path to your app's System sub-folder. For example, if your main app is running from "C:\Users\Me\Documents\MyApp", this method will return "C:\Users\Me\Documents\MyApp\System".

    This method always returns the path with a trailing backslash, and is compatible with paths containing Unicode characters.

    PathAppSystemRpc

    Code:
    Public Function PathAppSystemRpc() As String
    This method returns the path to your app's RPCDlls sub-folder. For example, if your main app is running from "C:\Users\Me\Documents\MyApp", this method will return "C:\Users\Me\Documents\MyApp\System\RPCDlls".

    This method always returns the path with a trailing backslash, and is compatible with paths containing Unicode characters.

    RegFreeOption

    Code:
    Public Property Get RegfreeOption(ByVal p_Option As e_RegfreeOption) As String
    Public Property Let RegfreeOption(ByVal p_Option As e_RegfreeOption, p_Value As String)
    Get/Set regfree options (like folder names & debug printing options). The current options enum is:

    Code:
    Public Enum e_RegfreeOption
       regfreeopt_AppFolderName   ' Name of the base folder whe compiled binaries will reside. Default is "Build"
       regfreeopt_AppSystemFolderName   ' Name of the system folder where compiled RC6 DLLs, non-RPC DLLs, and satellite EXEs will reside (sub folder of the base app folder). Default is "System"
       regfreeopt_AppSystemRpcFolderName   ' Name of the folder where compiled RPC DLLs will reside (sub folder of the System folder). Default is "RPCDlls"
       
       regfreeopt_DebugPrintOption   ' Controls app information output for debug messages
       
       [_regfreeopt_First] = regfreeopt_AppFolderName
       [_regfreeopt_Last] = regfreeopt_DebugPrintOption
    End Enum
    NOTE: By default reg-free options are private and not accessible outside the MRegFree module. If you want to change any options, change the RegfreeOptionsPublic conditional compilation constant to True.

    IsRunningInIde

    Returns True if running in the IDE. This is a convenience function that is Private by default, but can be made Public by setting the IsRunningInIde conditional compilation constant to True if you want to use it.

    DebugPrint

    Code:
    Public Sub DebugPrint(ByVal p_Message As String, Optional ByVal p_PrintOptions As e_DebugPrintOption = dbgopt_UseDefaultOption)
    Prints a message to the Immediate window AND the Windows Debug message stream (where it can be viewed by tools like DbgView), optionally including app info (Name, PID, ThreadID). If you don't want to use this function, you can disable it by setting the IncludeDebugPrint conditional compilation constant to False, but it can be quite handy for debugging, particularly in RPC application scenarios.

    Relevant DebugPrint Enums:

    Code:
       Public Enum e_DebugPrintOption
          dbgopt_UseDefaultOption = -1
          dbgopt_MessageOnly   ' Print passed message only, NO app info like name, PID, ThreadID
          dbgopt_PrintAppName = 1 ' Print App Name. Can be combined with other dbgopt_PrintApp* flags
          dgbopt_PrintAppPid = 2  ' Print App Process ID (pid#)
          dgbopt_PrintAppThreadId = 4   ' Print App Thread ID (tid#)
                                                                          
          dgbopt_PrintAll = (dbgopt_PrintAppName Or dgbopt_PrintAppPid Or dgbopt_PrintAppThreadId)  ' Print all available App Info
       End Enum
    Last edited by jpbro; Mar 26th, 2024 at 10:35 AM.

  3. #3
    Fanatic Member
    Join Date
    Jun 2016
    Location
    EspaƱa
    Posts
    514

    Re: Registry Free Object Instantiation using DirectCOM & RC6

    good job intersting

  4. #4

    Thread Starter
    PowerPoster
    Join Date
    Aug 2010
    Location
    Canada
    Posts
    2,466

    Re: Registry Free Object Instantiation using DirectCOM & RC6

    I've improved the documentation in Post #2.

  5. #5

    Thread Starter
    PowerPoster
    Join Date
    Aug 2010
    Location
    Canada
    Posts
    2,466

    Re: Registry Free Object Instantiation using DirectCOM & RC6

    Updated to get rid of the LongPtr stuff as it was unnecessary since RC6.dll and DirectCOM.dll are only available as 32-bit libraries so we'll never be using them from a 64-bit process. At least not until/unless 64-bit versions are released.

  6. #6

    Thread Starter
    PowerPoster
    Join Date
    Aug 2010
    Location
    Canada
    Posts
    2,466

    Re: Registry Free Object Instantiation using DirectCOM & RC6

    Nov 2, 2022 Update (in first post) fixes a problem where a compiled DLL can't find RC6.dll when it is being run from a parent app that is running in the VB6 IDE.

    This issue was discovered while working on this demonstration app: https://www.vbforums.com/showthread....mo-With-Source

  7. #7
    Addicted Member
    Join Date
    Feb 2022
    Posts
    189

    Re: Registry Free Object Instantiation using DirectCOM & RC6

    Quote Originally Posted by jpbro View Post
    Updated to get rid of the LongPtr stuff as it was unnecessary since RC6.dll and DirectCOM.dll are only available as 32-bit libraries so we'll never be using them from a 64-bit process. At least not until/unless 64-bit versions are released.
    This is really excellent work, and we all appreciate your inline documentation and thorough attention to details.
    Keep those LongPtr defs nearby, because we will drag RC6 kicking and screaming into 64bit with TwinBasic.
    haha only half kidding, don't beat me Olaf

  8. #8
    Addicted Member
    Join Date
    Feb 2022
    Posts
    189

    Re: Registry Free Object Instantiation using DirectCOM & RC6

    Quote Originally Posted by jpbro View Post
    I've improved the documentation in Post #2.
    A couple of questions:
    1. Since I am already using SxS for Krool's VBCCR, an anchor control, and Eduardo's NewTab, can I include RC6 to the SxS as well?
    2. Can this method be used to replace the components listed in #1?
    3. I'm regrettably not using Cairo on this project, so can I leave the those off the list and only include RC6.DLL and DirectCOM.dll ?

  9. #9
    PowerPoster
    Join Date
    Jun 2013
    Posts
    7,289

    Re: Registry Free Object Instantiation using DirectCOM & RC6

    Quote Originally Posted by taishan View Post
    A couple of questions:
    1. Since I am already using SxS for Krool's VBCCR, an anchor control, and Eduardo's NewTab, can I include RC6 to the SxS as well?
    2. Can this method be used to replace the components listed in #1?
    3. I'm regrettably not using Cairo on this project, so can I leave the those off the list and only include RC6.DLL and DirectCOM.dll ?
    1) Yes.
    2) No.
    3) Please always include all of the Dlls of the RC6-package in your deployment

    Olaf

  10. #10
    Addicted Member
    Join Date
    Feb 2022
    Posts
    189

    Re: Registry Free Object Instantiation using DirectCOM & RC6

    Quote Originally Posted by Schmidt View Post
    1) Yes.
    2) No.
    3) Please always include all of the Dlls of the RC6-package in your deployment

    Olaf
    Would you recommend putting all the controls and DLLs into a .res resource?

  11. #11

    Thread Starter
    PowerPoster
    Join Date
    Aug 2010
    Location
    Canada
    Posts
    2,466

    Re: Registry Free Object Instantiation using DirectCOM & RC6

    Quote Originally Posted by taishan View Post
    This is really excellent work, and we all appreciate your inline documentation and thorough attention to details.
    Thank you, I hope you found it useful.

  12. #12
    PowerPoster
    Join Date
    Jun 2013
    Posts
    7,289

    Re: Registry Free Object Instantiation using DirectCOM & RC6

    Quote Originally Posted by taishan View Post
    Would you recommend putting all the controls and DLLs into a .res resource?
    No, because your executable then runs a higher risk, to be flagged as a "false positive" by virus-scanners.

    There's nothing wrong with a honest \Bin\ Subfolder (which contains all libraries and ocxes your exe needs) -
    sitting beside your MyApp.exe (in a simple deployment-zip).

    Olaf

  13. #13

    Thread Starter
    PowerPoster
    Join Date
    Aug 2010
    Location
    Canada
    Posts
    2,466

    Re: Registry Free Object Instantiation using DirectCOM & RC6

    Quote Originally Posted by taishan View Post
    A couple of questions:
    1. Since I am already using SxS for Krool's VBCCR, an anchor control, and Eduardo's NewTab, can I include RC6 to the SxS as well?
    2. Can this method be used to replace the components listed in #1?
    3. I'm regrettably not using Cairo on this project, so can I leave the those off the list and only include RC6.DLL and DirectCOM.dll ?
    Olaf has answered definitively already, but I will put my own $0.02 in:

    1. Yes, but if you are already using SxS for DLLs then I recommend avoiding this DirectCOM reg-free approach.
    2. ?? Olaf replied with a firm "no" but I'm not sure what "components listed in #1" you are referring to?
    3. Always bring the full set of DLLs along - it's called "cairo_sqlite.dll", so there's more there than just Cairo. It's only a few MB anyway, so not too much to worry about package size-wise.

  14. #14
    PowerPoster
    Join Date
    Jun 2013
    Posts
    7,289

    Re: Registry Free Object Instantiation using DirectCOM & RC6

    Quote Originally Posted by jpbro View Post
    2. ?? Olaf replied with a firm "no" but I'm not sure what "components listed in #1" you are referring to?
    I understood it as containing at least 2 OCXes (Krools VBCCR- and Eduardos TabCtl-OCX) -
    and the DirectCOM.dll based approach does work only for COM-Dlls (not OCXes).

    Olaf

  15. #15

    Thread Starter
    PowerPoster
    Join Date
    Aug 2010
    Location
    Canada
    Posts
    2,466

    Re: Registry Free Object Instantiation using DirectCOM & RC6

    Quote Originally Posted by taishan View Post
    Would you recommend putting all the controls and DLLs into a .res resource?
    I do not recommend this for the DirectCOM reg-free approach described above. While others report success with the .res approach, I can only recommend that if you use anything that I've written here, then you should put all your DLLs in the folder that "PathAppSystem" points to.

  16. #16
    Addicted Member
    Join Date
    Feb 2022
    Posts
    189

    Re: Registry Free Object Instantiation using DirectCOM & RC6

    Olaf and jpbro, thanks to both of you for your suggestions, it gives me many options to consider!

  17. #17

    Thread Starter
    PowerPoster
    Join Date
    Aug 2010
    Location
    Canada
    Posts
    2,466

    Re: Registry Free Object Instantiation using DirectCOM & RC6

    Quote Originally Posted by Schmidt View Post
    I understood it as containing at least 2 OCXes (Krools VBCCR- and Eduardos TabCtl-OCX) -
    and the DirectCOM.dll based approach does work only for COM-Dlls (not OCXes).
    Thank you. I got tripped-up on the "replace" bit and the "can I include RC6" bit.

    @taishan: Nothing is being "replaced" component-wise - the components are the components. SxS or DirectCOM are just different ways to give you registry free access to those components/objects. There are pros and cons to either approach, but definitely don't use DirectCOM for OCXes - as Olaf said, it won't work.

  18. #18

    Thread Starter
    PowerPoster
    Join Date
    Aug 2010
    Location
    Canada
    Posts
    2,466

    Re: Registry Free Object Instantiation using DirectCOM & RC6

    Updated March 20, 2024:

    Added new public methods:

    GetOrCreateObjectRegfree

    Code:
    Public Function GetOrCreateObjectRegfree(ByVal p_DllName As String, ByVal p_ClassName As String, Optional ByVal p_ProgId As String = vbNullString, Optional ByVal p_OverrideCacheKey As String) As Object
    Returns a cached object instance (or creates a newly instated object and caches it for subsequent use) of any DLL in the App\System\ folder. This can improve performance if you have an class objects that you use throughout your project that you only need a single instance of. For example, a string helpers class, crypto class, or something along those lines. Can also be useful in situations where instantiation is "heavy" (classes that build lookup table on first access for example).

    ClearRegfreeObjectCache

    Frees all memory/objects held by the regfree objects cache used by GetOrCreateObjectRegfree.

    RemoveFromRegfreeObjectCache

    Code:
    Public Sub RemoveFromRegfreeObjectClass(ByVal p_DllName As String, ByVal p_ClassName As String, Optional ByVal p_OverrideCacheKey As String)
    Removes a single object from the regfree objects cache.

    Also:

    Minor code improvements
    PathImage now raises an error if you try to get the image filename while in the IDE (not determinable).
    Added comments, and improved some wrong/confusing comments.

  19. #19
    Frenzied Member VanGoghGaming's Avatar
    Join Date
    Jan 2020
    Location
    Eve Online - Mining, Missions & Market Trading!
    Posts
    1,536

    Question Re: Registry Free Object Instantiation using DirectCOM & RC6

    How does this "DirectCOM.dll" achieve reg-free registration without using SxS manifests? Does it call some undocumented API functions?

    My guess is that it isn't an ActiveX DLL since that would defeat the purpose of being reg-free...

  20. #20

    Thread Starter
    PowerPoster
    Join Date
    Aug 2010
    Location
    Canada
    Posts
    2,466

    Re: Registry Free Object Instantiation using DirectCOM & RC6

    Quote Originally Posted by VanGoghGaming View Post
    How does this "DirectCOM.dll" achieve reg-free registration without using SxS manifests? Does it call some undocumented API functions?

    My guess is that it isn't an ActiveX DLL since that would defeat the purpose of being reg-free...
    Only Olaf can answer that definitively - DirectCOM.dll is closed source. But you're right about it not being an ActiveX DLL, it's a standard DLL with the following magic function that instantiates and returns an object created from a class in an ActiveX DLL:

    Code:
    Private Declare Function GetInstanceEx Lib "DirectCOM" (spFName As Long, spClassName As Long, Optional ByVal UseAlteredSearchPath As Boolean = True) As Object

  21. #21
    Addicted Member
    Join Date
    Feb 2022
    Posts
    189

    Re: Registry Free Object Instantiation using DirectCOM & RC6

    Quote Originally Posted by VanGoghGaming View Post
    How does this "DirectCOM.dll" achieve reg-free registration without using SxS manifests? Does it call some undocumented API functions? My guess is that it isn't an ActiveX DLL since that would defeat the purpose of being reg-free...
    I now make everything Portable and Registration Free. I believe this should decimate support issues with the end user.

    I use LaVolpe's SxS manifest editor for registry free usage of VBCCR and Eduardo's NewTab01.

    Edit: Elroy has an excellent tutorial on the mechanics of SxS. He masterfully describes every step, and eliminates the mystery.

    I use the "Public Property" reg-free method for RC6 because I cannot get the "Sub Main" method working in both the IDE (registered RC6) and standalone (nothing registered on user machine, with all RC6 DLLs and beforementioned ActiveX controls placed in the \Bin folder.)

    I am anxious to test out jpbro's updates! Cheers
    Last edited by taishan; Mar 21st, 2024 at 09:16 PM.

  22. #22

    Thread Starter
    PowerPoster
    Join Date
    Aug 2010
    Location
    Canada
    Posts
    2,466

    Re: Registry Free Object Instantiation using DirectCOM & RC6

    Quote Originally Posted by taishan View Post
    I am anxious to test out jpbro's updates! Cheers
    Don't get too excited yet, there's still one puzzle I'm working on re: IDE and compiled differences.

    The good news: Right now everything works great when compiled, and most things work just as well in the IDE (all the regfree stuff is working perfectly whether in the IDE or compiled in my tests so far).

    The bad news: I'm not happy with the differences in what PathApp and PathImage return between the IDE and when compiled. In the IDE the paths are relative to the source code folder, but compiled they are relative to the compiled binary folder. I'd like them to be the same, but I don't know the best way to go about this.

    One option would be to force binaries to be compiled to a sibling folder of a source folder, so something like:

    Code:
    C:\MyApp\Source\ <- Project folders and Source files go here
    C:\MyApp\Bin\ <- Main EXE Binary files go here
    C:\MyApp\Bin\System <- DLLs and Satellite app binaries go here
    C:\MyApp\Bin\System\RPCDlls  <- Remotely callable DLLs go here.
    Under such a scheme, we can detect if we are in the the IDE and determine the binary folder easily - BUT - not everyone will want to arrange their files this way, and I'm trying to make the code as generic as possible.

    Another possibility would be to have some kind of Options methods which would allow you to set the base folder when in the IDE, but this would require devs to configure stuff every time they use the module, which is a bit of a pain.

    Anyway, I'm open to ideas here if anyone has any.

  23. #23
    Addicted Member
    Join Date
    Feb 2022
    Posts
    189

    Re: Registry Free Object Instantiation using DirectCOM & RC6

    Quote Originally Posted by jpbro View Post
    ...One option would be to force binaries to be compiled to a sibling folder of a source folder, so something like:
    Code:
    C:\MyApp\Source\ <- Project folders and Source files go here
    C:\MyApp\Bin\ <- Main EXE Binary files go here
    C:\MyApp\Bin\System <- DLLs and Satellite app binaries go here
    C:\MyApp\Bin\System\RPCDlls  <- Remotely callable DLLs go here.
    Under such a scheme, we can detect if we are in the the IDE and determine the binary folder easily - BUT - not everyone will want to arrange their files this way, and I'm trying to make the code as generic as possible.
    I think the trade-offs of hard-coding project files so "it just works" is not a bad proposal... Thanks for all of your hard work! Cheers

  24. #24

    Thread Starter
    PowerPoster
    Join Date
    Aug 2010
    Location
    Canada
    Posts
    2,466

    Re: Registry Free Object Instantiation using DirectCOM & RC6

    Thanks taishan. The more I think about it, the more I wonder if it makes more sense to make the Path* methods Private so that they are only used by regfree module, and leave the path handling outside of the regfree stuff to the app.

  25. #25
    Addicted Member
    Join Date
    Feb 2022
    Posts
    189

    Re: Registry Free Object Instantiation using DirectCOM & RC6

    Quote Originally Posted by jpbro View Post
    ...it makes more sense to make the Path* methods Private so that they are only used by regfree module, and leave the path handling outside of the regfree stuff to the app.
    That sounds very accessible for every object/method involved. Interested to see what you come up with! Cheers

  26. #26
    Frenzied Member VanGoghGaming's Avatar
    Join Date
    Jan 2020
    Location
    Eve Online - Mining, Missions & Market Trading!
    Posts
    1,536

    Cool Re: Registry Free Object Instantiation using DirectCOM & RC6

    Quote Originally Posted by jpbro View Post
    Only Olaf can answer that definitively - DirectCOM.dll is closed source. But you're right about it not being an ActiveX DLL, it's a standard DLL with the following magic function that instantiates and returns an object created from a class in an ActiveX DLL:

    Code:
    Private Declare Function GetInstanceEx Lib "DirectCOM" (spFName As Long, spClassName As Long, Optional ByVal UseAlteredSearchPath As Boolean = True) As Object
    I would venture a wild guess that at least some of the secret sauce involves checking the TypeLib information exposed by ActiveX DLLs created with VB6. I've just put together a quick example of a generic "RegFree" function that uses the "LoadTypeLibEx" API function to expose the ITypeLib interface of a VB6 ActiveX DLL. It uses Fafalone's "oleexp" TypeLib to save time with all the API declarations and unnecessary calls to "DispCallFunc":

    mdlRegfree BAS module (you need to supply your own ActiveX DLL for reg-free testing)
    Code:
    Option Explicit
    
    Private Const MEMBERID_NIL As Long = -1, DllGetClassObject As String = "DllGetClassObject"
    
    Private ParamTypes(0 To 10) As Integer, ParamValues(0 To 10) As Long, lParamCount As Long, pInterface As Long, vParams As Variant, _
            sCurrentLib As String, lpDllGetClassObject As Long, IID_IClassFactory As oleexp.UUID, IID_IUnknown As oleexp.UUID
            
    Public Function RegFree(sLibName As String, sClassName As String) As Object
    Dim RegFreeIUnknown As IUnknown, tliTypeLibInfo As oleexp.ITypeLib, i As Long, lpTypeAttr As Long, sInterfaceName As String, objClassFactory As oleexp.IClassFactory
        Set tliTypeLibInfo = LoadTypeLibEx(sLibName, REGKIND_NONE) ' REGKIND_NONE calls LoadTypeLib without the registration process enabled
        With tliTypeLibInfo
            For i = 0 To .GetTypeInfoCount - 1
                If .GetTypeInfoType(i) = TKIND_COCLASS Then ' Loop through the CoClasses exposed by this ActiveX DLL
                    With .GetTypeInfo(i)
                        With .GetRefTypeInfo(.GetRefTypeOfImplType(0)) ' Get the type description of the interface implemented by this class
                            .GetDocumentation MEMBERID_NIL, sInterfaceName, vbNullString, 0, vbNullString
                        End With
                        If sClassName = Mid$(sInterfaceName, 2) Then ' InterfaceName is ClassName prefixed with an underscore (_ClassName)
                            If sLibName <> sCurrentLib Then
                                sCurrentLib = sLibName: lpDllGetClassObject = GetModuleHandle(ByVal StrPtr(sCurrentLib)) ' Check if the library had already been loaded
                                If lpDllGetClassObject = 0 Then lpDllGetClassObject = CoLoadLibrary(sCurrentLib, True)
                                lpDllGetClassObject = GetProcAddress(lpDllGetClassObject, DllGetClassObject)
                                If IID_IClassFactory.Data1 = 0 Then CopyMemory IID_IClassFactory.Data4(0), 504403158265495.5712@, 8: IID_IUnknown = IID_IClassFactory: IID_IClassFactory.Data1 = 1
                            End If
                            lpTypeAttr = .GetTypeAttr
                            If lpTypeAttr Then InvokeObj Nothing, lpDllGetClassObject, lpTypeAttr, VarPtr(IID_IClassFactory), VarPtr(objClassFactory) ' Call DllGetClassObject to retrieve the class object from the DLL object handler
                            .ReleaseTypeAttr lpTypeAttr
                            objClassFactory.CreateInstance Nothing, IID_IUnknown, RegFreeIUnknown ' Create an instance of this class
                            Set RegFree = RegFreeIUnknown: Exit Function ' Get the IDispatch implementation of this class
                        End If
                    End With
                End If
            Next i
        End With
    End Function
    
    Private Function InvokeObj(Interface As IUnknown, vtbOffset As Long, ParamArray ParamsArray() As Variant) As Variant
    Dim lRet As Long
        InvokeObj = S_FALSE: pInterface = ObjPtr(Interface): vParams = ParamsArray
        For lParamCount = 0 To UBound(vParams): ParamTypes(lParamCount) = VarType(vParams(lParamCount)): ParamValues(lParamCount) = VarPtr(vParams(lParamCount)): Next lParamCount
        If pInterface Then
            lRet = DispCallFunc(ByVal pInterface, vtbOffset, CC_STDCALL, vbLong, lParamCount, ParamTypes(0), ParamValues(0), InvokeObj)
        ElseIf vtbOffset > 1024 Then
            lRet = DispCallFunc(ByVal pInterface, vtbOffset, CC_STDCALL, vbLong, lParamCount, ParamTypes(0), ParamValues(0), InvokeObj)
        End If
        If lRet Then Debug.Print Hex$(lRet)
    End Function
    
    Public Sub Main()
        Dim objRegFree As Object
        Set objRegFree = RegFree(App.Path & "\Bin\SomeActiveX.dll", "SomeClass")
    '    Set objRegFree = RegFree2(App.Path & "\Bin\SomeActiveX.dll", "SomeClass")
        If objRegFree Is Nothing Then Debug.Print "Object is Nothing!" Else objRegFree.CallSomeMethod
    End Sub
    mdlRegfree2 BAS module (same version as above but without any dependencies, doesn't require a TypeLib)
    Code:
    Option Explicit
    
    Private Const MEMBERID_NIL As Long = -1, REGKIND_NONE As Long = 2, CC_STDCALL As Long = 4, S_OK As Long = 0, S_FALSE As Long = 1, PTR_SIZE As Long = 4, DllGetClassObject As String = "DllGetClassObject"
    
    Private Enum vtbInterfaceOffsets
    '    ITypeLib
        ITypeLib_FindName = 11 * PTR_SIZE
    '    ITypeInfo
        ITypeInfo_GetTypeAttr = 3 * PTR_SIZE
        ITypeInfo_ReleaseTypeAttr = 19 * PTR_SIZE
    '    IClassFactory
        IClassFactory_CreateInstance = 3 * PTR_SIZE
    End Enum
    
    Private Declare Function LoadLibraryW Lib "kernel32" (ByVal lpLibFileName As Long) As Long
    Private Declare Function GetModuleHandleW Lib "kernel32" (ByVal lpModuleName As Long) As Long
    Private Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long
    Private Declare Function DispCallFunc Lib "oleaut32" Alias "#146" (ByVal pvInstance As Long, ByVal oVft As Long, ByVal cc As Long, ByVal vtReturn As VbVarType, ByVal cActuals As Long, prgvt As Any, prgpvarg As Any, pvargResult As Variant) As Long
    Private Declare Function LoadTypeLibEx Lib "oleaut32" Alias "#183" (ByVal lpszFile As Long, ByVal RegKind As Long, pptLib As IUnknown) As Long
    
    Private ParamTypes(0 To 10) As Integer, ParamValues(0 To 10) As Long, lParamCount As Long, pInterface As Long, vParams As Variant, _
            sCurrentLib As String, lpDllGetClassObject As Long, IID_IClassFactory(0 To 1) As Currency, IID_IUnknown(0 To 1) As Currency
    
    Public Function RegFree2(sLibName As String, sClassName As String) As Object
    Dim RegFreeIUnknown As IUnknown, ITypeLib As IUnknown, ITypeInfo As IUnknown, IClassFactory As IUnknown, rgMemId As Long, pcFound As Long, lpTypeAttr As Long
        If LoadTypeLibEx(StrPtr(sLibName), REGKIND_NONE, ITypeLib) = S_OK Then ' REGKIND_NONE calls LoadTypeLib without the registration process enabled
            pcFound = 1: InvokeObj ITypeLib, ITypeLib_FindName, StrPtr(sClassName), 0&, VarPtr(ITypeInfo), VarPtr(rgMemId), VarPtr(pcFound)
            If rgMemId = MEMBERID_NIL Then
                If sLibName <> sCurrentLib Then
                    sCurrentLib = sLibName: lpDllGetClassObject = GetModuleHandleW(StrPtr(sCurrentLib)) ' Check if the library had already been loaded
                    If lpDllGetClassObject = 0 Then lpDllGetClassObject = LoadLibraryW(StrPtr(sCurrentLib))
                    lpDllGetClassObject = GetProcAddress(lpDllGetClassObject, DllGetClassObject)
                    If IID_IClassFactory(1) = 0 Then IID_IClassFactory(0) = 0.0001@: IID_IClassFactory(1) = 504403158265495.5712@: IID_IUnknown(1) = IID_IClassFactory(1)
                End If
                InvokeObj ITypeInfo, ITypeInfo_GetTypeAttr, VarPtr(lpTypeAttr)
                If lpTypeAttr Then InvokeObj Nothing, lpDllGetClassObject, lpTypeAttr, VarPtr(IID_IClassFactory(0)), VarPtr(IClassFactory) ' Call DllGetClassObject to retrieve the class object from the DLL object handler
                InvokeObj ITypeInfo, ITypeInfo_ReleaseTypeAttr, lpTypeAttr
                InvokeObj IClassFactory, IClassFactory_CreateInstance, 0&, VarPtr(IID_IUnknown(0)), VarPtr(RegFreeIUnknown) ' Create an instance of this class
                Set RegFree2 = RegFreeIUnknown ' Get the IDispatch implementation of this class
            End If
        End If
    End Function
    
    Private Function InvokeObj(Interface As IUnknown, vtbOffset As Long, ParamArray ParamsArray() As Variant) As Variant
    Dim lRet As Long
        InvokeObj = S_FALSE: pInterface = ObjPtr(Interface): vParams = ParamsArray
        For lParamCount = 0 To UBound(vParams): ParamTypes(lParamCount) = VarType(vParams(lParamCount)): ParamValues(lParamCount) = VarPtr(vParams(lParamCount)): Next lParamCount
        If pInterface Then
            lRet = DispCallFunc(ByVal pInterface, vtbOffset, CC_STDCALL, vbLong, lParamCount, ParamTypes(0), ParamValues(0), InvokeObj)
        ElseIf vtbOffset > 1024 Then
            lRet = DispCallFunc(ByVal pInterface, vtbOffset, CC_STDCALL, vbLong, lParamCount, ParamTypes(0), ParamValues(0), InvokeObj)
        End If
        If lRet Then Debug.Print Hex$(lRet)
    End Function
    Here's the demo project for who's interested: RegFree.zip (contains both versions above)

    Now since the above code is pretty much "run-of-the-mill", I suspect that Olaf's DirectCOM does something more to warrant it being closed source and all. Maybe he could chime in with some insight, I'm always keen to learn new stuff!

  27. #27
    PowerPoster
    Join Date
    Jun 2013
    Posts
    7,289

    Re: Registry Free Object Instantiation using DirectCOM & RC6

    Quote Originally Posted by VanGoghGaming View Post
    I suspect that Olaf's DirectCOM does something more to warrant it being closed source and all.
    Yes, on top of that -
    it allows stable regfree instancing of COM-Classes on their own threaded STAs also in the VB6-IDE.
    (that's the main-reason it comes encapsulated in a Std-Dll).

    Olaf

  28. #28
    Frenzied Member VanGoghGaming's Avatar
    Join Date
    Jan 2020
    Location
    Eve Online - Mining, Missions & Market Trading!
    Posts
    1,536

    Wink Re: Registry Free Object Instantiation using DirectCOM & RC6

    I did see DirectCOM.dll referencing "CreateThread" and a whole bunch of other "multi-threading" API functions so this confirms what I've been suspecting all along. So I guess this code wouldn't work in a BAS module if you wanted multi-threading in the IDE as well... Cheers!

  29. #29

    Thread Starter
    PowerPoster
    Join Date
    Aug 2010
    Location
    Canada
    Posts
    2,466

    Re: Registry Free Object Instantiation using DirectCOM & RC6

    OK, I finally have something I'm happy with (for my own uses at least), and hopefully the latest version will be useful to others too.

    MRc6Base.bas is now MRegFree.bas and it includes a whole slew of bugs fixes, enhancements, and new features.

    New features include:

    RegFreeOption

    Code:
    Public Property Get RegfreeOption(ByVal p_Option As e_RegfreeOption) As String
    Public Property Let RegfreeOption(ByVal p_Option As e_RegfreeOption, p_Value As String)
    Get/Set regfree options (like folder names & debug printing options). The current options enum is:

    Code:
    Public Enum e_RegfreeOption
       regfreeopt_AppFolderName   ' Name of the base folder whe compiled binaries will reside. Default is "Build"
       regfreeopt_AppSystemFolderName   ' Name of the system folder where compiled RC6 DLLs, non-RPC DLLs, and satellite EXEs will reside (sub folder of the base app folder). Default is "System"
       regfreeopt_AppSystemRpcFolderName   ' Name of the folder where compiled RPC DLLs will reside (sub folder of the System folder). Default is "RPCDlls"
       
       regfreeopt_DebugPrintOption   ' Controls app information output for debug messages
       
       [_regfreeopt_First] = regfreeopt_AppFolderName
       [_regfreeopt_Last] = regfreeopt_DebugPrintOption
    End Enum
    NOTE: By default reg-free options are private and not accessible outside the MRegFree module. If you want to change any options, change the RegfreeOptionsPublic conditional compilation constant to True.

    IsRunningInIde

    Returns True if running in the IDE. This is a convenience function that is Private by default, but can be made Public by setting the IsRunningInIde conditional compilation constant to True if you want to use it.

    DebugPrint

    Code:
    Public Sub DebugPrint(ByVal p_Message As String, Optional ByVal p_PrintOptions As e_DebugPrintOption = dbgopt_UseDefaultOption)
    Prints a message to the Immediate window AND the Windows Debug message stream (where it can be viewed by tools like DbgView), optionally including app info (Name, PID, ThreadID). If you don't want to use this function, you can disable it by setting the IncludeDebugPrint conditional compilation constant to False, but it can be quite handy for debugging, particularly in RPC application scenarios.

    Relevant DebugPrint Enums:

    Code:
       Public Enum e_DebugPrintOption
          dbgopt_UseDefaultOption = -1
          dbgopt_MessageOnly   ' Print passed message only, NO app info like name, PID, ThreadID
          dbgopt_PrintAppName = 1 ' Print App Name. Can be combined with other dbgopt_PrintApp* flags
          dgbopt_PrintAppPid = 2  ' Print App Process ID (pid#)
          dgbopt_PrintAppThreadId = 4   ' Print App Thread ID (tid#)
                                                                          
          dgbopt_PrintAll = (dbgopt_PrintAppName Or dgbopt_PrintAppPid Or dgbopt_PrintAppThreadId)  ' Print all available App Info
       End Enum
    There is also now a full demo that includes a main application, DLL, and RPC DLL.

    Name:  2024-03-26_12-24-13.jpg
Views: 245
Size:  22.1 KB

    To try the demo:

    1. Make sure you have the latest version (6.0.15 at the time of writing) of RC6 registered on your development machine (available at https://vbrichclient.com/#/en/Downloads.htm)
    2. Copy the RC6 binaries to \Rc6Regfree\Demo\Build\System\ (DO NOT register them here).
    3. Open \Rc6Regfree\Demo\Source\MyRc6RpcDll1\MyRc6RpcDll1.vbp and compile the DLL to Demo\Build\System\RPCDlls\
    4. Open \Rc6Regfree\Demo\Source\MyRc6Dll1\MyRc6Dll1.vbp and compile the DLL to Demo\Build\System\
    5. Open \Rc6Regfree\Demo\Source\MainRc6App\MainRc6App.vbp and compile the EXE to Demo\Build\ NOTE: Before you can build the main app, you may need to fix a broken reference to MyRc6Dll. This can be done in the Project > References menu.
    6. Start \Rc6Regfree\Demo\Build\MainRc6App.exe and try out the demo, or open \Rc6Regfree\Demo\Source\Rc6Regfree.vbg to open the full demo project group and step along with the code while you run the demo app.


    RC6 Templates

    I've also included a Templates folder that includes template projects for RC6 EXE, RC6 ActiveX DLL, and RC6 ActiveX RPC DLL projects. These can be copied to C:\Program Files (x86)\Microsoft Visual Studio\VB98\Template\Projects to add templates to the New Project window in the VB6 IDE if desired.

    Name:  2024-03-26_12-36-11.jpg
Views: 234
Size:  35.3 KB

    Enjoy!
    Last edited by jpbro; Mar 26th, 2024 at 05:47 PM.

  30. #30
    Addicted Member
    Join Date
    Feb 2022
    Posts
    189

    Re: Registry Free Object Instantiation using DirectCOM & RC6

    Wow! This is really fantastic work!
    Can you include instaniating vbWidgets? I am getting Cairo fever... I want to learn a new skill that I can use on all platforms.
    I don't want to be a cromagnon and just stick ReexRe's code in here, as you have a whole different inIDE scheme going...
    Cheers bro
    Code:
    Public Function NewWidget(ClassName As String) As Object
        If App.LogMode Then    'we run compiled
            '// Set NewWidget = New_c.RegFree.GetInstanceEx(App.Path & "\vbRC5BaseDlls\vbWidgets.dll", ClassName)
            Set NewWidget = New_C.RegFree.GetInstanceEx(sBinPath & "vbWidgets.dll", ClassName)
        Else    'we run in the IDE, so we create the instance from the registered version
            Set NewWidget = CreateObject("vbWidgets." & ClassName)
        End If
    End Function
    Last edited by taishan; Mar 28th, 2024 at 07:43 PM.

  31. #31

    Thread Starter
    PowerPoster
    Join Date
    Aug 2010
    Location
    Canada
    Posts
    2,466

    Re: Registry Free Object Instantiation using DirectCOM & RC6

    Thanks taishan, glad you like what you see

    Regarding a way to instantiate vbWidgets, there are a few different approaches - all with their upsides and downsides - and I'm not firmly decided on what approach I prefer yet.

    Reexre's function is fine, but I have a bit of a natural aversion (rightly or wrongly) to passing the string class name as there's no intellisense and it is easy to make a typo. Granted even a minimal amount of testing would reveal the typo problem, but it just "feels" wrong to me. Reexre's approach also returns an Object, so you don't get the benefit of Intellisense, which can make it a pain to work with.

    One advantage for using string class names is that it is easy to use new widget classes as they become available, just start using the new class name. Another advantage is that the code is short and easy to reason about/audit.

    In any case, Reexre's code could be written to handle Firehacker's MakeTrue InIde approach with my MRegFree module like so:

    Code:
    Public Function NewWidget(ClassName As String) As Object
       Dim l_InIde As Boolean
    
    #If UseInlineIdeCheck Then
       Debug.Assert MakeTrue(l_InIde)
    #Else
       l_InIde = IsRunningInIde
    #End If
    
        If l_InIde Then
            ' We run in the IDE, so we create the instance from the registered version
    
            Set NewWidget = CreateObject("vbWidgets." & ClassName)
    
        Else
            ' Not in the IDE, so we create the instance from the registered version
    
            Set NewWidget = New_C.RegFree.GetInstanceEx(PathAppSystem & "vbWidgets.dll", ClassName)
    
        End If
    End Function
    Another approach is to have single functions for each widget that return a strongly typed widget variable. This is the approach I use in JPBFileFinder. The advantages are that intellisense works, and you can add extra "constructor" parameters that help you get the widget instantiated with things like position and dimensions set without requiring a bunch of extra property lets for everything. The downsides are that you have to update the codebase every time a new Widget is added to vbWidgets, and the code is comparatively quite verbose.

    There may be other approaches too - I'm all ears if anyone has any interesting suggestions.


    PS: Looking at the MWidgets.bas code in JPBFileFinder, I see that it could use a rewrite to use the new MRegFree module, so I'll see if I can find that time to do that soon.

  32. #32
    Addicted Member
    Join Date
    Feb 2022
    Posts
    189

    Re: Registry Free Object Instantiation using DirectCOM & RC6

    Quote Originally Posted by jpbro View Post
    Another approach is to have single functions for each widget that return a strongly typed widget variable. This is the approach I use in JPBFileFinder. The advantages are that intellisense works, and you can add extra "constructor" parameters that help you get the widget instantiated with things like position and dimensions set without requiring a bunch of extra property lets for everything. The downsides are that you have to update the codebase every time a new Widget is added to vbWidgets, and the code is comparatively quite verbose.
    Thanks for your detailed response. I now understand what you mean by typo caveats in ClassName. intellisense would be excellent to have! I think it's maybe the most important feature for beginners like me.

    Quote Originally Posted by jpbro View Post
    PS: Looking at the MWidgets.bas code in JPBFileFinder, I see that it could use a rewrite to use the new MRegFree module, so I'll see if I can find that time to do that soon.
    Hijack thread alert: Is it too much trouble to add a right-click shell extension "Search with JPBFileFinder" - it's the best search tool I've found. I just forget to fire it up when I'm searching...

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