刚接触COM组件,一直理不清ActiveX、Automation、COM组件、OLE之间啥关系,感性的认识就是最终生成的OCX/DLL/EXE中的代码可以很方便地被不同的语言不同场合调用。 MFC、ATL、Delphi都可以开发COM组件,其中MFC和Delphi相对简单些。 VC、Delphi、.Net(C#/VB等)、VBScript、Javascript都可以调用。
一、COM组件的编写
1.1 MFC
假设使用的是Visual studio 2008
首先要创建一个MFC DLL或者ActiveX工程。在工程中添加“MFC类”,在“MFC类向导”中,“基类”选择“CCmdTarget”,“自动化”选中中一定要选择“可按类型创建”,比如填入“TestActiveX.HelloWorld”,这样就生成了HelloWorld.cpp和HelloWorld.h(假如类名取为HelloWorld的话)。
打开IDE“视图”“类视图”,找到TextActiveX下的IHelloWorld接口,右键“添加”,可选添加方法和属性。添加方法(比如sayHello)时,需要设置“返回类型”,方法名,以及此方法的参数(假如设置了BSTR的visitor)。至于类型BSTR,BSTR*,IDISPATCH这些需要另外再去研究… 因为MFC封装的缘故,生成的HelloWord类的成员函数sayHello的参数为LPCTSTR(即在UNICODE条件编译下的const wchar_t*及多字节下的const char*)类型。最后只要在sayHello函数中编写代码即可。最后的函数看起像:
BSTR HelloWorld::sayHello(LPCTSTR visitor)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
CString strResult;
strResult.Format(TEXT("你好啊,%s!"), visitor);
MessageBox(0, strResult.GetBuffer(), TEXT("TestActiveX.HelloWorld.sayHello"), 0);
return strResult.AllocSysString();
}
如此一来,TestActiveX.HelloWorld就有了一个名为sayHello的函数可用了。
1.2 Delphi
假设使用的是Delphi 2010
Delphi编写也挺方便,就是操作步骤有些恼人。IDE “File”, “New”,”Other”,选择”Active Library”,这个时候其它的图标都是灰色的,表示这些内容的新建依赖于先建立某种工程。这时界面会切换到Project1.irdl编辑,修改name为TestActiveX。”File”, “Save All”保存TestActiveX.irdl和TestActiveX_TLB.pas及TestActiveX.dproj。
此时已建立一个输出了DllRegisterServer等函数的Dll,接着来添加自动化接口(即TestActiveX.HelloWorld)。IDE “File”, “New”,”Other”,选择”Automation Object”,这时会看到所有图标已显示亮色,表示可以在当前工程中插入这些文件了。在”Automation Object Wizard”界面中,将“coClass”取名为HelloWorld,“Description”无所谓,”Threading Mode”默认为”Apartment”,”Instancing”为“Multiple Instance”。确定后,将自动改写TestActiveX.irdl和TestActiveX_TLB.pas等。
在TestActiveX.irdl编辑界面,右键“IHelloWorld”(红色的棒槌),“New”, “Method”,取名为sayHello。接下来切换到”sayHello”,”Parameters”,保持Return Type为HRESULT不变,在表格“parameters”中编辑sayHello的参数和返回值。 (这里要留意,say的返回值实际是在”parameters”参数列表中定义的,看起来真奇怪.) 这里添加一个参数为visitor BSTR [in],另外一个返回值为psReturn BSTR* [out, retVal],即如果返回值是BSTR的,则类型需写为BSTR*,而“Modifier”必须为[out,retVal]。
然后保存一下,会提示保存HelloWorld.pas。以后每添加完一个方法或者属性,都要点击irdl编辑界面顶部一行图标中的“Refresh Implementation”,Delphi会自动在HelloWorld.pas中为你编写此方法或者属性相对应的构架代码,否则你都要自己对照着irdl手工辛苦地编写,何必呢…
HelloWorld.pas即是所以功能实现的代码了,以后只要在这里面好好维护每个函数即可。看起来像:
unit HelloWorld;
{$WARN SYMBOL_PLATFORM OFF}
interface
uses
ComObj, ActiveX, TestActiveX_TLB, StdVcl;
type
THelloWorld = class(TAutoObject, IHelloWorld)
protected
function sayHello(const visitor: WideString): WideString; safecall;
end;
implementation
uses ComServ;
function THelloWorld.sayHello(const visitor: WideString): WideString;
begin
Result := '你好啊, '+visitor+'!';
end;
initialization
TAutoObjectFactory.Create(ComServer, THelloWorld, Class_HelloWorld,
ciMultiInstance, tmApartment);
end.
二、COM组件的调用
以下所有调用方法都需要注册控件:使用regsvr32 /q TestActiveX.dll注册组件(其实就是调用了TestActiveX.dll中导出的函数DllRegisterServer)。
2.1 Javascript(cscript.exe)
估计这是测试COM组件相对方便的方式了。, 然后将以下内容保存为Helloword.js, 在命令行下执行cscript.exe Helloword.js
var test = {
startTest:function(){
var STDOUT = WScript.StdOut;
var STDERR = WScript.StdErr;
var ActiveX = null;
try{
ActiveX = WScript.CreateObject("TestActiveX.HelloWorld");
}
catch(e){
STDOUT.write("TestActiveX 未注册!\n");
return;
}
try{
var retStr = ActiveX.sayHello("sunu");
STDOUT.Write("sayHello="+retStr);
}
catch(e){
STDOUT.write("TestActiveX.HelloWorld 中无sayHello方法!\n");
}
}
};
test.startTest();
2.2 Delphi
假设使用的是Delphi 2010
IDE菜单 “Component” => “Import Component” => “Import a Type Library” => 在列表中找到TestActiveX或者通过Add找到TestActiveX.dll => 一定要选上“Generate Component Wrapper”(这样Delphi会自动生成相应的类,否则需要自己编写) => “Add Unit to …” 完成。这样就生成了一个TestActiveX_TLB.pas单元。调用代码如下:
procedure TForm1.Button1Click(Sender: TObject);
var
helloObj:THelloWorld;
begin
try
helloObj := THelloWorld.Create(self);
try
helloObj.sayHello('sunu');
finally
freeAndNil(helloObj);
end;
except on E:Exception do
showMessage('Error:'+E.Message);
end;
end;
2.3 C#
假设使用的是Visual studio 2008
在C#工程左边“解决方案管理器”中“引用”上右键“添加引用”,在对话框中选择“COM”项,找到TestActiveX(除非未注册控件,否则一定存在)项。调用代码如下:
//using TestActiveX;
private void button1_Click(object sender, EventArgs e){
try {
HelloWorldClass helloObj = new HelloWorldClass();
helloObj.sayHello("sunu");
} catch{
MessageBox.Show("Error!");
}
}
编译后,除主程序外,会生成一个Interop.TestActiveX.dll必须和主程序一同发布。另外,在主程序的Program.cs的Main函数中,最好加一层try保护,以在控件未注册的时候程序崩溃得友好一些。
static void Main(){
try {
//Application....省略
}catch{
MessageBox.Show("请行注册控件TestActiveX.dll!", "程序崩溃了!",
MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
}
2.4 MFC
假设使用的是Visual studio 2008
创建MFC工程时加上ActiveX控件支持。左边“解决方案管理器”工程上右键“添加” => “Typelib 中的MFC类”,在“可用的类型库”中找到TestActiveX,“接口”中出现了唯一一个接口“IHelloWorld”,将其添加到“生成的类”中去。这样就生成了CHelloWorld.h文件,其中已经包含类的声明和实现(其实只是个代理)。调用代码如下:
void CTestHelloworld_MFCDlg::OnBnClickedButton1()
{
CoInitialize(NULL);
CHelloWorld helloObj;
if (helloObj.CreateDispatch(TEXT("TestActiveX.HelloWorld"))!=0){
helloObj.sayHello(TEXT("sunu"));
//如果CoUninitialize在其它地方调用,则不需要以下一句
//否则会在helloObj构析的时候因CoUninitialize调用使得COM组件已不可用而出错
helloObj.ReleaseDispatch();
}
CoUninitialize();
}