Skip to content
PYD ENTERPRISES

Migration guide

VB6 to C# on modern .NET: extract the rules hiding in forms and modules

VB6 applications keep their business logic in button click handlers, global modules and Variant arithmetic. This guide covers the type and error-handling traps and the extraction workflow that turns them into a spec for C#.

Visual Basic 6C# / .NET
DIFFICULTY
High
OUTPUT
Rule / schema JSON
SYNTAX TRAPS
6 covered
WORKFLOW
6 steps

By Pranav Kulkarni, Head of Language Engineering · Updated 15 Oct 2026

Technical syntax challenges

Where Visual Basic 6 migrations go wrong.

01

Currency is a scaled 64-bit integer

VB6 Currency stores a 64-bit integer scaled by 10,000: four fixed decimal places. It maps to C# decimal, not double. Arithmetic that mixes Currency with Single or Double is done in floating point and then rounded back to four places on assignment, which a straight C# port does not reproduce.

02

Integer is 16 bits, Long is 32

VB6 Integer is short in C# and Long is int. Counters that overflowed at 32,767 in production raised error 6, and an On Error handler may have turned that into a business outcome nobody documented.

03

On Error Resume Next changes control flow

After On Error Resume Next, a failing statement is skipped and execution continues with variables in whatever state they had. Extraction has to treat every statement in that scope as a possible silent branch and record which outputs are still assigned when it fails.

04

Variant coercion and late binding

Dim x without a type is a Variant, and "10" + 5 evaluates to 15 while "10" & 5 is "105". Objects created with CreateObject("ADODB.Recordset") are late bound, so member calls resolve only at runtime and need type inference from usage.

05

Logic inside form event handlers

cmdApprove_Click often validates input, calculates a price and writes to the database in one handler, reading control values like txtQty.Text directly. The rule extractor must lift those reads into named inputs before any C# service boundary makes sense.

06

ByRef by default

Parameters are ByRef unless declared ByVal. A helper that quietly modifies its argument is an extra output of the call, and the C# contract needs it as a return value or ref parameter, not a hidden mutation.

Extraction workflow

6 steps from source to signed-off spec.

The sequence a PYD ENTERPRISES Discovery Scan runs for this stack. Every output links back to source file and line range.

  1. 1

    Collect the project group

    Ingest the .vbp and .vbg files with every .frm, .bas, .cls and .ctl they reference, plus type libraries for referenced COM components so early-bound calls resolve.

  2. 2

    Parse forms, modules and classes to an AST

    PYD ENTERPRISES parses VB6 source including the form designer section, so control names, default properties and event handler bindings are known symbols.

  3. 3

    Infer types for Variants and late-bound objects

    Dataflow analysis tracks assignments to each Variant and object variable and records the inferred type set; ambiguous ones are flagged for review rather than guessed.

  4. 4

    Model On Error scopes as explicit branches

    Each On Error Resume Next and On Error GoTo scope becomes explicit error edges in the control-flow graph, so silent-skip paths appear as rules or review items.

  5. 5

    Lift UI-bound logic into decisions

    Program slicing from each database write and each displayed result isolates calculations from UI plumbing and emits them as DMN 1.5 tables with named inputs.

  6. 6

    Export contracts and equivalence fixtures

    Data classes become JSON Schema, candidate service operations become OpenAPI 3.1, and boundary fixtures are generated for every decision so C# implementations can be checked in CI.

Legacy in, specification out

The source, and what PYD ENTERPRISES extracts from it.

Left: the Visual Basic 6 construct as it sits in production. Right: the reviewed artifact your C# / .NET engineers build against. PYD ENTERPRISES produces the spec and the tests; your team writes the service.

LEGACY SOURCE

modPricing.basVB6
1Public Function CalcDiscount(ByVal custType As String, _
2 ByVal orderTotal As Currency, _
3 ByVal yearsActive As Integer) As Currency
4 On Error Resume Next
5 Dim rate As Single
6 Select Case custType
7 Case "W"
8 If orderTotal >= 10000 Then rate = 0.12 Else rate = 0.08
9 Case "R"
10 If yearsActive > 5 Then rate = 0.05
11 Case Else
12 rate = 0
13 End Select
14 CalcDiscount = orderTotal * rate
15End Function

