这两周在和同事Ben讨论一些东西,Ben的想法是实现一个类似于iLog、Drools的规则引擎,但不需要如此庞大和昂贵,我们是轻量级的BizTalk,并且能够将SOA的组件通过规则结合成一个序列,从而将SOA和BPM紧密地结合起来,Ben这几年都致力于进行这方面的钻研。

规则引擎的概念是我在上一个涉及到BPM的项目中分离出来的(当时还未接触到Drools,Ben接手我的项目后,向我推荐了这款框架),去年我差一点使用了iLog(我们公司购买了这款软件),当时基本了解了一下,iLog的优势是:拥有目前号称最快的数学计算速度。

但目前我不太热衷于紧密的框架或ESB,我更倾向于诸如Google Friend Connect这样的Mash Up应用。通过Web的方式向其它B/S系统提供应用。

另一个值得探讨的问题是规则VS策略,这是我正在思考的一个问题。规则是指条件路由(即消息mapping),而策略应该是最优的路由通道。规则是向用户提供的一种灵活的运行时配置,而策略是向用户提供的一种权威模式(我们假定这种权威是在程序员进行计算和测试之前用户所无法模拟和估算的,我们假定这种权威是在程序员进行分析和设计并找出所有的问题和矛盾之后能避免用户在实际使用中遇到的各种困难);尽管从编程的角度来看它们是一个等同体。

此外如果实现这样的框架,业务数据的处理,尤其是计算所需的关键数据处理是一个急需解决的问题。缓存和同步技术需要应用于其中,还有大量的计算。

这个东西是解决什么问题呢?归根结底是业务模式的重用和构件粒度的分割,对于软件系统来说,重用的难度要大一些。

只是一些想法,暂时写到这儿,大概还需要一两周的时间来进行完善吧。

一些参考资料:

Java规则引擎的工作原理及其实际应用

Ilog、Drools、Jess规则引擎的Rule Language 对比

规则引擎实现探讨

也可以去iLog的官方站点,目前我对他们的数学计算极其感兴趣

http://www.ilog.cn/

posted @ 2008-12-27 00:06 Justina Chen 阅读(91) | 评论 (0)编辑

作为.NET的跨平台项目,Mono拓展了.NET cryptography框架中不足的地方,具体参见官方说明:

http://www.mono-project.com/Cryptography

1. Mono的X.509 certificates类是100%使用托管代码实现的;

2. Mono推荐优先使用他们的Mono.Security.X509.*,并许诺以后有升级保障,可以支持一些加密工具;他们还说:

We pass the fifteen tests from Merlin's xmldsig suite with success. Which is funny because Microsoft fails in one case where both a X509Certificate and an X509CRL are present in an X509Data.

唯一遗憾的是,Mono将读取CRL信息的X509CrlEntry类放在了X509Crl内部,只暴露了ArrayList(因无法访问,所以无法转换为X509CrlEntry)和一个输出byte[]类型SerialNumber的读取方法;另一提供的GetCrlEntry方法必须是已知证书或已知SerialNumber的情况下才能查找X509CrlEntry。所以,要实现更多的功能,尤其是在DN和SerialNumber的处理上,还是优先选用Bouncy Castle Crypto。

下面是X509CrlEntry的源代码:

http://www.koders.com/csharp/fid7A8A0694ACFA57ED4F39F7F93511E896CA71068E.aspx?s=mdef%3Ainsert

下载Mono for Windows安装之后,将Mono.Security.dll引入工程,读取X509Crl序列号的代码如下:

using System;
using Mono.Security.X509;

namespace Security
{
    
public class CrlTest
    {
        
public CrlTest(byte[] obj)
        {
            X509Crl crl 
= new X509Crl(obj);
            
for ( int i = 0; i < crl.Entries.Count; i++ )
            {
                
int serialNumber = Convert.ToInt32(crl[i].SerialNumber);
            }
        }
    }
}
posted @ 2008-11-24 22:05 Justina Chen 阅读(1169) | 评论 (1)编辑

