- Written by Chris
- Created: 26 January 2017
In part 1 and part 2 of this article series I have shown you some problems that we found in existing code (in our own code as well, and we thought that that code was perfect). Today we will discuss an important other problem, that has to do with WIn32 interoperability.
Win32 Callback functions
There exist many functions in the Win32 API that require a callback function to return their results to, or use it for some other reason. Probably the most known one is the EnumWindows() function, which enumerates all top-level windows managed by the OS and it can be used like this (VO code – please don’t mind the style, it’s for minimum line length):
FUNCTION Start() AS INT
EnumWindows(@EnumWindowsCallback() , 0)
// this is the callback function:
FUNCTION EnumWindowsCallback(hWnd AS PTR , lParam AS INT) AS LOGIC
LOCAL pszString := MemAlloc(100) AS PSZ
IF GetWindowText(hWnd,pszString,100) != 0
There are a lot of such callback functions used in the VOSDK and in a lot of VO-programmers’ code as well. And this code can work in .Net, too, but unfortunately not without some changes, because the calling convention of the function when compiled in .Net is different to what the OS expects. Making those changes (see below) is not too difficult, but the real hard part is to locate those places (possibly among 100,000s of other lines of code) where callbacks are used.
The problem is that Vulcan compiles this code “as is”, without any errors or warnings, so the programmer is not aware that there is some problem with it when running it in .Net. It is only when the code is executed (possibly by the end user…) that it becomes apparent that it does not do what it is expected to do and it either just fails, or throws a runtime exception due to memory corruption.
Fortunately, X# does not allow this to compile, but instead reports: error XS7036: There is no argument given that corresponds to the required formal parameter 'hWnd' of 'Functions.EnumWindowsCallback(void*, int)'. The error message can be improved (we will do that in one of the next X# releases), but even now it tells us that this is not valid code and we must modify it so that it works properly in .Net. Without going into details, the required change is to use a delegate for the callback function so that it gets called properly under the .Net environment. This is the correct modified version (the EnumWindowsCallback function remains unchanged) for both X# and Vulcan:
// delegate with same signature with the function:
DELEGATE CallbackDelegate(hWnd AS PTR , lParam AS INT) AS LOGIC
FUNCTION Start() AS INT
LOCAL oCallbackDelegate AS CallbackDelegate
oCallbackDelegate := EnumWindowsCallback
LOCAL oFunctionPointer AS IntPtr
oFunctionPointer := Marshal.GetFunctionPointerForDelegate(oCallbackDelegate)
EnumWindows(oFunctionPointer , 0)
If you try to compile the GUI classes of the Vulcan SDK in X#, you will get one such compiler error for the __SubClassForDragList() method of the DialogWindow class (it is used by ListBox: EnableItemDrag(), which enables drag & drop of items inside a ListBox). But the error is reported in a line that is not supposed to be compiled, as it is included within the #else part of an #ifdef ..#endif preprocessor directive. Looking at the code more closely, we can see why this happens, it is because there is a typo in the “#ifdef __VULCAN_” directive in the beginning of that method, there’s an underscore missing at the end of that line! For this reason the wrong code is being compiled and as a result it does not work properly at runtime, causing this drag & drop feature of ListBoxes not to work at all in Vulcan. Admittedly, this is partially our own fault as well, as we were for many years in charge of maintaining this SDK code, but unfortunately we didn’t have back then the powerful X# compiler to reveal those problems to us.
This concludes our article for today. Next week, in part 4 of this article, I will discuss some other problems we found.