[翻譯]用C#寫COM組件

來源:互聯網
上載者:User

原文出處: 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,然後測試了My Code.(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除了慢,再沒有其他優點.

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.