【译文转帖】用C#写COM组件 Building COM Objects in C#
2012-10-29 16:19
591 查看
说明:
我是一个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")]
publicinterface 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事件:
实现接口的类:
在类的前面标记:
ClassInterface(ClassInterfaceType.None), ComSourceInterfaces(typeof(DBCOM_Events))]
ClassInterfaceType.None表示,这个类不会产生类接口.如果没有显式的接口实现,那么这个类只能提供对IDispatch的访问.用户期待通过接口导出该类显式实现了的成员.所以需要使用设置ClassInterfaceAttribute.
ComSourceInterfaces(typeof(DBCOM_Events))]标明标记的这个类会把接口暴露给COM事件源.在我们的例子中,没有什么需要暴露的..
下面是完整的COM对象:
在编译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().
下面的代码通过一个Customer ID去在Customer表中查询:
PS:
总算翻译完了....英语不好,有一些句子不能理解含义,英语好的童鞋推荐直接看e文.
我只能说我很悲剧,当初想法很好,只可惜,.NET CF下面,不支持用C#写一个COM组件.
我的口头禅就是:.NET CF除了慢,再没有其他优点.
我是一个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")]
publicinterface 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除了慢,再没有其他优点.
相关文章推荐
- 转帖: C# vs. Java:相反的思维方式 (译文)
- [转帖]C#执行SQL脚本,读取XML文件
- C#操作Excel(导入导出) (转帖)
- c# list排序的三种实现方式 (转帖)
- 串口绘制曲线(基于C#绘制曲线类)(转帖)
- 【转帖】C# DllImport 系统调用使用详解 托管代码的介绍 EntryPoint的使用
- C#获取当前IE地址栏的url[转帖]
- c# 日期函数[string.Format----GetDateTimeFormats]格式 .【转帖备查】
- [转帖]如何将一张图片写入数据库SQL,并能正确在C#网页中显示
- (转帖)用C#创建SQL Server的存储过程
- (转帖)用C#建立通用对象池
- [译文]C# Heap(ing) Vs Stack(ing) in .NET: Part IV
- C#正则表达式整理备忘 (转帖)
- C#中委托如何使用?(转帖)
- C#基础概念二十五问 (转帖)
- C# 具有背景栅格的绘曲线类(一)(转帖)
- 重新过一遍ASP.NET 2.0(C#)(4) - Cache&SqlCacheDependency(缓存和SqlCacheDependency特性)(转帖)
- C#中 父类与子类相互强制转换之实验(转帖)
- LINQ 的演变及其对C#设计的影响(转帖)
- 转帖备用,各种截取字符串不同要求 C#