• [翻译]用C#写COM组件


    原文出处: Building COM Objects in C#

    说明:

    我是一个C#程序员,但是有一次一个需求只能用C/C++去写,恰好需要读取的数据存放在DB(SQL CE v3)里面,而我又不会C/C++(关键是用OleDB访问DB,这个实在是繁琐),所以催生了用C#写一个COM组件,用C/C++去调用的想法.可谓,很傻很天真.但是也是一种思路,如果MS提供C API的话,问题就简单多了.可是事实是,MS自己的.NET CF用着C API,给用户却暴露着COM API.....OK,言归正传.

    主要内容:

    • 用C#创建一个简单的COM组件(通过COM Interop)
    • 用VC++写一个客户端去访问COM组件.客户端用TLB文件.

    本着易于使用的目的,我把Northwind导入到了SQLServer,然后测试了我的代码.(the sake of simplycity这个不知道啥意思,难道是由于出现纸尿布....).

    • 修改COM组件里面的机器名为你的SQL Server的机器名.(2005以上需要 机器名\实例名)
    • 当然我在里面也创建了一个用户scott密码是tiger,用来连接数据库.你可以选择这个用户名,或者重新建一个.

    Part I: 用C#创建一个简单的COM组件

    COM对象是一种类库.COM组件将产生DLL文件.在VS环境里面创建COM组件请选择....

      File->New->Project->VisualC# Projects ->Class Library.

    创建一个名为Database_COMObject的类库工程.

    请记住:想要把C#对象当作COM对象需要以下几点...

    • class必须是public的
    • 属性,方法和事件必须是public
    • 属性和方法必须在Interface里面定义
    • 事件必须在事件的接口中

    未在接口中定义的成员,而在实现里面是public的成员,对COM是不可见的,但是对其他的.NET程序是可见的.为了把属性和方法暴露给COM,你必须在接口中定义他们,并且把他们用DispId属性标记,在class里面实现(.....).在接口里面定义的成员只是为了使用vtable(虚函数表).要想暴露事件,你也必须把成员定义在事件接口里面并且标记DispId属性.类不需要实现此接口(???).类可以实现接口(一个类可以实现多个接口,只有第一个接口才是默认的接口.).暴露给COM的那些属性方法其实就在类的实现里面.他们必须被标记为public,而且要符合接口里面的定义.Also, declare the events raised by the class here. They must be marked public and must match the declarations in the events interface. (这两句不知道具体的含义,代码里面也没看出端倪.)

    每一个接口都要有一个GUID属性(我当时上学的时候,把他叫属性属性,或者定制属性,现在也不清楚到底叫什么..).你可以用guidgen.exe来产生一个GUID值.

    这个接口就长这个样子:

        [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("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那里注册.

    打开Solution Explorer->Properties->Configuration->Build->Expand the output section->Register for COM Interop改为True.

    (我在VS 2008里面貌似不是这么操作的,工程上面点右键->属性->Build->最下面的Output->Register for COM Interop改为True).

    表明,我要把Managed程序导出一个COM对象,并且COM对象可以和我们的托管程序交互.

    为了(真正)导出COM对象,程序集还需要强命名,可以用sn.exe生成一个StrongName:

    sn -k Database_COM_Key.snk

    在AssemblyInfo.cs文件里面修改:

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

    编译这个对象.会产生一个tlb文件,通过这个可以使Managed代码和Native代码都能访问你的COM对象.

    Part II : 用VC++创建一个客户端去访问这个COM对象

    我已经在VC++6.0和VC++.NET下面访问呢过改COM对象.

    创建一个简单的工程,通过 #import directive导入type library.

    创建一个只能指针指向接口的实例,执行那些导出函数,确保在程序加载的时候执行CoInitialize().

        CoInitialize(NULL);
    
        Database_COMObject::DBCOM_InterfacePtr 
           p(__uuidof(Database_COMObject::DBCOM_Class));
        db_com_ptr = p ;
        db_com_ptr->Init("scott" , "tiger");
    

    下面的代码通过一个Customer ID去在Customer表中查询:

        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 ;
    

    PS:

    总算翻译完了....英语不好,有一些句子不能理解含义,英语好的童鞋推荐直接看e文.

    我只能说我很悲剧,当初想法很好,只可惜,.NET CF下面,不支持用C#写一个COM组件.

    我的口头禅就是:.NET CF除了慢,再没有其他优点.

    /**********************************************************************
     * 机械教条主义
     *
     * From:          http://www.cnblogs.com/egmkang/
     * Email:          egmkang [at] outlook.com
     * Weibo:        http://weibo.com/egmkang
     * Github:       http://github.com/egmkang
     *
     **********************************************************************/

  • 相关阅读:
    基于Metronic的Bootstrap开发框架经验总结(14)--条码和二维码的生成及打印处理
    基于Metronic的Bootstrap开发框架经验总结(13)--页面链接收藏夹功能的实现2(利用Sortable进行拖动排序)
    基于Metronic的Bootstrap开发框架经验总结(12)--页面链接收藏夹功能的实现
    C#开发微信门户及应用(45)--微信扫码登录
    从博客园迁移到简书平台
    基于MVC4+EasyUI的Web开发框架经验总结(17)--布局和对话框自动适应大小的处理
    基于Metronic的Bootstrap开发框架经验总结(11)--页面菜单的几种呈现方式
    基于MVC4+EasyUI的Web开发框架经验总结(16)--使用云打印控件C-Lodop打印页面或套打报关运单信息
    在Winform程序中设置管理员权限及为用户组添加写入权限
    C#开发微信门户及应用(44)--微信H5页面开发的经验总结
  • 原文地址:https://www.cnblogs.com/egmkang/p/1626607.html
Copyright © 2020-2023  润新知