Multi-Language Programming - Visual Basic and Clarion


One of the more interesting aspects of programming with Clarion is how to interface it to other languages. One of the more difficult languages to interface to is Visual Basic. Why? - because at least interfacing to C is documented in the Clarion Programmers Guide under "Multi Language Programming".

VB6 does not support threads or the creation of DLLs, so it's very unlikely that you'll be calling VB code from Clarion. Instead, you are more likely to go the other way - a VB executable calling a Clarion DLL.

For the most part it's pretty easy, but there are some things to watch out for, especially when passing string parameters.

Listed in the VB Component Tools Guide is a section on accessing DLLs and calling the Windows API. For the most part, all of the information you require to call a Clarion DLL is documented here.

One of the example VB prototypes is for GetTickCount.  In VB it's declared as 

Declare Function GetTickCount Lib "kernel32" Alias _
"GetTickCount" () As Long
It's Clarion prototype would be:
Module('Kernel32')
  GetTickCount(),Long,Raw,Pascal
End

Calling Convention

Clarion, as standard, uses a propretary register-based calling convention.  It is documented in Chapter 5 ('Multi-Language Programming") of the TopSpeed Advanced Programmers Guide, but basically involves putting parameters into the AX/BX/CX/DX registers of the CPU. If they run out of registers, parameters are then pushed on to the stack in left-to-right order.

Virtually all other programming languages put all their parameters on the stack. The only difference is what order (left-to-right or right-to-left) they do it in. The C calling convention specifies right-to-left and the Pascal calling convention specifies left-to-right.

VB uses the Pascal calling convention, so any Clarion procedure that accepts parameters from a VB application has to be prototyped with the Pascal attribute. If this is a Clarion DLL that will be called by both VB and Clarion applications you must use the Pascal attribute in both the Clarion DLL and application.

Name Mangling

Pay special attention to the section in the VB documentation on declaring VB procedures with non-standard names. 
Because of Clarion's name-mangling, the exported name of the procedure you want to call may be substantially different
than the name by which you know the procedure in the Clarion code.

To calculate the mangled name for a Clarion procedure you'll need to use the pro2exp utility. If you can't find the executable then it's normally included as hand-coded legacy source code with C4 and C5

Why does Clarion use name mangling in the first place? That question is answered, although you wouldn't know it, in the procedure overloading section of the "Program Source Code Format" chapter in the Clarion Language Reference manual.

A better (and slightly out-of-date) answer can be found in in Appendix C ("Name Encoding") of the TopSpeed C++ Language Reference:

"In order to implement function overloading, and the scope rules of C++ which allows identical external names to be declared in different scopes, all function names and external data names are encoded by TopSpeed C++ to allow them to be distinguished by the linker. Although the TopSpeed system will decode these names when they are displayed (in MAP files and by VID, for example), it may be useful to know the encoding algorithm used in order to interface to other products."

Using the NAME() attribute or the Pascal calling convention on a Clarion prototype disables name mangling, which is why only one of the three example Clarion procedures has a mangled name - the other two accept parameters, so have the Pascal calling convention.

Passing Simple Parameters

To start with, lets create a Clarion DLL which exports a function. This function does nothing but return a byte. The code to do this may look rather similar to the following:

  Program
  Map
    CWReturnByte(),Byte
  End
  Code
CWReturnByte Function
  Code
  Return(100)

When you compile a DLL Clarion will automatically create an export file. The export file (*.EXP) will tell the linker what functions and variables are exported from the DLL.   We can look up the mangled name of our function by looking in the .EXP file (or in the .MAP file)

Once we know the mangled name ("CWReturnByte@F") we fire up VB and create a new form. In the (General) (Declarations) section we prototype our new Clarion function as follows:

Private Declare Function CWProcedure _
Lib "CALLCW.DLL" _
Alias "CWReturnByte@F" () As Byte

VB uses the Alias keyword on a declaration in the same manner that Clarion uses the Name attribute on a prototype. The procedure is known internally to the VB code as "CWProcedure" but the VB runtime will look for a procedure called "CWReturnByte@F" in the CallCW DLL.

Passing String Parameters

Passing string parameters from VB to Clarion is slightly more complex. VB allows the use of the ByVal or ByRef keywords when declaring a procedure. One passes parameters by value, the other by reference (or address, in Clarion terms).

The problem is, VB doesn't stick to that when passing string parameters to Clarion. VB actually converts the string to a CString and passes by address.  Your Clarion procedure has to be prototyped to take a LONG, and then you have to call lstrcpy ( a C runtime function to copy a string in memory ) to de-reference the pointer.

So, your VB declaration looks like this:

Private Declare Function CWAcceptStringReturnByte _
Lib "CALLCW.DLL" _
(ByVal CWdata As String) As Byte

Your Clarion prototype and code would actually be this:

  Program
  Map
    CWAcceptStringReturnByte(Long),Byte,Pascal
  End
  Code

CWAcceptStringReturnByte Function(Prm:Long)
TempString CString(100)
  Code
  x# = lstrcpy(Address(TempString),Prm:Long)
  Message(Clip(TempString),'CWAcceptString')
  Return(100)

DLL Required?

There is also another difference between VB and Clarion when it comes to using external DLLs.

In Clarion, when you prototype a procedure that is included in another DLL into your executable, your executable and DLL must both be present before the executable is run. The operating loads your executable into memory and resolves any lookups into the DLL when it attempts to load the executable. If the DLL is not present you receive an error message, and your application does not run.

In VB, the VB runtime library loads the DLL and resolves the lookups at runtime. The DLL does not need to be present in order to load the executable. However, if you do call a procedure in a DLL that is not present you receive a runtime error.

In essence what the VB runtime is doing is placing a small wrapper around every call to a procedure that is declared as being in another DLL.   That wrapper effectively does nothing more than dynamically load the DLL via a call LoadLibrary() and calls the procedure via GetProcAddress().

Download source code


Back to my home page, my Clarion page, or send me mail at paula@attglobal.net

 

1