用C#创建COM对象

http://www.uncj.net/bbs/index.asp 2003-1-15 ;程序员大联盟

在本篇文章中,我们将讨论下面的问题:

  • 使用C#创建一个简单的COM对象(使用COM的Interop特性)。
  • 从VC++客户端软件中访问COM。客户端软件使用了TypeLibrary(.TLB文件)。

为了简单和方便开发人员使用、测试起见,我们使用了SQLSERVER数据库软件的缺省安装中的Northwind数据库。

  • 修改COM对象中SQLServer的名字,与SQLServer连接。
  • 我们已经创建了连接数据库用的分别为scott、tiger的用户名和口令,我们可以使用它或者其他现有的用户名和口令。

第一部分:用C#创建简单的COM对象

COM对象是ClassLibrary类,它生成DLL文件。要在VS开发环境中创建一个简单的COM对象,我们可以依次选择“文件”->;“新创建”->;“工程”->;“VisualC#工程”->;“类库”,然后创建一个名字为Database_COMObject的工程。

需要注意的是:在COM中调用VC#对象需要下面的条件:

  • 类必须是public性质。
  • 特性、方法和事件必须是public性质的。
  • 特性和方法必须在类接口中定义。
  • 事件必须在事件接口中定义。

不是在这些接口中定义的public性质的类成员不能被COM访问,但它们可以被其他的.NET Framework对象访问。要让COM能够访问特性和方法,我们必须在类接口中定义它们,使它们具有DispId属性,并在类中实现这些特性和方法。这些成员定义时的顺序也就是它们在COM中顺序。要让COM访问类中的事件,必须在事件接口中定义这些事件,并赋予它们DispId属性。事件接口不应当由类完成,类只实现类接口(它可以实现不止一个接口,但第一个接口是缺省接口),应当在缺省接口中实现需要让COM访问的方法和特性,方法和特性必须被标识为public性质,并符合在类接口中的定义。需要让COM访问的事件也在缺省的类接口中完成,它们也必须被标识为public性质,并符合事件接口中的定义。

在接口名字之前,每个接口需要一个GUID特性。要生成变个唯一的Guid,需要运行guidgen.exe工具软件,并选择“注册表格式” 下面是一个类界面:

[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事件接口:

// 事件接口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(BCOM_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);
    }

    // 事件接口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注册该对象。右击方案管理器中的工程名字,点击快捷菜单上的“属性”选项,然后再点击“配置”->;“创建”,扩展output小节,将Register for COM Interop选项的值设置为true。这样,一个COM对象就能够与可管理性应用程序进行交互。

为了使COM对象能够被外部对象调用,类库组合必须有一个强名字。创建强名字需要用到SN.EXE名字: sn -k Database_COM_Key.snk

打开AssemblyInfo.cs,并修改下面一行的内容:

[assembly:AssemblyKeyFile(";Database_COM_Key.snk";)]

创建对象。创建对象会生成一个可以被导入到可管理性或非可管理性代码中的类库。

第二部分:使用Visual C++创建访问COM对象的客户端软件

  • 使用VC++开发环境创建一个简单的工程。
  • 使用#import directive导入类型库。
  • 在界面中创建一个Smart Pointer,从接口中执行COM类提供的功能。确保在应用程序加载时添加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;
asp.net
2002-9-28 3:23:23


liqit 2002-9-28 7:47:00

好文章!

johlon 2002-9-28 8:14:05

不错

tianli_ 2002-10-17 10:22:29

收藏先

WS.NET 2002-10-17 10:41:45

代码有些错误,如:

bool ret = db_com_ptr->; ExecuteSelectCommand(cmd);
if(! db_com_ptr->;NextRow()) return;
_bstr_t mData = db_com_ptr->; GetColumnData(3);

文章还不错。

Contributors: FHL