Introduction
In this article (modified from my last posted article!) I will give you an example on how to access a web service from a C project using a C# Library. Moreover, the web service's URL can also be dynamically read from an initialization file (.ini) in the C project.
So this is a typical .NET Interop scenario. The C project would communicate with the C# library which in turn would access the web service and retrieve the necessary data into the C# library and in return the C# library should parse the returned XML and then serialize the returned data into corresponding structures so that the C project could consume it.
Solution
To begin with, this is the step by step procedure to get the task done:
- Create a new C# library Solution Explorer->Add->Add New Project->Visual C#->Class Library. Give the project a name (let's call it Library).
- Add reference to the web service, Solution Explorer->C# Class Library�s add Reference->Add Web Reference and then key in the URL of the exposed WebService (example :http://localhost:8080/axis/service1/GmsWs?wsdl) and click on Go. Once the WSDL shows up, give a descriptive name for the WebService and click on Add Reference. (Let's call the reference WebService.)
- Now, the WebService is added and a wrapper class is created to interact with the service. Right click the web reference of the service and change the URL behavior to dynamic from static, as our program would read the URL of the service dynamically from the initialization file and the constructor also has to be modified to take a string (URL of a WebService).
- Click on the Show all files icon on the Solution Explorer tab and navigate to the Reference.cs file found under the reference section of the service reference, replace the constructor of the WebService with a custom constructor designed to read the URL dynamically and create wrappers.
[System.Web.Services.WebServiceBindingAttribute(Name="WsSoapBinding",
Namespace="urn:GmsWs")]
public class WsAPIService : System.Web.Services.Protocols.
SoapHttpClientProtocol {
public WsAPIService(string szURL) {
this.Url = szURL;
}
- Next, sketch the C# library to make calls to the WebService, create instances of the WebService before accessing the methods:
Library.WebService.WsAPIService myService =
new Library.WebService.WsAPIService(URL);
Library
is the name of our C# library and WebService
is the name given to the Web Service that we are referencing. WsAPIService
is the name given to the WebService at the server end. This can be found in the wsdl:service name =" " tag of the WSDL file generated by the WebService. Also, the URL of the WebService has to be passed to the constructor while instantiating the WebService as we have defined it to be dynamic.
- Remember to write interfaces for all the functions that need to be accessed from the C DLL as it would be referencing the C# library�s methods only through the interfaces created:
public interface IMyInterface
{
int LfindName2(string name, string URL);
string LgetStandardName(string name,string URL);
}
- Moreover, while doing a .NET Interop we have to supply GUID's for the interfaces and classes created as COM relies on the registry, we have to give a unique GUID for the interface and the classes exposed. This is the equivalent of CLSID's generated while registering a component. Tools->Create GUID->Registry Format, this generates unique GUID's that could be incorporated into the code, now click on Copy and then paste it above the class declaration for interfaces and other classes:
Guid("876B4EAD-C5C2-4bd2-86C5-E132B1320006")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IMyInterface
{..;
.. }
- Also remember to create data structures that are COM compatible, (refer to Microsoft's Developers Network Library).
- Write the functions inside the class which will be used to access the WebService, remember to add GUIDs for the classes:
[Guid("59B6BA59-A296-4b38-AD6E-D0896C5D3FE8")]
[ClassInterface(ClassInterfaceType.None)]
public class LibraryImplementation : IMyInterface
{
public int LfindName2(string name, string URL)
{
try
{
}
catch
{ ...
}
}
}
- Once the C# library is sketched and compiled successfully, register it in the GAC together with converting it into a type library to be imported by the C DLL.
regasm Library.dll /tlb Library.tlb
(Library is the name of our C# library; this command can be put in the post-build option of Visual Studio IDE.)
- Remember the strong naming of assemblies, yes you have to do it if you need to do an .NET Interop, so do a:
sn -k Library.snk
which will generate a key that you have to add to your project.
- Go to the assembly.cs file and fill in the title, description, company, products etc., most important of all, give a specific assembly version number instead of the default 1.1.*.* otherwise every time you compile your DLL after making small changes, the revision number would be incremented and registered in your registry as a new library. Hence giving a specific number like 1.1.1.1 would stop populating your registry unnecessarily. Make these changes also in the assembly.cs file, add the generated strong name key in the
AssemblyKeyFile
. [assembly: AssemblyDelaySign(false)]
[assembly: AssemblyKeyFile("Library.snk")]
[assembly: AssemblyKeyName("")]
- Import the generated Library.tlb file into the C project�s header file and create a reference to the managed interfaces declared in the C# library.
#pragma warning (disable: 4278)
#include <string.h>
#import <mscorlib.tlb> raw_interfaces_only
#if defined (USINGPROJECTSYSTEM)
#import "..\Library\Library.tlb" no_namespace named_guids
#else
#import "Library.tlb" no_namespace named_guids
#endif
using namespace std;
- The C code can access the C# library only through the managed interface:
IMyInterface *test = NULL;
CoInitialize(NULL);
HRESULT hr =
CoCreateInstance(CLSID_LibraryImplementation,NULL,CLSCTX_INPROC_SERVER,
IID_IManagedInterface,reinterpret_cast
(&test));
if (FAILED(hr))
{
MessageBox(NULL,"Couldn't create the instance!","C# Library",MB_OK);
}
- Access the corresponding C# library functions through the created managed pointer:
string str;
str = test->LfindName2(szSearchName,szURL);
- After using the managed interface in functions, we have to clean up COM so as to free memory:
CoUninitialize();
test->Release();
- Well, before that let me tell you how to read the URL of the WebService from the .INI file from C:
void CheckWebService(LPCSTR szIniFile)
{
CHAR wsURL[128];
GetPrivateProfileString("Central GMS","URL","",
wsURL,sizeof(wsURL),szIniFile);
if(strcmp(wsURL,"") != 0)
{
szURL = (_bstr_t) wsURL;
}
else
{
szURL =
(_bstr_t) "http://localhost:8080/axis/services/GmsWs?wsdl";
}
}
- Finally, handle errors and exception thrown or received and the communication between the two libraries.
- Converting the C# library to a type library could also be achieved through the command:
tlbexp Library.dll /out:Library.tlb
Installing the resultant DLL in the clients PCs
The final lap is in installing the resultant DLL to the target PCs. This can be achieved by copying both the resultant C DLL and the C# library (Library.dll) to the application directory of the target PC and then executing:
regasm Library.dll /tlb:Library.tlb
gacutil /I Library.dll
Software required on target PC
The target PC should have the .NET framework re-distributable pack installed. You can download it from the Microsoft website.
Summary
This article gives a way of accessing a WebService from C by creating a C# library to interface between the WebService and the C project. For further clarifications, please email me.
Namespaces used
using System;
using System.Runtime.InteropServices;
using System.IO;
using System.Xml;
using System.Collections;
using System.ComponentModel;
using System.Text;
using System.Data;