X.509 结构的证书被吊销后,序列号会出现在Certificate Revocation List (CRL) 中,我们可以将它另存为一个.crl的文件,就能够查看被吊销的证书信息,但.NET Framework并没有提供可对Crl进行属性访问的类(Java中提供了X509Crl),要实现这样的功能,我们得借助.NET框架之外的技术:Bouncy Castle Crypto或者Mono SDK。

先介绍如何使用Bouncy Castle Crypto(Version 1.4)读取X.509证书及吊销列表。

Bouncy Castle Crypto是一个开源的加/解密框架,下载地址:

http://www.bouncycastle.org/csharp/download/bccrypto-net-1.4-bin.zip

首先加入X.509证书所在的命名空间:

using Org.BouncyCastle.X509;

 

其中的几个有关的类:

X509CrlParser 用于构建一个crl对象,支持从字节数组和内存流中获取数据。

X509Crl crl对象,包含证书吊销组织、吊销证书列表、时间戳等信息。

X509CrlEntry crl对象中被吊销的证书对象。

ISet 在Org.BouncyCastle.Utilities.Collections下,X509Crl中被读取所有证书对象放在HashSet中,以ISet接口类型返回,HashSet支持迭代器。

下面是读取Crl的示例:

//获取obj
List<int> numbers = new List<int>();

X509CrlParser parser 
= new X509CrlParser();
X509Crl crl 
= parser.ReadCrl((byte[])obj);
//获取所有的吊销证书
ISet crlSet = crl.GetRevokedCertificates();
if (crlSet != null && crlSet.Count > 0)
{
    
foreach (object o in crlSet)
    {
        X509CrlEntry crlEntry 
= (X509CrlEntry)o;
        
int serialNumber = crlEntry.SerialNumber.IntValue;
        
if (!numbers.Contains(serialNumber))
        {
            numbers.Add(serialNumber);
        }
    }
}

 

X509CrlEntry.SerialNumber.IntValue将16进制的证书序列号以10进制的Int32类型输出。

另外再提一下BC Crypto的X509Certificate类和.NET Framework下的X509Certificate2类的区别:

X509Certificate2输出证书DN信息时,属性SubjectName是将个人信息按照从小到大(姓名-组织-市-省-国家)排列,而BC Crypto的X509Certificate使用SubjectDN属性,将个人信息从大到小排列,市的标识是ST,.NET是S。如果需要根据证书生成组织结构,这个需要特别注意。

posted @ 2008-11-20 21:25 Justina Chen 阅读(1188) | 评论 (1)编辑

从Active Directory获取大量对象时应特别注意,一不小心,就会掉入性能瓶颈甚至引起内存泄漏。本文提供了一个关于.NET访问Active Directory的优化例子。

1.获取对象的属性值 DirectoryEntry.Properties

获取一个DirectoryEntry对象后,我们就开始检索它的属性值;习惯了用foreach的朋友一般会这样写:

