-
Sep 24th, 2022, 04:12 PM
#1
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.
-
Sep 24th, 2022, 04:13 PM
#2
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.
-
Sep 25th, 2022, 03:25 AM
#3
Hyperactive Member
Re: Registry Free Object Instantiation using DirectCOM & RC6
-
Oct 23rd, 2022, 04:02 PM
#4
Re: Registry Free Object Instantiation using DirectCOM & RC6
I've improved the documentation in Post #2.
-
Oct 23rd, 2022, 06:54 PM
#5
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.
-
Nov 2nd, 2022, 04:21 PM
#6
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
-
Aug 24th, 2023, 03:14 PM
#7
Addicted Member
Re: Registry Free Object Instantiation using DirectCOM & RC6
Originally Posted by jpbro
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
-
Aug 25th, 2023, 09:28 AM
#8
Addicted Member
Re: Registry Free Object Instantiation using DirectCOM & RC6
Originally Posted by jpbro
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 ?
-
Aug 25th, 2023, 10:48 AM
#9
Re: Registry Free Object Instantiation using DirectCOM & RC6
Originally Posted by taishan
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
-
Aug 25th, 2023, 11:16 AM
#10
Addicted Member
Re: Registry Free Object Instantiation using DirectCOM & RC6
Originally Posted by Schmidt
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?
-
Aug 25th, 2023, 10:05 PM
#11
Re: Registry Free Object Instantiation using DirectCOM & RC6
Originally Posted by taishan
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.
-
Aug 25th, 2023, 10:14 PM
#12
Re: Registry Free Object Instantiation using DirectCOM & RC6
Originally Posted by taishan
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
-
Aug 25th, 2023, 10:15 PM
#13
Re: Registry Free Object Instantiation using DirectCOM & RC6
Originally Posted by taishan
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.
-
Aug 25th, 2023, 10:20 PM
#14
Re: Registry Free Object Instantiation using DirectCOM & RC6
Originally Posted by jpbro
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
-
Aug 25th, 2023, 10:23 PM
#15
Re: Registry Free Object Instantiation using DirectCOM & RC6
Originally Posted by taishan
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.
-
Aug 30th, 2023, 08:02 PM
#16
Addicted Member
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!
-
Aug 31st, 2023, 01:11 AM
#17
Re: Registry Free Object Instantiation using DirectCOM & RC6
Originally Posted by Schmidt
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.
-
Mar 20th, 2024, 03:16 PM
#18
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.
-
Mar 21st, 2024, 06:12 AM
#19
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...
-
Mar 21st, 2024, 06:38 AM
#20
Re: Registry Free Object Instantiation using DirectCOM & RC6
Originally Posted by VanGoghGaming
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
-
Mar 21st, 2024, 09:48 AM
#21
Addicted Member
Re: Registry Free Object Instantiation using DirectCOM & RC6
Originally Posted by VanGoghGaming
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.
-
Mar 21st, 2024, 11:37 AM
#22
Re: Registry Free Object Instantiation using DirectCOM & RC6
Originally Posted by taishan
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.
-
Mar 21st, 2024, 09:22 PM
#23
Addicted Member
Re: Registry Free Object Instantiation using DirectCOM & RC6
Originally Posted by jpbro
...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
-
Mar 21st, 2024, 10:16 PM
#24
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.
-
Mar 21st, 2024, 10:59 PM
#25
Addicted Member
Re: Registry Free Object Instantiation using DirectCOM & RC6
Originally Posted by jpbro
...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
-
Mar 22nd, 2024, 09:56 AM
#26
Re: Registry Free Object Instantiation using DirectCOM & RC6
Originally Posted by jpbro
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, IID_IClassFactory As String = "{00000001-0000-0000-C000-000000000046}"
Public Function RegFree(sLibName As String, sClassName As String) As Object
Static sCurrentLib As String, lpDllGetClassObject As Long
Dim RegFreeIUnknown As IUnknown, tliTypeLibInfo As oleexp.ITypeLib, i As Long, lpTypeAttr As Long, tTypeAttr As oleexp.TYPEATTR, classGUID As oleexp.UUID, lpInterfaceTypeAttr As Long, _
tInterfaceTypeAttr As oleexp.TYPEATTR, interfaceGUID As oleexp.UUID, sInterfaceName As String, IIDIClassFactory As oleexp.UUID, 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)
lpTypeAttr = .GetTypeAttr
CopyMemory tTypeAttr, ByVal lpTypeAttr, LenB(tTypeAttr)
classGUID = tTypeAttr.iid
With .GetRefTypeInfo(.GetRefTypeOfImplType(0)) ' Get the type description of the interface implemented by this class
.GetDocumentation MEMBERID_NIL, sInterfaceName, vbNullString, 0, vbNullString
lpInterfaceTypeAttr = .GetTypeAttr
CopyMemory tInterfaceTypeAttr, ByVal lpInterfaceTypeAttr, LenB(tInterfaceTypeAttr)
interfaceGUID = tInterfaceTypeAttr.iid
.ReleaseTypeAttr lpInterfaceTypeAttr
End With
.ReleaseTypeAttr lpTypeAttr
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")
End If
oleexp.CLSIDFromString IID_IClassFactory, IIDIClassFactory
InvokeObj Nothing, lpDllGetClassObject, VarPtr(classGUID), VarPtr(IIDIClassFactory), VarPtr(objClassFactory) ' Call DllGetClassObject to retrieve the class object from the DLL object handler
objClassFactory.CreateInstance Nothing, interfaceGUID, 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
Public Function InvokeObj(Interface As IUnknown, vtbOffset As Long, ParamArray ParamsArray() As Variant) As Variant
Dim ParamTypes(0 To 10) As Integer, ParamValues(0 To 10) As Long, lParamCount As Long, vParams As Variant, lRet As Long
InvokeObj = S_FALSE: vParams = ParamsArray
For lParamCount = 0 To UBound(vParams): ParamTypes(lParamCount) = VarType(vParams(lParamCount)): ParamValues(lParamCount) = VarPtr(vParams(lParamCount)): Next lParamCount
If Not (Interface Is Nothing) Then
lRet = DispCallFunc(ByVal ObjPtr(Interface), vtbOffset, CC_STDCALL, vbLong, lParamCount, ParamTypes(0), ParamValues(0), InvokeObj)
ElseIf vtbOffset > 1024 Then
lRet = DispCallFunc(ByVal 0&, 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")
If objRegFree Is Nothing Then Debug.Print "Object is Nothing!" Else objRegFree.CallSomeMethod
End Sub
Here's the demo project for who's interested: RegFree.zip (requires a reference to Fafalone's "oleexp" TypeLib)
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!
-
Mar 22nd, 2024, 01:38 PM
#27
Re: Registry Free Object Instantiation using DirectCOM & RC6
Originally Posted by VanGoghGaming
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
-
Mar 22nd, 2024, 02:33 PM
#28
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!
-
Mar 26th, 2024, 11:32 AM
#29
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.
To try the demo:
- 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)
- Copy the RC6 binaries to \Rc6Regfree\Demo\Build\System\ (DO NOT register them here).
- Open \Rc6Regfree\Demo\Source\MyRc6RpcDll1\MyRc6RpcDll1.vbp and compile the DLL to Demo\Build\System\RPCDlls\
- Open \Rc6Regfree\Demo\Source\MyRc6Dll1\MyRc6Dll1.vbp and compile the DLL to Demo\Build\System\
- 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.
- 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.
Enjoy!
Last edited by jpbro; Mar 26th, 2024 at 05:47 PM.
-
Mar 26th, 2024, 06:49 PM
#30
Addicted Member
Re: Registry Free Object Instantiation using DirectCOM & RC6
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|