Delphi Clinic | C++Builder Gate | Training & Consultancy | Delphi Notes Weblog | Dr.Bob's Webshop |
![]() |
![]() |
![]() |
|
C++Builder 6 meets C# ASP.NET Web Services
In this article, I use the .NET Framework SDK and C# together with C++Builder and examine how C# and C++Builder can be "connected" to each other using ASP.NET web services.
Some months ago (in the June 2003 issue of C++Builder Developer's Journal), I connected a C# assembly to C++Builder using a special generated type library in between.
However, this wasn't really an elegant solution, and I showed that C# exceptions (thrown in the C# assembly) were not correctly received by the C++Builder application (at least not without missing the information that was placed into it at the .NET side).
This time, instead of using a C# assembly, I take a C# web service - implemented using ASP.NET - and show that this is a much easier way to build a communication between the .NET and the Win32 worlds.
Celsius to Fahrenheit
In order to give you the best comparison between the two techniques, I decided to use a simple C# example: a Celsius (centigrade) to Fahrenheit temperature conversion engine.
The syntax of the actual conversion routines is 100% similar, it's only the "wrapper" (assembly versus web service) that's different - so I actually recommend that you compare the source code from this article with that of the previous one for a complete picture.
A C# web service implemented using ASP.NET consists of an .asmx file that starts with a line that declares the WebService, specifies the Language (C# in this case) as well as the name of the Class (Celsius42).
After this first line, the "plain" C# source code follows, including the two namespaces that we need: System (for the ApplicationException) and System.Web.Services (for the base class System.Web.Services.WebService).
A web service is a public class derived from WebService, as can be seen in the listing.
Using attributes (between square brackets) I can specify additional information.
I need this for example to define the namespace of the web service, otherwise it will use the default namespace http://www.tempuri.org which is not recommended (in case your web service is combined with another web service, and both export the same name for a type or method.
A unique namespace prevents collisions).
Also, each method that is to be "exported" as web service method needs to have the attribute [WebMethods] defined.
All in all, it's pretty straightforward to define a web service in C# - compare it to building web services in C++Builder (in the July 2002 issue) and you see some significant differences.
In favor of C# I have to say in all honesty.
<%@ WebService Language="C#" Class="Celsius42" %> using System; using System.Web.Services; [WebService(Namespace="http://www.eBob42.com", Description="Dr.Bob's Celsius42 Web Service written in C# using ASP.NET")] public class Celsius42: System.Web.Services.WebService { [WebMethod] public string About() { return "Dr.Bob's Celsius42 Web Service written in C# using ASP.NET"; } const double AbsoluteZeroCelsius = -273.15; [WebMethod] public double Celsius2Fahrenheit(double Degrees) { if (Degrees < AbsoluteZeroCelsius) throw new ApplicationException("Invalid Temperature"); return 32 + (9 * Degrees / 5); } const double AbsoluteZeroFahrenheit = -459.67; [WebMethod] public double Fahrenheit2Celsius(double Degrees) { if (Degrees < AbsoluteZeroFahrenheit) throw new ApplicationException("Invalid Temperature"); return (5 * (Degrees - 32) / 9); } }If I compare the Celsius web service with a Celsius assembly, then a big difference between a web service (using ASP.NET) and assembly is the fact that I don't have to explicitly compile the former. Not manually anyway, since it will be done automatically by the ASP.NET framework. I only need a web server with the .NET Framework and ASP.NET support, and a scripting directory where I can upload the .asmx file to. As soon as a request is made to the .asmx file, then the ASP.NET environment will compile the C# source code into an assembly that will be used from that moment on. Until ASP.NET detects a change in the .asmx file (for example when I make a little modification to it), which causes a recompile. This gives me the efficiency of an ISAPI dll (load once, remain loaded until changed) and yet the flexibility of a CGI executable, since I can replace or update the .asmx source code without problems while replacing an ISAPI dll can be difficult to say the least.
Importing ASP.NET C# Web Services
But enough about the C# side, let's focus on C++Builder again and import the web service.
Anyway, in the August 2002 column, I used the WSDL Importer wizard which can be found in the WebServices tab of the Object Repository of C++Builder.
However, apart from using this wizard, there is also a command-line tool called Borland's WSDLIMP which can be found in the CBuilder\Bin directory.
This command-line tool can be used to import a WSDL definition and create a C++ header and source file (as well as a Delphi import unit, by the way, which is not that unusual if you remember that C++Builder can compile Delphi source code just as well as C++ source code).
In fact, if you don't specify the source code option with either -C or -P it generates both a C++ header file and source file and a Delphi import unit.
The following command will do this for you:
wsdlimp http://www.eBob42.com/cgi-bin/Celsius42.asmx?wsdlThose of you who still want to use a visual wizard can generate the exact same input units by using the WSDL Importer wizard from the C++Builder Object Repository. The header file Celsius42.h contains the Celsius42Soap interface definition which is as follows:
__interface INTERFACE_UUID("{0FC7E511-...-7DC3B2DA83BD}") Celsius42Soap: public IInvokable { public: virtual AnsiString About() = 0; virtual double Celsius2Fahrenheit(const double Degrees) = 0; virtual double Fahrenheit2Celsius(const double Degrees) = 0; }; typedef DelphiInterfaceTake a good look at the last line: in practice, we have to use this _di_Celsius42Soap (Delphi Interface), which is created and returned by the GetCelsius42Soap function that can be found in the generated Celsius42.cpp source file:_di_Celsius42Soap;
_di_Celsius42Soap GetCelsius42Soap(bool useWSDL, AnsiString addr) { static const char* defWSDL= "http://www.eBob42.com/cgi-bin/Celsius42.asmx?wsdl"; static const char* defURL = "http://www.eBob42.com/cgi-bin/Celsius42.asmx"; static const char* defSvc = "Celsius42"; static const char* defPrt = "Celsius42Soap"; if (addr=="") addr = useWSDL ? defWSDL : defURL; THTTPRIO* rio = new THTTPRIO(0); if (useWSDL) { rio->WSDLLocation = addr; rio->Service = defSvc; rio->Port = defPrt; } else { rio->URL = addr; } _di_Celsius42Soap service; rio->QueryInterface(service); if (!service) delete rio; return service; }GetCelsius42Soap has two default arguments: useWSDL set to False and an empty addr string. You need to pass a value for these arguments only if you wish to override the addr (the location of the SOAP action or the WSDL) or if you want to specify that the WSDL has to be used to determine the location of the SOAP action itself. Usually, just calling GetCelsius42Soap without arguments works good enough.
Using ASP.NET C# Web Service
Now that the ASP.NET C# web service has been imported into Celsius42.h/.cpp files, it's time to create a new project and use them.
Create a new project, save the main form in MainForm.cpp and the project itself in TempClient.bpr.
Add the generated import unit Celsius42.cpp to the project, and use Alt+F11 to include it in the source of the MainForm as well.
On the mainform, drop two TEdits (call them edtCelsius and edtFahrenheit), and two TButtons (call them btnC2F and btnF2C).
The implementation of the two buttons is simple again: take the contents of one editbox, convert it from string to float, then call the Celsius2Fahrenheit (or Fahrenheit2Celsius) method of the Celsius42 web service and place the result back in the other editbox.
A final step consists of obtaining the interface to the _di_Celsius42Soap web service by calling the GetCelsius42Soap() function.
In C++ code, all this combined results in the following:
void __fastcall TForm1::btnC2FClick(TObject *Sender) { _di_Celsius42Soap Celsius = GetCelsius42Soap(); edtFahrenheit->Text = FloatToStr( Celsius->Celsius2Fahrenheit(StrToFloatDef(edtCelsius->Text,0))); } void __fastcall TForm1::btnF2CClick(TObject *Sender) { _di_Celsius42Soap Celsius = GetCelsius42Soap(); edtCelsius->Text = FloatToStr( Celsius->Fahrenheit2Celsius(StrToFloatDef(edtFahrenheit->Text,0))); }And this can be seen in action (converting 20 degrees centigrade to 68 degrees Fahrenheit) below:
C# web service usage
Note that each button click makes a call to the Celsius42 web service, which in turn calls the GetCelsius42Soap() function to retrieve the _di_Celsius42Soap interface.
This is not the most efficient way to work with web services if you plan to make a lot of method calls.
In those situations, you better call GetCelsius42Soap() once, save the resulting web service interface in a variable and work with from that point on.
Remember that the .NET Framework is all about safe, managed code.
But the C++Builder client application is a pure Win32 application without anything related to .NET.
The fact that we're using a web service that was implemented using C# and ASP.NET is not relevant anymore to our client application (we could have written it in Kylix on Linux for that matter), but it does demonstrate another way to connect .NET code (web services) to C++Builder projects.
Exceptions and Errors?
So far, so good.
But what happens if something goes wrong.
Specifically, what would happen if an exception was thrown by the ASP.NET web service?
Ín another article, I've noticed that .NET assembly exceptions are not completely received by the C++Builder client application (the exception information itself was not reachable - although we did get the message that something was wrong).
In this case, the C# web service again contains code to throw an exception if we try to convert a temperature which is below the absolute zero.
So let's find out how web services pass this information on to their clients.
In order to catch exceptions, I have modified the C++ OnClick event handlers as follows:
void __fastcall TForm1::btnC2FClick(TObject *Sender) { try { _di_Celsius42Soap Celsius = GetCelsius42Soap(); edtFahrenheit->Text = FloatToStr( Celsius->Celsius2Fahrenheit(StrToFloatDef(edtCelsius->Text,0))); } catch (Exception& E) { ShowMessage("Error (" + E.ClassName() + "): " + E.Message); } catch (...) { ShowMessage("Oops: ASP.NET Web Service Error!"); } } void __fastcall TForm1::btnF2CClick(TObject *Sender) { try { _di_Celsius42Soap Celsius = GetCelsius42Soap(); edtCelsius->Text = FloatToStr( Celsius->Fahrenheit2Celsius(StrToFloatDef(edtFahrenheit->Text,0))); } catch (Exception& E) { ShowMessage("Error (" + E.ClassName() + "): " + E.Message); } catch (...) { ShowMessage("Oops: ASP.NET Web Service Error!"); } }We can test the C++Builder client again by entering -400 in the Celsius editbox and clicking on the Celsius-to-Fahrenheit button. The result is an exception error message (the classname is ERemotableException) that contains the information that was given by the C# web service (Invalid Temperature) as well as the additional information from the ASP.NET framework (that the error occurred on line 20 of the Celsius42.asmx file), which can be seen below:
C# ASP.NET SoapException
Although it's better than the error message you get when importing .NET assemblies as COM (in which case the actual error message passed by the C# code is gone), I'm not so sure if your endusers will want to see the error dialog, so you still better work on your custom error messages.
But at least you get more detailed information than before.
Conclusion
In this article, I've shown the source code of a C# web services implemented using ASP.NET, and explained how and where I've deployed it on the internet.
I then used the WSDLIMP utility from Borland to import this web service into the Celsius42.h/.cpp files and created a new C++Builder application using these import files.
The resulting application has communicate with a .NET web service, but has no idea it actually communicates with .NET (it could also talk to a Kylix web services on Linux for example).
Finally, error messages from SOAP exceptions are returned as ERemotableExceptions and can be handled by the C++Builder client application.
Afterword
In practice, a .NET developer would want to reuse his code in different ways.
A C# assembly is more flexible and easier to reuse than a web service (especially if you need to deploy on a stand-alone desktop with no access to the internet).
A combined solution can consist of a web service wrapper around the C# assembly, where you can use the assemble to implement the engine functionality, and the web service only as interface for non .NET clients (such as C++Builder).
This is a topic that will be covered at some other time in more detail, so stay tuned!