Introduction
The topics covered in this article are:
- Creating a simple COM object in C# (use the COM Interop property).
- Accessing the COM from a VC++ client. (I have tested it with VC++6.0 and VC++ .NET). Client uses the TypeLibrary (.TLB file).
For the sake of simplicity and ease of use for the developers, testing this code, I have used the Northwind database built into with the default installation of SQL Server database.
- Modify the name of the SQL Server in the COM object to connect to your SQL Server.
- Also, I have created a default user ID and password of scott / tiger to connect to the database. Either create this, or use an existing ID / password.
Part I: Creating a simple COM object in C#
COM objects are of the type ClassLibrary. The COM object generates a DLL file. To create a simple COM object from the VS Development Environment, select ....
New->Project->Visual C# Projects ->Class Library
Create a project named Database_COMObject.
Remember: Exposing the VC# objects to the COM world requires the following ...
- The class must be
public
.
- Properties, methods, and events must be
public
.
- Properties and methods must be declared on the class interface.
- Events must be declared in the event interface.
Other public members in the class that are not declared in these interfaces will not be visible to COM, but they will be visible to other .NET Framework objects. To expose properties and methods to COM, you must declare them on the class interface and mark them with a DispId
attribute, and implement them in the class. The order the members are declared in the interface is the order used for the COM vtable. To expose events from your class, you must declare them on the events interface and mark them with a DispId
attribute. The class should not implement this interface. The class implements the class interface (it can implement more than one interface, but the first implementation will be the default class interface). Implement the methods and properties exposed to COM here. They must be marked public
and must match the declarations in the class interface. Also, declare the events raised by the class here. They must be marked public
and must match the declarations in the events interface.
Every interface needs a GUID property set before the interface name. To generate the unique GUID, use the guidgen.exe utility and select the Registry Format.
Here is how the interface class looks like ...
[Guid("694C1820-04B6-4988-928F-FD858B95C880")]
public interface DBCOM_Interface
{
[DispId(1)]
void Init(string userid , string password);
[DispId(2)]
bool ExecuteSelectCommand(string selCommand);
[DispId(3)]
bool NextRow();
[DispId(4)]
void ExecuteNonSelectCommand(string insCommand);
[DispId(5)]
string GetColumnData(int pos);
}
For COM events ..
[Guid("47C976E0-C208-4740-AC42-41212D3C34F0"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface DBCOM_Events
{
}
For the actual class declaration:
[Guid("9E5E5FB2-219D-4ee7-AB27-E4DBED8E123E"),
ClassInterface(ClassInterfaceType.None),
ComSourceInterfaces(typeof(DBCOM_Events))]
public class DBCOM_Class : DBCOM_Interface
{
Note the following property set before the class:
ClassInterface(ClassInterfaceType.None),
ComSourceInterfaces(typeof(DBCOM_Events))]
The ClassInterfaceType.None
indicates that no class interface is generated for the class. If no interfaces are implemented explicitly, the class will only provide late bound access through IDispatch
. Users are expected to expose functionality through interfaces that are explicitly implemented by the class. This is the recommended setting for ClassInterfaceAttribute
.
The ComSourceInterfaces(typeof(DBCOM_Events))]
identifies a list of interfaces that are exposed as COM event sources for the attributed class. For our example, we do not have any events exposed.
Here is the complete COM object source ..
using System;
using System.Runtime.InteropServices;
using System.IO;
using System.Text;
using System.Data.SqlClient;
using System.Windows.Forms ;
namespace Database_COMObject
{
[Guid("694C1820-04B6-4988-928F-FD858B95C880")]
public interface DBCOM_Interface
{
[DispId(1)]
void Init(string userid , string password);
[DispId(2)]
bool ExecuteSelectCommand(string selCommand);
[DispId(3)]
bool NextRow();
[DispId(4)]
void ExecuteNonSelectCommand(string insCommand);
[DispId(5)]
string GetColumnData(int pos);
}
[Guid("47C976E0-C208-4740-AC42-41212D3C34F0"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface DBCOM_Events
{
}
[Guid("9E5E5FB2-219D-4ee7-AB27-E4DBED8E123E"),
ClassInterface(ClassInterfaceType.None),
ComSourceInterfaces(typeof(DBCOM_Events))]
public class DBCOM_Class : DBCOM_Interface
{
private SqlConnection myConnection = null ;
SqlDataReader myReader = null ;
public DBCOM_Class()
{
}
public void Init(string userid , string password)
{
try
{
string myConnectString = "user id="+userid+";password="+password+
";Database=NorthWind;Server=SKYWALKER;Connect Timeout=30";
myConnection = new SqlConnection(myConnectString);
myConnection.Open();
}
catch(Exception e)
{
MessageBox.Show(e.Message);
}
}
public bool ExecuteSelectCommand(string selCommand)
{
if ( myReader != null )
myReader.Close() ;
SqlCommand myCommand = new SqlCommand(selCommand);
myCommand.Connection = myConnection;
myCommand.ExecuteNonQuery();
myReader = myCommand.ExecuteReader();
return true ;
}
public bool NextRow()
{
if ( ! myReader.Read() )
{
myReader.Close();
return false ;
}
return true ;
}
public string GetColumnData(int pos)
{
Object obj = myReader.GetValue(pos);
if ( obj == null ) return "" ;
return obj.ToString() ;
}
public void ExecuteNonSelectCommand(string insCommand)
{
SqlCommand myCommand = new SqlCommand(insCommand , myConnection);
int retRows = myCommand.ExecuteNonQuery();
}
}
}
Before you build the COM object, we have to register the object for COM Interop. To do this, right click the project name in the Solution Explorer. Click Properties. Click Configuration ->Build. Expand the output section. Set the Register for COM Interop to true.
Indicate that your managed application will expose a COM object (a COM-callable wrapper) that allows a COM object to interact with your managed application.
In order for the COM object to be exposed, your class library assembly must also have a strong name. To create a strong name, use the utility SN.EXE.
sn -k Database_COM_Key.snk
Open the AssemblyInfo.cs and modify the line:
[assembly: AssemblyKeyFile("Database_COM_Key.snk")]
Build the object. The build also results into a type library that can be imported into your managed or unmanaged code.
Part II: Creating a Client using Visual C++ to access this COM Object
I have tested the COM object with the VC++ 6.0 and VC++ .NET environment. Create a simple project using the VC++ development environment. Import the type library using the #import
directive. Create a Smart Pointer to the Interface.Execute
, the exposed functions from the interface. Make sure to add the CoInitialize()
call when the application loads.
CoInitialize(NULL);
Database_COMObject::DBCOM_InterfacePtr
p(__uuidof(Database_COMObject::DBCOM_Class));
db_com_ptr = p ;
db_com_ptr->Init("scott" , "tiger");
This code executes a SQL Command against the Customers table and returns the customer information for a given customer ID.
char cmd[1024];
sprintf(cmd , "SELECT COMPANYNAME , CONTACTNAME ,
CONTACTTITLE , ADDRESS FROM CUSTOMERS WHERE CUSTOMERID = '%s'" , m_id );
const char *p ;
bool ret = db_com_ptr->ExecuteSelectCommand(cmd);
if ( ! db_com_ptr->NextRow() ) return ;
_bstr_t mData = db_com_ptr->GetColumnData(3);
p = mData ;
m_address = (CString)p ;