![]() |
|
Published 2005-08-31 Printer-friendly version
I'll say it right up front. I like the Clarion language a lot, and I always have. I've been using it along with a half-dozen other languages since the dawn of time, and I find Clarion's syntax and template-driven code paradigm to be extremely productive. What I've always hated about it is that it never plays nicely with the rest of the world. COM interop has always been a nightmare. Third party COM libraries are nearly non-existent, and when you do get it to work with other systems, it's seldom smooth or seamless. Such a simple task as embedding an Active-X control, or using any of the many of the thousands of COM utilities available, has always been difficult. Clarion is also abysmally bad at text processing and several other classes of problem that I seem to get a lot, which has always forced me to write utility programs in better suited languages.
That's all changing now though. It's a not very well-kept secret that Clarion is moving towards the .Net world; with the upcoming .NET-based IDE, you'll use Clarion 7 to create Win32 apps, and Clarion.NET to create .NET apps. Clarion.NET will breathe a whole new life into the Clarion language. The .NET framework is extremely large, growing and very well supported. Right out of the box the framework has hundreds of classes that are well thought out and which integrate seamlessly with .NET applications. These classes cover a very wide range of the things you might want to do, such as directory services, graphics, internet connectivity, security, XML, message services, web services, really good text processing and much, much more. You can find tons of examples all over the place. Most of the .NET languages aren't structurally a lot better than Clarion, but the framework is extremely nice.
If you're eager to start, you don't have to wait for Clarion to get their next version out. With a bit of interop code, you can get many of the benefits of the .NET world right here and now in Clarion 6. You'll find that Clarion and .NET make a powerful combination.
This series will cover in detail how to interoperate between Clarion and .NET. I'll cover calling .NET from Clarion, calling Clarion from .NET, and embedding WinForms windows and controls into Clarion applications. Most of it isn't all that hard to do once you know the secrets.
I'll be demonstrating how to do all of these things, and I'll provide a couple of utilities to do some of the grunt work. I'll use Visual Studio 2003 and Clarion 6 for my discussion, but those are not limiting factors. You can use any version of Visual Studio .NET, and any version of Clarion after Clarion 4. All of my examples will be in C#, but again you aren't limited to C#. Any .NET language will probably work with minor syntax changes. Our company uses mostly C#, but I'm constantly tempted towards VB.NET with its more Clarion-like syntax.
There are currently two versions of .NET and Visual Studio (VS) available. Visual Studio 2003 along with the .NET framework 1.1 is the current active shipping release. It's very stable and has been around for some time. Visual Studio 2005, with the .NET 2.0 framework is currently in Beta II phase and will ship on November 9. If you can possibly wait until after November to ship real code to customers I strongly recommend Visual Studio 2005. There are a lot of new and cool things in 2005 and the C# language which would make it a compelling upgrade, particularly since you'll probably do it in six months anyway. The real clincher though is that the debugger in VS 2005. You can single-step and use all the features of a real debugger when calling .NET from Clarion. I've never been able to get it to work with 2003. It may be a solvable problem, but I don't feel compelled to waste any time on it. If you have to go with 2003, fear not. Everything I'm going to show you will work just fine in 2003, but your debugging experience will be suboptimal.
Calling Clarion from a .NET language is fairly simple. You use the .NET interop
classes in a manner similar to how you would make native API calls. The only
real complexities are in bypassing Clarion's unusual parameter passing mechanism,
which I'll cover in Part 3, and in passing GROUPs, which I'll cover
in Part 4.
To call any .NET procedure from Clarion, you have a few choices. You can expose any .NET class to COM, which makes it visible to any COM client. Once you've done that, you can either use Clarion's OLE control or write some Clarion wrapper classes. The latter is better, but harder to do.
You can also make a Managed C++ wrapper, which is stored inside your .NET assembly. You then call this wrapper the same way you would call an API function. This is less flexible and a tiny bit harder to do, but it works much faster than the COM option so you may want to consider it if you're calling something a lot of times.
Along the way, I'll describe two of the ways you can embed WinForms windows inside of Clarion applications, as well as embedding WinForms controls into Clarion windows.
The simplest way to call a method in .NET is to make a COM wrapper around the
.NET class, and then use Clarion's COM capabilities to call that method. First,
I'll demonstrate using Clarion's OLE
control, which is adequate for simple tasks, and much better than you might
expect if you used it prior to Clarion 6.
Start out by making a really simple .NET project.
In Visual Studio, Use File | New Project.
Pick Class Library from the Visual C# tab, and pick a good folder to place it in.
I also recommend you click the More button and turn the Create directory for solution checkbox off. If you don't, you end up with an annoying double directory structure.
Click OK, and you'll have a new solution with the file Class1.cs
opened. You should immediately rename it, and change the name of the class and
its constructor. I renamed it to Interop01.cs,
with a namespace of Interop01 and a classname
of Interop01Class. (In VS 2005, if the
first thing you do is rename the file, the class and its constructor are renamed
automatically).
Whenever you start a new C# project, you'll want to immediately add a few #using
directives to the top of the file, and if you want to do anything involving
Windows you'll need a few references. For this demo:
In the Solution Explorer, right-click on References and select
Add. From the list, add a reference to System.Windows.Forms.dll.
This has a similar effect to adding a LIB to a Clarion project, and adding the
associated INC file at the same time - you're including the Systems.Windows.Forms
classes in your project. You'll probably want this in every project you ever
do until the end of time, even if it's a console project, because it includes
the ubiquitous MessageBox class. It never hurts
to add it, because .NET will only pull in the assemblies you actually reference.
At the top of the file, add a few using directives.
These aren't strictly necessary, but they'll save you a lot of typing. Without
the using directive, you'd need to type the fully
qualified path name for every method or property you ever access (e.g. System.Windows.Forms.MessageBox.Show).
With it you need only type MessageBox.Show.
Only the first three are absolutely necessary for this example. The remaining
two are something you'll need about ten minutes after you start writing something
real. Over time, you'll probably come up with your own list of using
directives.
using System; using System.Windows.Forms; using System.Runtime.InteropServices; using System.Collections; using System.IO;
Next, make a method to call for demonstrations. Start with something simple
that doesn't do much. For testing purposes, just make a method that shows something
with MessageBox.Show. My test method looks something
like:
public void CallDotNet(string message)
{
MessageBox.Show(String.Format("Message Box in .NET showing {0} from Clarion.", message));
}
If you build the project, you'll have a very simple .NET assembly that you could use with any .NET language. I suggest building it now just to be sure you have everything.
Note that C# doesn't have the concept of raw procedures like Clarion does. Everything has to be a method belonging to a class. This makes some sense, as it helps to eliminate procedure name conflicts. Make a good unique classname, and the chances of duplication are very low. To make the analog of a Clarion procedure that doesn't require an instantiation of an object, you create a static method. This produces something just like a Clarion procedure, except you need the classname to reference it.
Now that you have a valid .NET assembly, you need to make it a COM component. It's really easy to do, but Microsoft is trying really hard to kill COM, so they don't give you plain instructions. There are a few steps you'll need to take, and later on a few deployment choices you'll have to make.
The first decision involves where to put your DLLs. Unlike earlier COM components, you can't just put the DLLs anywhere you like. The .NET designers deliberately removed that capability to help eliminate versioning conflicts and the infamous DLL Hell. Your DLLs have to be either in the same folder as the EXE file that's using them, or they have to be in the Global Assembly Cache (GAC). The GAC is a good place for things that you'll use all over the place such as global tools, but I generally put the DLL in the same folder as the Clarion executable. For this example, I'll put the DLLs in the same folder as the Clarion project, but while I'm at it I'll do a couple of things that may be necessary if I decide to move it to the GAC later.
There are two ways to put the DLL in a different location than its default. You can either have .NET make them in your output folder in the first place, or you can copy them after they're built. I generally prefer to just put them in the output folder in the first place. The only time this doesn't work well is if you have a .NET solution with multiple project files that reference each other. VS 2003 has problems with this, so you have to use the copy method.
For this example, I'll be making the Clarion components in the same folder as the Solution file.
In VS, execute Project | Properties to change the properties for this project.
Under the Build entry, change Output Path to .\.
That will place it in the same folder as the solution. In practice, you would
probably pick a different folder where your Clarion project generates its output.
Note that the IDE will always change a hard-coded path to a relative path.
Just below the Output Path, set Register for COM Interop to True. This will register your assembly for COM when you do a build.
This will generate the files directly in the same folder as your solution. If you want to copy the files instead:
Under the Common Properties folder, find Build Events and change
it to copy /y $(OutDir)* $(ProjectPath).
This does the same thing using a copy. Naturally, you can change the destination
path, or the file pattern to copy as appropriate.
Next, change some of the project attributes:
In the Solution Explorer, open the AssemblyInfo.cs
file. This file contains information that controls how the .NET assembly is
built.
Locate this line:
[assembly: AssemblyVersion("1.0.*")]
and change it to
[assembly: AssemblyVersion("1.0.0.0")]
At the bottom of the file, locate:
[assembly: AssemblyKeyFile("")]
and change it to
[assembly: AssemblyKeyFile(@"..\..\Interop01.snk")]
If you don't change the AssemblyVersion entry,
.NET will make a new side-by-side version and register that new version in your
registry every time you do a build. Eventually you'll have hundreds of them.
Changing it as shown will make just a single version that's updated with each
build It's up to you to change the number when you make a version change that
matters. You should change the version number any time you make a breaking public
interface change after you've deployed your assembly.
The AssemblyKeyFile entry makes what's called
a Strong Key for the assembly. This is something used to for digitally
signing the assembly. You don't actually need it unless you deploy to the GAC,
but it's always a good idea so I just do it for all projects. Note the @"
in the string. This is a C# convention that means the string is interpreted
like a Visual Basic string. This makes it so you don't have to double up the
backslashes. All characters go in the string exactly as entered except double-quotes.
To get a double quote, you put in two of them.
The next step is to indicate which classes should be exposed to the COM interface. You do that by decorating your declaration with attributes. Attributes are special instructions that can attach metadata to any program entity. That metadata can be used for any number of things, including instructing the compiler or linker to generate a COM wrapper for something. In this case, we want to tell .NET to generate a COM wrapper for a particular class:
In your source code, Right above the declaration for any class that you want to expose to COM, add this line:
[ClassInterface(ClassInterfaceType.AutoDual)]
Once you do that, all public methods, fields or properties for the class are exposed to COM. You can add similar attributes to demote certain of these items so they're public for .NET but private to COM. There's quite a bit of flexibility in what gets exposed to COM and what isn't.
Once you've made these changes, the complete source file looks like:
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Collections;
using System.IO;
namespace Interop01
{
[ClassInterface(ClassInterfaceType.AutoDual)]
public class Interop01Class
{
public void CallDotNet(string message)
{
MessageBox.Show("Message Box in .NET showing " + message + " from Clarion.");
}
}
}
If you try to build the solution after this change the build will fail because
it couldn't find the strong encryption file specified in AssemblyInfo.cs.
You'll need to make that using some command line tools.
If you have the full retail version of Visual Studio, open a command prompt,
using the "Visual Studio xxx Command Prompt" shortcut, which you'll find
in the Start Menu under "Visual Studio Tools". This command executes a batch
file that updates your path to find the .NET command line tools, and sets some
environment variables for the command line compiler. If you're using the free
Visual Studio Express version, download sn.exe
here,
and place it somewhere in your path.
Navigate to your solution directory, and execute this command, to generate the missing Strong Name file:
sn -k Interop01.snk
If all went well, you should be able to build the project.
You'll notice that I made the .snk file the
same as my assembly name. Normally, I just make a single CompanyName.snk
file, put it in a hard-coded location, and always use the same file for all
assemblies. I run sn.exe one time and
I'm done with it.
In my development environment, I'm depending on Visual Studio to register the
assembly for COM. When you distribute your app, you'll have to give the users
a tool to register the assembly. The distributable will use regase.exe
/i dllname to register the assembly. This is the .NET version of RegSvr32
that you're probably used to. You'll find it in your Visual Studio folder, or
you can get it here
if you're using the Express version.
You should also note that placing the code in the target folder works well for Clarion, but may not work as well for other environments. For example, VB6 would require the DLL to be in one place when you're running in the IDE (the same folder as VB6.EXE) and a different place for a compiled EXE (the same folder as the EXE). If you're trying to use this component from an environment like that, the best thing to do is just put it in the GAC. To do that, you would add these lines to the Post-Build step if you're using a retail version of VS.
Call "$(DevEnvDir)..\Tools\VSVars.bat" gacutil /I $(TargetFilename)
If you're using the Express version, get gacutil.exe here,
place it somewhere in your path, and omit the first line shown above.
This doesn't necessarily mean that you have to put it in the GAC for your users. In this case, the GAC is only necessary for running in the VB6 IDE.
You now have a public COM component that can be consumed by any COM container
you like including Clarion. I don't recommend using this with anything prior
to Clarion 5.. The COM-visible name for the control is the namespace,
followed by a dot and the classname. For this example, it's Interop01.Interop01Class.
For an example, I'll use a really simple Clarion window. You'll find the simplest
of all Clarion projects in the sample code, but you can test it with any Clarion
program that has a window. The following code can be added to any window, and
it will create an OLE control and call
the sample method:
WorkOpenWin routine
data
Interop01 long
code
Interop01 = CREATE(0, CREATE:OLE)
Interop01{PROP:Create} = 'Interop01.Interop01Class'
Interop01{'CallDotnet("Message from Clarion")'}
Compile and run the Clarion program, and you should see a message box with the descriptive text. You'll notice that I created a new OLE control instead of adding one to the window. You can do either, but in practice creating a new control seems to be more stable with certain COM components.
Returning something isn't much harder than that what I've just shown. Change
the CallDotNet method to look like this:
public string CallDotNet(string message)
{
MessageBox.Show(String.Format("Message Box in .NET showing {0} from Clarion.", message));
return "This text returned from .NET";
}
Change the Clarion code to look like:
Interop01 = CREATE(0, CREATE:OLE)
Interop01{PROP:Create} = 'Interop01.Interop01Class'
Message(Interop01{'CallDotnet("Message from Clarion")'})
Compile and run this, and you'll see one message box displayed by .NET and a second one displayed by Clarion.
I hope this will give you a bit to whet your appetite. For a lot of the projects I've done over the years, this capability would have made huge differences. I wish I'd had .NET a lot sooner, because the .NET framework supplies a lot of extended functionality that just doesn't come with Clarion. In the next segment, I'll show how to add WinForms controls to Clarion windows, or WinForms forms to Clarion apps.
Wade Hatler has been around Clarion so long he got the very first patch release for Clarion 1.0 for DOS. That was back when Clarion had a dongle, no compiler and no templates. Wade co-owned IntelliScan, a company producing third-party Clarion products for several years. For the last 10 years he has worked for IMPAC Medical Systems creating software for the management of Cancer Therapy. He recently completed a 3-year, 12,000 mile, 12 country bicycle tour of the world, during which he carried a laptop and worked about half-time. He lives with his wife and daughter near Seattle. You can reach Wade at
|
Posted on Friday, September 02, 2005 by Stephen Mull Great article Wade, looking forward to the rest! Thanks --SM Posted on Thursday, September 15, 2005 by Bernard Grosperrin Waow! Just learning C# now, it's great to see that it's possible to do something like that from Clarion. Keep them coming! That will motivate me a lot more to dig in .Net and C# than I already was.
Posted on Thursday, September 22, 2005 by David W. Yes, nice article. Wets the appetite a little for .NET. So I tried this same technique in VB.NET but was unable to get Clarion to connect. It reported "No ole automation interface". Do I have to something else different with VB? Thanks. Posted on Tuesday, March 28, 2006 by Trond Kaldhussýter Hello.
Posted on Wednesday, March 29, 2006 by Dave Harms Trond,
Posted on Wednesday, April 12, 2006 by Carl Barnes VS2005 might have made this easier. May 2006 MSDN article explains how to call .Net from VB 6 by putting your .Net code in a COM wrapper.
Posted on Wednesday, May 23, 2007 by Eric Lyng The message box said "No ole automation interface". I followed the article well, and I am using VB 2005 and Clarion 6. Was a step left out of the article Posted on Wednesday, May 23, 2007 by Eric Lyng Aha....
|
To add a comment to this article you must log in.
Copyright © 1999-2008 by CoveComm Inc. All Rights Reserved. Reproduction in any form without the express written consent of CoveComm Inc., except as described in the subscription agreement, is prohibited.
Clarion Magazine ISSN 1718-9942
One year: $189
(includes all back issues since '99)
Renewals from $139
Two years: $289
Renewals from $239