Large VB6 applications are composed of many VBPs and it is common to see certain code files referenced by more that one VBP. The default translation process for a VBP creates a .NET version of each code file referenced by the VBP. If there are multiple VBPs referencing the same code file, there will be multiple copies of the shared files: one for each VBP. Restructuring the generated code files to remove this duplication is a common upgrade feature. Many different configurations of code files are feasible, but there are constraints. The constraints arise from the external dependencies that are contained within each file and from having to still satisfy those dependencies after conversion – and moving files without creating build cycles or other conflicts is a challenge. Some configurations are easier to implement, some are easier to describe, and some are more elegant and maintainable. It is difficult to say that one configuration is “best” -- only that one may be better or worse than another for some reason. There is no magic algorithm to determine the “best configuration”. For example, one might say having all of the shared files in one assembly is “bad” and we agree with that in concept, but we cannot say in general that it is never appropriate, or why some other organization of shared files would be better.
For a large-scale VB6 application with shared files, it is possible to reproduce something close to the structure of the legacy app using
.NET linked files, but it is not a straight conversion: we would have to make some decisions and also deal with assigning and using namespaces so it will be different from VB6 with wide spread implications across the C# code. Code file linking has other disadvantages for builds and deployments so it has a higher cost of ownership. Consequently, there is some value to trying to host each shared file in one specific assembly and reference it there. The tool can help implement this re-hosting process, but it does not design nor prescribe the end state for you.
Assuming we want to go with
hosting rather than
linking, we begin with a guiding principle: each file should be referenced by at most one assembly (the host). Once in that host, the file will become a class in the namespace of the host. If a class is used by other assemblies, those others must reference the host assembly and use the shared class members through the host namespace. But, as always, the problems are in the details.
Regarding shared files, we offer these options:
- Make an independent copy of the shared file for each referencing project. This is the default, but it is typically only used in the initial iterations of an upgrade solution.
- Compile a single copy of the shared file into one of the existing projects or into a custom 'host' project and reference the shared file content through the host assembly. This is done using the
"SharedFile" statement described here https://portal.greatmigrations.com/display/GMG/gmplSharedFileStatement
Put a single copy of the shared file in a specified location in the source directory and reference the shared file content through a link to the shared file. This can be achieved fairly easily with a few translation script commands; however additional work is also needed to set a namespace for the linked file and then use that namespace in references to the contents of the file.
How To: Shared Files Consolidation
I have a set of VBPs that share code files; that is, many VBPs reference the same code files. In default gmStudio Translations, the resulting .NET projects each gets separate copies of the shared files they reference. This file copying simplifies the translation, but it has some disadvantages in terms of version control, builds, and deployments. An alternative structure is to put the shared files into one of more "host" assemblies and have the translated application projects reference the shared files through the host assembly. Migrating to this assembly-based code sharing model is the approach described here.
For this example, all of the shared code files will be placed into a single new .NET DLL assembly project and a single new namespace. This "Single Dedicated Host" strategy is appropriate when the legacy code base lacks shared DLLs of its own and when there is no benefit to creating multiple host DLLs.
Step 1: Creating the Single Dedicated Host Project
The legacy system does not have any DLLs that could have shared files added, so a host DLL must be created from scratch.
First create an empty starter VBP manually adding references to the share code files and any required COM.
To get information needed to do this, I create a SharedFiles script that loads the legacy VBPs that share code:
<Storage Action="Create" Identifier="Shared" />
<select Target="%UserFolder%" />
<Select Local="%IdfFromCodeFolder%" />
<Select System="%IdfFromIdlFolder%" />
<Select Progress="1" />
<Load project="%VirtualRoot%\ABCView\ABCView.vbp" SourceCode="On" />
<Load project="%VirtualRoot%\ABCRead\ABCRead.vbp" SourceCode="On" />
<Load project="%VirtualRoot%\ABCManage\ABCManage.vbp" SourceCode="On" />
<Load project="%VirtualRoot%\ABCMonitor\ABCMonitor.vbp" SourceCode="On" />
<Load project="%VirtualRoot%\ABCReport\ABCReport.vbp" SourceCode="On" />
<Load project="%VirtualRoot%\ABCClock\ABCClock.vbp" SourceCode="On" />
<Load project="%VirtualRoot%\ABCEmailReport\ABCEmailReport.vbp" SourceCode="On" />
<Load project="%VirtualRoot%\ABCCheck\ABCCheck.vbp" SourceCode="On" />
<Load project="%VirtualRoot%\ABCKey\ABCKey.vbp" SourceCode="On" />
<Load project="%VirtualRoot%\ABCUpdate\ABCUpdate.vbp" SourceCode="On" />
<Storage Action="Close" />
Add script as a Custom task in the gmStudio project and process it with the Translate operation. The resulting log output contains a detailed report of code file sharing across the VBPs followed Registry-SharedFiles commands:
Basic Processor V31.13(08/02/20) System Build(08/02/20 4:21:15)
Processing file: \Applications\ABCCommon\ABCCommon.vbp
Processing file: \Applications\ABCView\ABCView.vbp
Processing file: \Applications\ABCRead\ABCRead.vbp
Processing file: \Applications\ABCManage\ABCManage.vbp
Processing file: \Applications\ABCMonitor\ABCMonitor.vbp
Processing file: \Applications\ABCReport\ABCReport.vbp
Processing file: \Applications\ABCClock\ABCClock.vbp
Processing file: \Applications\ABCEmailReport\ABCEmailReport.vbp
Processing file: \Applications\ABCCheck\ABCCheck.vbp
Processing file: \Applications\ABCKey\ABCKey.vbp
Processing file: \Applications\ABCUpdate\ABCUpdate.vbp
There were 44 Shared Files in this group of projects: Modules = 30, Forms = 8 Classes = 6
The Module file [\Applications\glbl-Monitor.bas] is shared by 11 projects
<Registry type="SharedFile" Source="\Applications\glbl-Monitor.bas" Target="\Applications\ABCView\ABCView.vbp;ABCView.exe" />
<Registry type="SharedFile" Source="\Applications\glbl-Monitor.cls" Target="\Applications\ABCView\ABCView.vbp;ABCView.exe" />
<Registry type="SharedFile" Source="\Applications\glbl-modShift.bas" Target="\Applications\ABCClock\ABCClock.vbp;ABCClock.exe" />
<Registry type="SharedFile" Source="\Applications\glbl-modLocalPort.bas" Target="\Applications\ABCClock\ABCClock.vbp;ABCClock.exe" />
Note: this process does not detect code clones (i.e. independent files with identical content). If you want to address clones, all sharing VBPs must be modified to point to one of the clone copies.
The listing generated by the SharedFilesPrep script reveals both the shared files and potential VBPs that might "host" them. However, in this case, these are EXE projects that are not suitable as shared hosts. This listing shows the majority of SharedFiles end up in ABCView. I will use the ABCView.vbp as the starter for my new Dedicated Host VBP just to get most of the COM references required.
The new VBP is modified to have the name ABCCommon, inside and out. Several VBP attributes are changed: Type=OleDll and Startup="(None)".
The new VBP must reference all of the shared code files as well the unique set of COM file references (ocx, tlb, dll) found in the VBPs above: ABCView, METRead, ABCManage, and ABCClock.
The new VBP may not build in VB6 depending on environmental factors and that is not a requirement. However, the VBP must translate with gmStudio giving a Host project: ABCCommon.csproj that builds in .NET.
Step 2: Translating the legacy code to .NET codes that use the Host Assembly
Step 2a. Generate the Registry-Shared Files commands:
A Load command is added to the top of the list of Load commands in the SharedFilesPrep script:
<Load project="%VirtualRoot%\ABCCommon\ABCCommon.vbp" SourceCode="On" />
Running SharedFilesPrep.xml gives a listing of Registry-SharedFiles commands all having Target=ABCCommon.
<Registry type="SharedFile" Source="\Applications\glbl-Monitor.bas" Target="\Applications\ABCCommon\ABCCommon.vbp;ABCCommon.dll" />
<Registry type="SharedFile" Source="\Applications\glbl-Monitor.cls" Target="\Applications\ABCCommon\ABCCommon.vbp;ABCCommon.dll" />
<Registry type="SharedFile" Source="\Applications\glbl-modABCMail.bas" Target="\Applications\ABCCommon\ABCCommon.vbp;ABCCommon.dll" />
<Registry type="SharedFile" Source="\Applications\glbl-modLocalPort.bas" Target="\Applications\ABCCommon\ABCCommon.vbp;ABCCommon.dll" />
This is as expected and it shows we created the shared host VBP properly.
Step 2b. Add the Registry-SharedFiles commands into a GlobalSettings file
Copy the Registry-SharedFiles commands from the log of SharedFilesPrep to a GlobalSettings file.
Translate the GlobalSettings file creating a GlobalSettings.vbi file.
Note: A code base with many shared files is likely to already have a GlobalSettings file containing EditFile and RefactorFiles. The registry commands may be placed in a block at the top or bottom of this file.
Step 2c. Activate the GlobalSettings and SharedFiles migration
<Select GlobalSettings="%UserFolder%\GlobalSettings" />
<Select SharedFile="on" />
Note: The Select GlobalSettings command may already be present. A second one should not be added.
Step 2d. Run the Translations
Step 3. Verify Results
The SharedFiles migration causes the following types of changes in the generated codes:
1) The IDFs generated for the shared host VBPs will contain declarations for all public elements of files registered as SharedFiles.
2) The translation logs of VBPs that contained shared files will report loading the IDFs for the SharedFiles hosts.
3) The translations of VBPs that contained shared files will reference SharedFiles host assemblies.
4) References to SharedFiles host API elements will be done through the SharedFiles namespaces.
Note: for this example, there is a single SharedFile host and namespace: ABCCommon.
Step 4. Put it all together in a Visual Studio solution
Step 4a. Add the csproj for the host assembly to the GenerateSolution.txt file for the system
:: Description: VS Solution Project List
Step 4b. Use gmStudio to generate the full integrated Visual Studio solution with projects cross-referencing each other.
The build model now contains a new dependency on the Shared host assembly and references to it must be made properly throughout the rest of the translations.
Step 4c. Build the VS solution to test the integration of the Shared Host assembly