在 C# 中构建 COM 对象






4.77/5 (49投票s)
2004 年 8 月 3 日
4分钟阅读

413847

2
在 C# 中构建 COM 对象。
引言
本文涵盖的主题包括:
- 在 C# 中创建简单的 COM 对象(使用 COM Interop 属性)。
- 通过 VC++ 客户端访问 COM。(我已使用 VC++6.0 和 VC++ .NET 进行测试)。客户端使用类型库(.TLB 文件)。
为了方便开发人员使用和测试此代码,我使用了 SQL Server 数据库默认安装中内置的 **Northwind** 数据库。
- 修改 COM 对象中的 SQL Server 名称以连接到您的 SQL Server。
- 此外,我还创建了一个默认的用户 ID 和密码 **scott / tiger** 来连接数据库。您可以创建此用户,或使用现有的 ID / 密码。
第一部分:在 C# 中创建简单的 COM 对象
COM 对象是 **ClassLibrary** 类型。COM 对象会生成一个 DLL 文件。要在 VS 开发环境中创建简单的 COM 对象,请选择...
New->Project->Visual C# Projects ->Class Library
创建一个名为 **Database_COMObject** 的项目。
**请记住**:将 VC# 对象暴露给 COM 世界需要遵循以下要求...
- 类必须是
public
的。 - 属性、方法和事件必须是
public
的。 - 属性和方法必须在类接口上声明。
- 事件必须在事件接口上声明。
类中未在这些接口上声明的其他公共成员对 COM 不可见,但对其他 .NET Framework 对象可见。要将属性和方法暴露给 COM,您必须在类接口上声明它们,并用 DispId
属性标记它们,并在类中实现它们。成员在接口中声明的顺序就是 COM **vtable** 中使用的顺序。要从您的类中暴露事件,您必须在事件接口上声明它们,并用 DispId
属性标记它们。该类不应实现此接口。该类实现类接口(它可以实现多个接口,但第一个实现将是默认类接口)。在此实现暴露给 COM 的方法和属性。它们必须标记为 public
,并且必须与类接口中的声明匹配。此外,在此声明类引发的事件。它们必须标记为 public
,并且必须与事件接口中的声明匹配。
每个接口都需要在接口名称之前设置一个 GUID 属性。要生成唯一的 GUID,请使用 guidgen.exe 工具并选择 **Registry Format**。
接口类看起来是这样的...
[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);
}
对于 COM 事件...
// // Events interface Database_COMObjectEvents
[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
{
请注意类前面的以下属性设置
ClassInterface(ClassInterfaceType.None),
ComSourceInterfaces(typeof(DBCOM_Events))]
ClassInterfaceType.None
表示该类不生成类接口。如果没有显式实现接口,该类将仅通过 IDispatch
提供后期绑定访问。用户应通过类显式实现的接口公开功能。这是 ClassInterfaceAttribute
的推荐设置。
ComSourceInterfaces(typeof(DBCOM_Events))]
标识了一个接口列表,这些接口将作为已标记类的 COM 事件源公开。在我们的示例中,我们没有公开任何事件。
这是完整的 COM 对象源代码...
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);
}
// Events interface Database_COMObjectEvents
[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();
//MessageBox.Show("CONNECTED");
}
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();
}
}
}
在构建 COM 对象之前,我们必须注册该对象以进行 COM Interop。为此,请右键单击“解决方案资源管理器”中的项目名称。单击“属性”。单击“配置”->“生成”。展开输出部分。将“注册 COM Interop”设置为“true”。
指示您的托管应用程序将公开一个 COM 对象(COM 可调用包装器),该对象允许 COM 对象与您的托管应用程序进行交互。
为了暴露 COM 对象,您的类库程序集也必须具有强名称。要创建强名称,请使用实用程序 SN.EXE。
sn -k Database_COM_Key.snk
打开 AssemblyInfo.cs 并修改行
[assembly: AssemblyKeyFile("Database_COM_Key.snk")]
构建该对象。构建还会生成一个类型库,可以将其导入到您的托管或非托管代码中。
第二部分:使用 Visual C++ 创建客户端以访问此 COM 对象
我已使用 VC++ 6.0 和 VC++ .NET 环境测试了此 COM 对象。使用 VC++ 开发环境创建一个简单的项目。使用 #import
指令导入类型库。创建一个指向 Interface.Execute
的智能指针,这是从接口公开的函数。确保在应用程序加载时添加 CoInitialize()
调用。
CoInitialize(NULL); Database_COMObject::DBCOM_InterfacePtr p(__uuidof(Database_COMObject::DBCOM_Class)); db_com_ptr = p ; db_com_ptr->Init("scott" , "tiger");
此代码针对 Customers 表执行 SQL 命令,并返回给定客户 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 ;