EXTRACTED ARTIFACT

PRICING-0007.dmn.jsonJSON
1{
2 "decision": "Order discount rate",
3 "hitPolicy": "FIRST",
4 "inputs": ["custType", "orderTotal", "yearsActive"],
5 "output": { "name": "rate", "type": "decimal" },
6 "rules": [
7 { "id": "R1", "when": ["\"W\"", ">= 10000", "-"], "then": 0.12 },
8 { "id": "R2", "when": ["\"W\"", "< 10000", "-"], "then": 0.08 },
9 { "id": "R3", "when": ["\"R\"", "-", "> 5"], "then": 0.05 },
10 { "id": "R4", "when": ["\"R\"", "-", "<= 5"], "then": 0 },
11 { "id": "R5", "when": ["-", "-", "-"], "then": 0 }
12 ],
13 "review": [
14 "rate is Single: 0.12 is stored as 0.119999997; result rounded to 4 dp",
15 "On Error Resume Next: overflow returns 0 discount silently"
16 ],
17 "source": "modPricing.bas:1-15"
18}

Architectural best practices

Build the C# / .NET side to the spec, not to the old shape.

  • Map Currency to decimal and decide explicitly whether the new service keeps four-place intermediate precision or rounds to cents at the boundary.
  • Replace On Error Resume Next behavior with explicit results the business has approved; never port the silence.
  • Move every rule out of form event handlers into services with named inputs, so the same rule serves the web UI, APIs and batch.
  • Treat each COM component the VB6 app calls as an integration contract and document it before replacing either side.
  • Keep the extracted DMN tables as the source of truth for pricing and eligibility, and let C# code call a decision service or implement the table under test.

Why upgrade wizards were never enough

Microsoft's old VB6 upgrade path and later commercial converters produce C# or VB.NET that compiles, with compatibility shims for Variant, control arrays and On Error. What they cannot produce is an answer to the question your product owner asks: what does this pricing screen actually decide?

PYD ENTERPRISES answers that question. It extracts decisions, contracts and error paths from the VB6 source with links to file and line, and your analysts sign them off. Your .NET engineers then write C# services against reviewed specs, using the generated fixtures to prove each decision matches. PYD ENTERPRISES does not write the C# application.

Finding the database logic

VB6 applications usually talk to SQL Server or Oracle through ADO, with SQL assembled as strings: "SELECT * FROM Orders WHERE CustID = " & txtCust.Text. PYD ENTERPRISES resolves string concatenation where the fragments are static and records the tables, columns and predicates each query touches. That gives the data lineage behind each rule and flags injection-prone concatenations that the replacement must not carry forward.

Where stored procedures do the real work, pair this guide with the T-SQL to .NET services guide: PYD ENTERPRISES traces calls from ADODB.Command into the procedure body so one rule inventory spans both layers.

FAQ

Visual Basic 6 to C# / .NET questions

Can PYD ENTERPRISES parse VB6 forms or only modules?

Both. Form files are parsed including the designer section, so controls, their default properties and event bindings are known to the analyzer. Logic in handlers like cmdSave_Click is extracted the same way as logic in .bas modules.

How does PYD ENTERPRISES handle Variant types?

It infers a type set for each Variant from assignments and usage across the project. Where the set has one member, the contract uses it; where it does not, the field is flagged in the review queue with the assignments that disagree.

Does the output target .NET Framework or modern .NET?

Neither directly. The outputs are DMN, JSON Schema, OpenAPI 3.1 and test fixtures, which are runtime neutral. Most teams build on current .NET, and the fixtures run against whatever service they build.

Get the spec for your Visual Basic 6 estate.

Request a codebase scan and review extracted rules, contracts and fixtures for your own Visual Basic 6 estate, inside your VPC.