半冷半暖秋天

2010-08-21

COM组件的编写及调用

Filed under: C/C++ — 标签:, , , — sunu @ 20:01:43

 刚接触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();
}

Powered by WordPress