DirectoryEntry entry = //
foreach(Property property in entry.Properties)
{
    
if(property.Name == //)
        
//
}

事实上,System.DirectoryService.PropertyCollection内部包含了一个HashTable用于键值对访问,并且在索引器中,对未包含的键,创建一个新的PropertyValueCollection对象并加入到HashTable中。

所以,我们可以放心地直接写下:

string propertName = //
object value = entry.Properties[propertName].Value;

 

对这个新建的PropertyValueColletion,它的Value属性定义同样保证了访问的健壮性,这是它的源代码:

public object Value
{
    
get
    {
        
if (base.Count == 0)
        {
            
return null;
        }
        
if (base.Count == 1)
        {
            
return base.List[0];
        }
        
object[] array = new object[base.Count];
        
base.List.CopyTo(array, 0);
        
return array;
    }
}

 

这样,代码中不需要去调用entry.Properties.Contains方法,而直接使用以下代码就可以高效地访问DirectoryEntry对象的属性了:

object value = entry.Properties[propertyName].Value;
if ( value != null )
{
    
//
}

 

在测试中,查询两万个对象的属性值时,可以节省90秒左右的时间。

2.构建搜索器 DirectorySearcher

使用这个类应注意三点:

(1)对搜索结果的缓存。它默认的CacheResults为true,如果你不需要,应记得将它设置为false,否则一大块空间就浪费了。

(2)仅搜索已赋值的Property。PropertyNamesOnly默认为false,会搜索所有的Property返回多于你想要的对象,记得将它设置为true。

(3)可以在其构造函数中指定PropertyName数组,只搜索包含PropertyName的对象。

下面的例子是搜索包含UserName和Password属性并且属性已赋值的对象:

DirectorySearcher searcher = new DirectorySearcher(
                entry,
                
"username=*",
                
new string[] { "UserName""Password", });
searcher.PropertyNamesOnly 
= true;
SearchResultCollection results 
= searcher.FindAll();

 

3.遍历和释放结果集 SearchResultCollection

foreach对于SearchResultCollection的访问是更高效的方式,这是因为,SearchResultCollection的内部使用ArrayList进行集合的索引访问,在第一次加载ArrayList时,它调用迭代器将所有的SearchResult对象创建并装箱到ArrayList中,当进行SearchResultCollection[int index]访问时,它对ArrayList中的对象进行拆箱操作。下面是SearchResultCollection构建InnerList的源代码:

private ArrayList InnerList
{
    
get
    {
        
if (this.innerList == null)
        {
            
this.innerList = new ArrayList();
            IEnumerator enumerator 
= new ResultsEnumerator(thisthis.rootEntry.GetUsername(), this.rootEntry.GetPassword(), this.rootEntry.AuthenticationType);
            
while (enumerator.MoveNext())
            {
                
this.innerList.Add(enumerator.Current);
            }
        }
        
return this.innerList;
    }
}

 

而foreach是直接调用迭代器创建和返回一个SearchResult对象,避免了装箱与拆箱的过程。

应严重注意的是:SearchResultCollection是未托管资源,而且会占用大量的内存,需要获取大量对象的属性时,推荐使用集合来保存所需的属性值,完成之后立即调用SearchResultCollection.Dispose()来对它进行释放,否则,会导致内存泄露。

posted @ 2008-11-18 23:11 Justina Chen 阅读(1564) | 评论 (4)编辑

Castle Project非常庞大,ActiveRecord是其中一个非常适合用于Domain Model开发的O/R Mapping框架。它使用.NET的反射特性无需配置文件,集成NHibernate完成数据层持久化功能,根据创建的Model生成数据表,这使得我们在Domain Model的开发中,只需要致力于业务对象关系的分析和定义,大大简少了工作量。

开发环境:

Windows Server 2003 Enterprise + IIS 6.0

CastleProject-1.0-RC3

Visual Studio 2008

.NET Framework 3.5

Oracle 9i

Code Smith 4.1(如果你先定义好了数据表,可用于生成实体类,但它不支持复合主键)

NUnit 2.4.8.0

首先我们来看看ActiveRecord的官方示例MoreComplexSample-vs2003中几个类的关系图:

ActiveRecord的实体类需要在类名上标记[ActiveRecord]。

[BelongsTo] + [HasMany] 表示两个对象的一对多关系

[BelongsTo("ColumnName")] 生成ColumnName的列作为外键。一个Order只有一个Customer(belongs to)。

[HasMany] 不会生成列,Customer可以有多个Order(has many)。如果类中BelongsTo的属性类型不是这个类本身,那么它的类型中必须有对应的HasMany(或HasAndBelongsToMany),否则使用ActiveRecordStarter初始化时会报错。

生成数据表关系如下图所示:

Order.cs


[BelongsTo("CustomerId")]
public Customer Customer
{
get { return customer; }
set { customer = value; }
}

 
Customer.cs
[HasMany(Lazy=true)]
public ISet<Order> Orders
{
get { return orders; }
set { orders = value; }

}

 
而对于Category,它使用自身的类型定义Parent属性并标记[BelongsTo],代码如下:
[BelongsTo("ParentId")]
public Category Parent
{
get { return parent; }
set { parent = value; }
}

将生成一个带有Parent外键,指向自身主键的Category表。

[HasAndBelongsToMany] 表示多对多关系并生成关系表

[HasAndBelongsToMany(Table="TableName", ColumnKey="ColumnName", ColumnRef="ColumnName")]

HasAndBelongsToMany将以指定的表名生成两个类的关联表,ColumnKey、ColumnRef分别代表这个类与关系对象在TableName中提供作外键的列名。例中Category与Product是多对多关系:

Category.cs

[HasAndBelongsToMany(
Table
="ProductCategory", ColumnKey="CategoryId", ColumnRef="ProductId",
Inverse
=true, Lazy=true)]
public ISet<Product> Products
{
get { return products; }
set { products = value; }
}

 


Product.cs

[HasAndBelongsToMany(
Table
="ProductCategory", ColumnKey="ProductId", ColumnRef="CategoryId", Lazy=true)]
public ISet<Category> Categories
{
get { return categories; }
set { categories = value; }
}

 

两个对象中均标记为HasAndBelongsToMany并且表、列对应,ActiveRecord将生成一个名为ProducCategory,包含ProductID和CategoryID的关系表。 

如果关系表恰恰也被定义作为了类,如LineItem,它在Order.cs中已经被使用[HasAndBelongsToMany]进行了表名定义,那么,它需要用两个BelongsTo来表示对Order和Product的关系映射。

Order.cs

[HasAndBelongsToMany(
Table
="LineItem",
ColumnKey
="OrderId", ColumnRef="ProductId", Inverse = true, Lazy = true)]
public ISet<Product> Products
{
get { return products; }
set { products = value; }
}

 

LineItem.cs

[BelongsTo("OrderId", NotNull = true, UniqueKey = "ConstraintName")]
public Order Order
{
get { return order; }
set { order = value; }
}

[BelongsTo(
"ProductId", NotNull = true, UniqueKey = "ConstraintName")]
public Product Product
{
get { return product; }
set { product = value; }
}

 
除本例所用到的关系之外,还有[OneToOne]表示一对一关系:
public class Product
{
[OneToOne]
public ProductDetail Detail { get; set; }
}

在对应的ProductDetail类中需要在主键上标记[PrimaryKey(PrimaryKeyType.Foreign)]来表示它的主键同时是外键:
public class ProductDetail
{
[PrimaryKey(PrimaryKeyType.Foreign)]
public int ProductID { get; set; }
}

 

posted @ 2008-11-16 22:29 Justina Chen 阅读(1309) | 评论 (7)编辑

目前.NET Framework 3.5 LINQ查询的一个BUG了,在对DataTable进行删除操作之后使用LINQ表达式进行查询,它会检索RowState为DataRowState.Deleted的行记录,然后抛出DataRowState.Deleted记录不可访问的异常。在MS解决这个BUG之前,编程中应注意避免出错:

1.使用LINQ查询之前调用DataSet.AcceptChanges(),移除被标记为DataRowState.Deleted的记录;

2.使用Where方法过滤:

dt.Select(a => a.UserID).Where(a => a.RowState != DataRowState.Deleted);
posted @ 2008-11-15 06:22 Justina Chen 阅读(107) | 评论 (0)编辑

项目中需要对两个不同格式的存储设备进行数据转录,因为数据量非常大,所以时间非常缓慢;解决方案是使用ReaderWriterSlim类建立一个共享的同步数据,可以支持一个线程读取外部设备,向同步数据写入;多个线程从同步数据中读取,转换格式,然后写入到本地设备。

本例中采用Queue<T>作为存放数据的集合,写入线程向它的尾部写入对象,读取线程从它的头部获取对象。

需要注意的是,由于Queue会抛弃已处理的对象,所以在同步数据队列中无法验证数据对象的唯一性,被写入的数据库需要去掉唯一约束,或在写入时向数据库请求验证。

首先定义一个读写接口: 

 

namespace Common
{
    
public interface IReaderWriter<T>
    {
        T Read(
int argument);
        
void Write(int arugment, T instance);
        
void Delete(int argument);
        
void Clear();
    }
}

 

然后实现一个队列的读写器:

 

namespace Common
{
    
public class QueueReaderWriter<T> : IReaderWriter<T>
    {
        
private Queue<T> queues;

        
public QueueReaderWriter()
        {
            queues 
= new Queue<T>();
        }

        
public QueueReaderWriter(int capacity)
        {
            queues 
= new Queue<T>(capacity);
        }

        
#region IReadWrite<T> 成员

        
public T Read(int argument)
        {
            
return queues.FirstOrDefault();
        }

        
public void Write(int arugment, T instance)
        {
            queues.Enqueue(instance);
        }

        
public void Delete(int argument)
        {
            queues.Dequeue();
        }

        
public void Clear()
        {
            queues.Clear();
            queues.TrimExcess();
        }

        
#endregion
    }
}

 

使用ReaderWriterLockSlim实现同步数据类:

namespace Common
{
    
public class SynchronizedWriteData<T> : IDisposable
    {
        
private ReaderWriterLockSlim _dataLock = new ReaderWriterLockSlim();
        
private IReaderWriter<T> _innerData;

        
private SynchronizedWriteData()
        { }

        
public SynchronizedWriteData(IReaderWriter<T> innerData)
        {
            _innerData 
= innerData;
        }

        
public T Read()
        {
            _dataLock.EnterReadLock();
            
try
            {
                
return _innerData.Read(0);
            }
            
finally
            {
                _dataLock.ExitReadLock();
            }
        }

        
public T Read(int argument)
        {
            _dataLock.EnterReadLock();
            
try
            {
                
return _innerData.Read(argument);
            }
            
finally
            {
                _dataLock.ExitReadLock();
            }
        }

        
public void Add(T instance)
        {
            _dataLock.EnterWriteLock();
            
try
            {
                _innerData.Write(
0, instance);
            }
            
finally
            {
                _dataLock.ExitWriteLock();
            }
        }

        
public void Add(int argument, T instance)
        {
            _dataLock.EnterWriteLock();
            
try
            {
                _innerData.Write(argument, instance);
            }
            
finally
            {
                _dataLock.ExitWriteLock();
            }
        }

        
public bool AddWithTimeout(T instance, int timeout)
        {
            
if (_dataLock.TryEnterWriteLock(timeout))
            {
                
try
                {
                    _innerData.Write(
0, instance);
                }
                
finally
                {
                    _dataLock.ExitWriteLock();
                }
                
return true;
            }
            
else
            {
                
return false;
            }
        }

        
public bool AddWithTimeout(int argument, T instance, int timeout)
        {
            
if (_dataLock.TryEnterWriteLock(timeout))
            {
                
try
                {
                    _innerData.Write(argument, instance);
                }
                
finally
                {
                    _dataLock.ExitWriteLock();
                }
                
return true;
            }
            
else
            {
                
return false;
            }
        }

        
public void Delete()
        {
            _dataLock.EnterWriteLock();
            
try
            {
                _innerData.Delete(
0);
            }
            
finally
            {
                _dataLock.ExitWriteLock();
            }
        }

        
public void Delete(int argument)
        {
            _dataLock.EnterWriteLock();
            
try
            {
                _innerData.Delete(argument);
            }
            
finally
            {
                _dataLock.ExitWriteLock();
            }
        }

        
#region IDisposable 成员

        
public void Dispose()
        {
            
try
            {
                _dataLock.EnterWriteLock();
                {
                    
try
                    {
                        _innerData.Clear();
                    }
                    
finally
                    {
                        _dataLock.ExitWriteLock();
                    }
                }
            }
            
finally
            {
                _dataLock.Dispose();
            }
        }

        
#endregion
    }
}

 

namespace ExternalDataHandle
{
    
/// <summary>
    
/// 从外部数据源获取到内部数据源的适配器抽象类
    
/// </summary>
    
/// <typeparam name="T">T 数据对象类型</typeparam>
    public abstract class ExternalDataAdapter<T> : IDisposable
    {
        
/// <summary>
        
/// 外部数据源连接字符串
        
/// </summary>
        protected abstract string ConnectString { get; }

        
/// <summary>
        
/// 提供初始化数据适配器的方法
        
/// </summary>
        protected abstract void Initialize();

        
/// <summary>
        
/// 提供数据传递的方法
        
/// </summary>
        public abstract void Transmit();

        
/// <summary>
        
/// 提供从外部数据设备读取数据的方法
        
/// </summary>
        protected abstract void ReadFromExternalDevice();

        
/// <summary>
        
/// 提供保存数据到内部设备的方法
        
/// </summary>
        protected abstract void SaveToInternalDevice();

        
#region IDisposable 成员

        
public abstract void Dispose();

        
#endregion
    }
}

 

多线程数据转录类,本例只使用了一个读取线程:

namespace ExternalDataHandle
{
    
/// <summary>
    
/// 提供多线程方式从外部数据源获取到内部数据源的适配器类
    
/// </summary>
    
/// <typeparam name="T"></typeparam>
    public abstract class MultiThreadAdapter<T> : ExternalDataAdapter<T>
    {
        
protected SynchronizedWriteData<T> _data;
        
protected Thread _readThread;

        
protected abstract override string ConnectString { get; }

        
protected abstract override void Initialize();

        
public sealed override void Transmit()
        {
            _readThread 
= new Thread(new ThreadStart(ReadFromExternalDevice));
            _readThread.Start();
            Thread.Sleep(
10000);
            
while (_readThread.IsAlive)
            {
                SaveToInternalDevice();
            }
            _readThread.Join();
        }

        
protected abstract override void ReadFromExternalDevice();

        
protected abstract override void SaveToInternalDevice();

        
public override void Dispose()
        {
            
if (_data != null)
            {
                _data.Dispose();
            }
        }
    }
}

 

posted @ 2008-11-15 04:50 Justina Chen 阅读(2107) | 评论 (4)编辑

要使用C#实现一个ActiveX控件,需要解决三个问题:

1.使.NET组件能够被COM调用

2.在客户机上注册后,ActiveX控件能通过IE的安全认证

3.未在客户机上注册时,安装包能通过IE的签名认证

本程序的开发环境是.NET Framework 3.5,工具是Visual Studio .NET 2008,在安装.NET Framework 3.5的客户机上通过测试。

下面是实现步骤:

(一)创建可从COM访问的程序集

首先实现一个对COM可见的程序集,创建类库工程,AssemblyInfo.cs应包含:

using System.Runtime.InteropServices;

//使此程序集中的类型对COM组件可见
[assembly: ComVisible(true)]
// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
[assembly: Guid("94882155-3B7C-48e3-B357-234D56D8F15E")]

加入以下代码到AssemblyInfo.cs确保程序集的可访问性:

using System.Security;

[assembly: AllowPartiallyTrustedCallers()]

注意上面的Guid,如果程序集内部的类未标注Guid,COM注册的Guid是会新生成的,此处的Guid没有作用。

创建用户控件(自定义类待测)IdentityKey.cs,加入:

using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace KeyActiveX
{
    [Guid(
"94882155-3B7C-48e3-B357-234D56D8F15E")]
    
public partial class IdentityKey : UserControl
    {
    }
}

这里的Guid和AssemblyInfo.cs一样,它会在COM注册中成为CLSID并被html以clsid调用。

类库工程属性中,选择生成,勾选COM注册,在html文件中加入

<object id="controlbyid" classid="clsid:{94882155-3B7C-48e3-B357-234D56D8F15E}" ></object>

在IE中启用不安全控件,查看html页面,应能访问到控件,现在一个在发布时对COM注册的程序集开发完成了。

使用OLE/COM Object Viewer(安装VC自带)可以在.NET Categories中查看组件和CLSID。

 

(二)通过IE安全控件认证

如果客户机的IE未开启访问非安全标记的ActiveX控件,通过IE浏览上面的步骤开发出的ActiveX控件,发现IE会给出警告:

此页上的 ActiveX 对象可能不安全的。 要允许它将初始化并通过脚本访问吗?

或禁止访问。这是客户机IE的安全规则设置的,我们应该在控件开发上解决IE安全认证的问题。首先我们要了解IE是如何判断一个ActiveX控件是不安全的,参见Microsoft帮助和支持文档:

How Internet Explorer Determines If ActiveX Controls Are Safe

There are two ways to mark a control as safe for scripting and initialization: 

1.Implement the IObjectSafety interface. 

2.Provide the following registry keys for the control's CLSID under the Implemented Categories section:

     a.The following key marks the control safe for scripting:
          {7DD95801-9882-11CF-9FA9-00AA006C42C4}

     b.The following key marks the control safe for initialization from persistent data:
          {7DD95802-9882-11CF-9FA9-00AA006C42C4} 

Microsoft recommends that you implement IObjectSafety to mark a control as safe or unsafe. This prevents other users from repackaging your control and marking it as safe when it is not.

我决定实现IObjectSafety接口来向IE表明ActiveX控件的安全标识,以保证控件再次打包时安全标识不会被被改写。

IObjectSafety是一个COM下的接口,对于C++程序来说,只需要实现它就行了,而.NET之下没有这个接口,在这种情况下,我们的ActiveX控件就是一个不带类型库的COM组件,必须使用C#代码重新定义COM接口。

这里需要了解一点COM的接口知识。接口是COM的核心,它区分了在客户和对象之间使用的契约和实现。COM的接口有三种类型:定制接口÷分派接口和双重接口。.NET Framework使用ComInterfaceType对它进行了重定义:

namespace System.Runtime.InteropServices
{
    
// 摘要:
    
//     Identifies how to expose an interface to COM.
    [Serializable]
    [ComVisible(
true)]
    
public enum ComInterfaceType
    {
        
// 摘要:
        
//     Indicates the interface is exposed to COM as a dual interface, which enables
        
//     both early and late binding. System.Runtime.InteropServices.ComInterfaceType.InterfaceIsDual
        
//     is the default value.
        InterfaceIsDual = 0,
        
//
        
// 摘要:
        
//     Indicates an interface is exposed to COM as an IUnknown -derived interface,
        
//     which enables only early binding.
        InterfaceIsIUnknown = 1,
        
//
        
// 摘要:
        
//     Indicates an interface is exposed to COM as a dispinterface, which enables
        
//     late binding only.
        InterfaceIsIDispatch = 2,
    }
}

关于三个接口的具体描述,可以参考《C#高级编程第三版》28.1.3 接口。

在MSDN上查找,可以知道IObjectSafety继承自IUnknown,是一个定制接口;通过上一章节,可以发现向COM注册时,需要提供一个Guid作为CLSID来标识程序集中的C#类,事实上在COM中,接口和类型库都是带有Guid作为唯一标识的,分别为IID和typelib id。

这样,通过在C#编写的接口标上需要的COM接口IID,就可以在注册是向COM表明接口身份了。在Microsoft帮助上查找IObjectSafety定义:

     [
          uuid(C67830E0
-D11D-11cf-BD80-00AA00575603),
          helpstring(
"VB IObjectSafety Interface"),
          version(
1.0)
      ]
      library IObjectSafetyTLB
      {
          importlib(
"stdole2.tlb");
          [
              uuid(CB5BDC81
-93C1-11cf-8F20-00805F2CD064),
              helpstring(
"IObjectSafety Interface"),
              odl
          ]
          
interface IObjectSafety:IUnknown {
              [helpstring(
"GetInterfaceSafetyOptions")]
              HRESULT GetInterfaceSafetyOptions(
                        [
in]  long  riid,
                        [
in]  long *pdwSupportedOptions,
                        [
in]  long *pdwEnabledOptions);

              [helpstring(
"SetInterfaceSafetyOptions")]
              HRESULT SetInterfaceSafetyOptions(
                        [
in]  long  riid,
                        [
in]  long  dwOptionsSetMask,
                        [
in]  long  dwEnabledOptions);
           }
       }

其中的uuid(CB5BDC81-93C1-11cf-8F20-00805F2CD064)就是需要的接口IID。

使用C#编写IObjectSafety:

using System;
using System.Runtime.InteropServices;

namespace KeyActiveX
{
    [ComImport, Guid(
"CB5BDC81-93C1-11CF-8F20-00805F2CD064")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    
public interface IObjectSafety
    {
        [PreserveSig]
        
void GetInterfacceSafyOptions(
            
int riid,
            
out int pdwSupportedOptions,
            
out int pdwEnabledOptions);

        [PreserveSig]
        
void SetInterfaceSafetyOptions(
            
int riid,
            
int dwOptionsSetMask,
            
int dwEnabledOptions);
    }
}

InterfaceType中一定要使用ComInterfaceType.InterfaceIsIUnknown,因为IObjectSafety继承自IUnkown。

接下来是KeyActiveX的接口实现:

namespace KeyActiveX
{
    [Guid(
"94882155-3B7C-48e3-B357-234D56D8F15E")]
    
public partial class IdentityKey : UserControl, IObjectSafety
    {
        
#region IObjectSafety 成员

        
public void GetInterfacceSafyOptions(int riid, out int pdwSupportedOptions, out int pdwEnabledOptions)
        {
            pdwSupportedOptions 
= 1;
            pdwEnabledOptions 
= 2;
        }

        
public void SetInterfaceSafetyOptions(int riid, int dwOptionsSetMask, int dwEnabledOptions)
        {
            
throw new NotImplementedException();
        }

        
#endregion
    }
}


通过返回一个已定值来告诉IE控件是安全的。具体参见

如何在 VisualBasic 控件实现 IObjectSafety

 

(三)签名发布

C#开发的ActiveX控件发布方式有三种:

  1. 制作客户端安装包,分发给客户机安装;
  2. 制作在线安装包,客户机联机安装;
  3. 使用html中object的codebase指向安装包地址。

前两个比较简单,适合在局域网内实施,生成安装包时需要装Register属性设置为vsdrpCOM;最后一种方式,需要在安装包上进行数字签名,以保证客户机的安全信任。受信任的签名证书应该向证书提供商(如Versign)购买,然后使用签名工具对安装包进行签名。

下面利用Visual Studio 2008自带的测试证书创建工具MakeCert和签名工具SignTool进行测试,首先创建一个带有公司信息的测试证书,在Visual Studio命令提示符后输入:

makecert -sk ABC -n "CN=ABC Corporation" f:\abccorptest.cer

在F盘上创建了测试证书。然后输入

signtool signwizard

在Signing Options页面上,选择Custom,定义证书文件的位置,再下一步选择一个加密算法(MD5或SHA1),指定应用程序的名称和描述URL,确认。

此时ActiveX控件安装包有了一个被标记为未信任的测试证书,需要将IE设置为启用未信任安装程序,在html中引用

<object id="controlbyid" classid="clsid:{94882155-3B7C-48e3-B357-234D56D8F15E}" codebase="setup.exe" ></object>

客户机安装之后就可以使用ActiveX控件了。

posted @ 2008-11-15 03:15 Justina Chen 阅读(3194) | 评论 (20)编辑