This is a tale of the "fun" I've had creating some software. It involves taking a Microsoft technology called Virtual Channel and using it with an older programming language, Delphi. I want to show what can happen when translating code into other languages, and how I got around the inevitable stumbling blocks. Always good to learn new lessons when fiddling with older code bases, no?
What are virtual channels? In short, they’re a technology that you can use to send messages between your local computer (laptop etc.) and a remote computer. It’s not a new technology, but it’s also not well-documented outside of Microsoft’s own materials—there are a couple of open-source projects, some questions on Stack Overflow, plus some discussions on a handful of websites, and that’s it.
My job was to make our property-centric financial software interoperate with Remote Desktop Services using virtual channels. It was envisaged that this could save a lot of money—a million dollars a year, potentially!
We are a Delphi shop with a bit of .NET. As I'm the only developer on the team who knows C++, it would make sense to task me with making Virtual Channels also work with Delphi. So here are the roadblocks (Gotchas!) I encountered. Take note: Although this dives into Delphi, you can get these kinds of issues with any programming language.
Gotcha #1: No Delphi Code
As luck would have it, there are a couple of Delphi examples for Virtual Channels (VC) online, but people had trouble making VC work on OS from Windows 2012 server onwards. In short, it seemed that the older VC didn’t work on newer operating systems—which meant I needed to take a different approach: Dynamic Virtual Channels (DVC).
Dynamic Virtual Channel APIs expand on the existing Virtual Channel APIs, solving existing VC issues such as a limited number of channels. I found an excellent implementation of DVC on GitHubin C++; I compiled it, and it worked.
But that wasn’t the end of my problems! There were no examples available for DVC except in C++, so I would have to translate those C++ examples to Delphi. That was my first Gotcha! We’re talking about 1,500 lines of code; not exactly a trivial task. My big lesson here: scope out the work ahead of time (if at all possible).
Gotcha #2: COM
COM (Component Object Model) is an essential part of Windows, but it's one area I've always avoided in my somewhat limited C++ programming experience. COM makes it easier to run another application without knowing where it is located; you register the application and it stores its details in the registry.
Unfortunately for me, DVC required the local end to be implemented as a COM dll. When you launch mstsc.exe to start RDP, if a COM object has been previously registered, it’s launched and runs within the mstsc.exe process.
To work correctly, the COM object class must implement several interfaces (an interface isn’t mysterious; just a specification that says which functions your software must provide to satisfy that interface). I discovered (bit of a fluke, really) that in a folder on my PC: (C:\Program Files (x86)\Windows Kits\10\Include\10.0.17763.0\um), there was a file called ‘tsvirtualchannels.idl.’ It was a text file some 444 lines long that provided definitions of the interfaces needed for RDPs, their functions and parameters, and so on.
Around half of these interfaces had to be implemented, with the other half implemented by the RDP framework.
You can find a lot of useful stuff by searching the web, but because COM and Delphi were popular about 15 years ago, I only found lots of “link rot.” When dealing with older technologies, you may just have to buy a book, but odds are it’ll be second-hand. Sometimes technical knowledge is not easy to acquire, even in our age of Googling.
Gotcha #3: No Type Library
A .idl file is the source-code definition of the interfaces. It’s not pleasant to read or write, but there are software tools that ease creating it. Fortunately, Delphi has a type library editor: You compile an idl file (idl is short for Interface Definition Language) using a compiler supplied by Microsoft. That usually compiles it into a .tlb file, which is a file that Windows understands.
Delphi can import .tlbs and usually convert them to a source code module. I say ‘usually’ because, when I compiled tsvirtualchannels.idl, it didn't create a .tlb file. That's because there is no type library definition in the file, and no library section means no .tlb file is created.
Luckily, there's nothing to stop you from manually editing and adding that library section into the file. I did just that and got my .tlb file and a source file after importing it into Delphi.
Figuring this all out took me over a week. When you are outside your comfort zone and there’s a ton of new stuff to pick up, it will take longer (much longer!) than you anticipated; plan accordingly, no matter how good you think you are. I scoured the web for so much information, I was regularly running up against the “can only have 50 tabs open in Chrome browser at one time” limit!
Gotcha #4: Delphi and C++ Disagree!
Amidst all this, I hit a curious error that I’d never seen before. The computer-generated source files had a small number of errors in them; this seems mostly because of a fundamental difference in the syntax of C++ and Delphi regarding passing of parameters. These weren’t syntax errors, but actual semantic mistakes with different meanings to the original code!
Without getting too technical, what should have been a pointer to a block of ram in C++ became a passed-by-reference byte!
byte * pointerVariable; // C++
Var pointerVariable : byte; // Delphi
These are definitely not the same. I just think the Delphi IDE that created this was trying to be too clever. You can just write it in Delphi per the below (PByte (Delphi) is the same as byte * (C++)):
pointerVariable : Pbyte;
Two lessons here. First, check source code created by tools very carefully. Second: Only edit computer-generated files if you really have to, and document your process very carefully. You do not want someone else compiling the files, generating new error-prone files that overwrite your edited code, and leaving everything either not compiling or running incorrectly.
Gotcha #5: Delphi Compiler's Older Versions
The Delphi compiler I use is five years old and occasionally relies on software that has not been kept up-to-date (only some of these low-level technical functions have been updated over time). I couldn’t physically translate some function calls (and the data structures they used) because the libraries were outdated—specifically, the ADDRINFOW data structure.
Luckily I found an open-source code library that did the trick.
Finding equivalents between two languages is sometimes necessary. I did a physical translation very rapidly at the start of the project, and compiling it revealed the deficiencies; fixing took another couple of days, though. Slow, tedious work! Focus your effort so you can discover these issues early on in the process. You don’t want to have the project 90 percent done, only to find a huge hole you can’t fill!
After about 14 weeks of stumbling in the dark, reading many webpages, posting questions on StackOverflow, and writing and testing lots of code, I succeeded in getting my code working. It was a tremendous moment, a high point in my programming career.
It has been a massive learning exercise for me, consuming far more development time than I anticipated. It’s almost impossible to estimate the delays that “unknown unknowns” can add to a project. The old adage about doubling your initial estimate and adding three definitely rings true here! Keep all that in mind during your next big project.