Last Date: 2015-09-17
Last Date: 2015-10-10
Revision:3Author: Kagula
准备:
[1]因为VS2010是最后一个针对WinXP的IDE,所以使用VS2010 SP1。
[2]参考资料[1]安装ActiveX Control Test Container。
因为"MFC Dialog"的“Insert Active X”可能会看不到我们新建的ATL Control所以
我们需要“ctiveX Control Test Container”来测试我们的程序。
第一部份:先有个概念
参考资料[2]先对Atl Control的流程有个概念。
这里需要注意的是:
[1]添加"ATL Control"要求输入short name时,建议起的名字前不要加字母“C”,如果别人不用uuid
的话,就是用这个short name来引用你的ATL Control class。
[2]比如新添加了short name为“MyAtlControl2”类,通过idl文件你可以找到它的uuid值。
通过这个值你可以在其它语言中引用这个class。
下面是Qt5.4中如何引用的参考代码:
//#include <QAxObject>
//#include <QDebug>
QAxObject *_rtxObject = new QAxObject();
if (!_rtxObject->setControl("{1AC261EA-1D71-4EDA-89AC-E9677EE1F341}"))
{
qDebug() << "加载ATL Control对象失败。";
return -1;
}
QVariantList params;
params << 3 << 5;
//flash->dynamicCall("LoadMovie(long,string)",0,"d:/b.swf");
QString qtResult = _rtxObject->dynamicCall("Add(int, int)", params).toString();
qDebug() << "3+5=" << qtResult;
可以看到uuid值作为参数传给了setControl函数。
Add是我在“MyAtlControl2”类中添加的方法。
[2]如何在C#中引用?
如果你的ATL项目名称为“ KagulaTestATL”,你要引用的类为“ MyATLSimpleObject”
(就是你新建ATL Simple Object或ATL Control 时输入的name),
可以用下面的C# snippet来引用。
KagulaTestATLLib. MyATLSimpleObject simpleObj = new KagulaTestATLLib. MyATLSimpleObject();
Console.WriteLine(simpleObj.Add(3,2));
第二部份:无界面控件
新建ATL Control时在默认[ATL Control Wizard]中点选[Appearance]->[Invisible at runtime]
这样界面只在设计器中被看到。
第三部份:COM组件如何回调client方法的示例!
方法一:参考资料[7]《Adding an Event (ATL Tutorial, Part 5》IDE会为你自动添加事件接口代码
这里要注意下面两个步骤,因为容易疏漏所以这里在写一遍。
[a]在Class View下展开XXXLib后,右键单击_IXXXEvents添加method,实现在idl文件中添加method.
假设method name为“eventNotify”
[b]在Class View下,右键单击CXXX,选择“Add Connection Point...”,建立“eventNotify”的实现“Fire_eventNotify”。
方法二:(不推荐,因为,手动添加代码容易出错)
参考了资料[3],但是其中两步步骤又有不同,下面是具体我的步骤。开发环境暂时改为VS2013Update5,但是也应该适用于VS2010. 如果想要被Qt5调用,就把“ATL Control”替换为“ATL Simple Object”。
Step1: 添加了ATL Simple Object并起名为“MySimpleATL”,记得选中“Connection Points”选项。
这个选项会让Wizard新建_IMySimpleATLEvents对象,
Step2: 在ClassView中,右键单击_IMySimpleEvents添加method,只带一个参数,在wizard的listbox中
显示为“LONG op”。
Step3: 修改idl文件,修改后的代码片段如下:
dispinterface _IMySimpleATLEvents
{
properties:
methods:
[id(1)] HRESULT MyEvent(LONG op);
};
其中“MyEvent”这行代码是我手动添加的。
Step4:右键单击“CMySimpleATL”Add Connection Point.
Step5:通过修改_IMySimpleATLEvents_CP.h文件,手动添加Fire_MyEvent函数。
修改后的源码清单如下
#pragma once
template<class T>
class CProxy_IMySimpleATLEvents :
public ATL::IConnectionPointImpl<T, &__uuidof(_IMySimpleATLEvents)>
{
public:
HRESULT Fire_MyEvent(LONG op)
{
HRESULT hr = S_OK;
T * pThis = static_cast<T *>(this);
int cConnections = m_vec.GetSize();
for (int iConnection = 0; iConnection < cConnections; iConnection++)
{
pThis->Lock();
CComPtr<IUnknown> punkConnection = m_vec.GetAt(iConnection);
pThis->Unlock();
IDispatch * pConnection = static_cast<IDispatch *>(punkConnection.p);
if (pConnection)
{
CComVariant avarParams[1];
avarParams[0] = op;
avarParams[0].vt = VT_I4;
CComVariant varResult;
DISPPARAMS params = { avarParams, NULL, 1, 0 };
hr = pConnection->Invoke(1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, ¶ms, &varResult, NULL, NULL);
}
}
return hr;
}
};
Step5:新建一个C# Console工程,现在可以测试并使用了。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TestATLControl
{
class Program
{
void MyCallback(int op)
{
Console.WriteLine(op);
}
static void Main(string[] args)
{
Program program = new Program();
TestATLCallbackLib.MySimpleATL obj = new TestATLCallbackLib.MySimpleATL();
obj.MyEvent += program.MyCallback;
obj.TestCallback(123);
}
}
}
第四部分:自定义数据类型
如何自定义数据类型?
第一步:修改idl文件,在library关键词的外面加入下面的代码
typedef
[
uuid(C21871AF-33EB-11D4-A13A-BE2573A1120F),
version(1.0),
helpstring("A Demo UDT variable for CSharp projects")
]
struct UDTVariable {
[helpstring("Special case variant")] VARIANT Special;
[helpstring("Name of the variable")] BSTR Name;
[helpstring("Value of the variable")] long Value;
} UDTVariable;
typedef
[
uuid(C21871FF-33EB-11D4-A13A-BE2573A1120F),
version(1.0),
helpstring("A Demo UDT Holding an Array of Named Variables")
]
struct UDTArray {
[helpstring("array of named variables")]
SAFEARRAY(UDTVariable) NamedVars;
} UDTArray;
第二步:修改idl文件,在在 library关键词里面加入下面的代码
struct UDTVariable;
struct UDTArray;
这样C#能找到这些定义。
第三步:IDE可能不知道你已经在idl文件中添加了新的数据类型,
方式一:
添加method时,可能不会显示自定义数据类型,你可以手动输入它(UDTVariable)。
方式二(不推荐):
可以添加method后,再修改它参数的数据类型。idl文件和C源代码文件都要修改。
可以先修改idl中的,demo如下
[id(2)] HRESULT GetUDT([in] UDTVariable* input, [out,retval] UDTVariable* retVal);
再修改C源代码。
最后一步:
实现方法后就可以在C#中使用它们了,demo snippet如下
KagulaAxForTestLib.UDTVariable udtv = new KagulaAxForTestLib.UDTVariable();
udtv.Name = "0123456789abc";
udtv.Value = 123;
Console.WriteLine(udtv.Name);
Console.WriteLine("10 times: "+he.GetUDT(ref udtv).Value);
具体参考资料[9]
如何使用带自定义数据类型的成员变量(属性)?
参考资料[9]关于如何新建和使用“UdtVar”属性的描述。
如何使用带自定义数据类型的数组?
可以参考资料[9],但是在vs2010 sp1中要重点注意以下几个细节
[1]add UDTSequence method的时候,向导里你输入“SAFEARRAY(UDTVariable) *”会变成“SAFEARRAY(UDTVariable)”
只能添加后在idl文件中手动把 “*”加上。
[2]由于IDE不认识“SAFEARRAY(UDTVariable)”类型,所以在头文件中会变成
STDMETHOD(UDTSequence)(LONG start, LONG length, SAFEARRAY * SequenceArr);
但是这里也相应少个 “*”,把它补上,补上 “*”后的代码如下:
STDMETHOD(UDTSequence)(LONG start, LONG length, SAFEARRAY ** SequenceArr);
相应的cpp文件,形参的声明也要做修改。
现在你可以成功编译它了。
[3]为了能够添加自定义数据类型的数组,在cpp文件中添加自定义数据类型的声明,
UDTVariable变量的ID从idl文件中复制过来,只是表现形式不一样,源码如下:
const IID UDTVariable_IID = { 0xC21871AF,
0x33EB,
0x11D4,
{
0xA1,
0x3A,
0xBE,
0x25,
0x73,
0xA1,
0x12,
0x0F
}
};
现在你可以参考资料[9],新建数组,我的cpp清单如下。
// KagulaHavingEvent.cpp : Implementation of CKagulaHavingEvent
#include "stdafx.h"
#include "KagulaHavingEvent.h"
//在这里定义UDTVariable_IID仅仅是为了创建数组用。
const IID UDTVariable_IID = { 0xC21871AF,
0x33EB,
0x11D4,
{
0xA1,
0x3A,
0xBE,
0x25,
0x73,
0xA1,
0x12,
0x0F
}
};
// CKagulaHavingEvent
STDMETHODIMP CKagulaHavingEvent::IndirectCall(BSTR cstrMsg, LONG* retVal)
{
Fire_eventNotify(cstrMsg,retVal);
return S_OK;
}
STDMETHODIMP CKagulaHavingEvent::GetUDT(UDTVariable* input, UDTVariable* retVal)
{
retVal->Value = input->Value*10;
return S_OK;
}
STDMETHODIMP CKagulaHavingEvent::UDTSequence(LONG start, LONG length, SAFEARRAY ** SequenceArr)
{
if( !SequenceArr )
return( E_POINTER );
if( length <= 0 ) {
HRESULT hr = Error( _T("Length must be greater than zero") );
return( hr );
}
if( *SequenceArr != NULL ) {
::SafeArrayDestroy( *SequenceArr );
*SequenceArr = NULL;
}
//
//here starts the actual creation of the array
//
IRecordInfo *pUdtRecordInfo = NULL;
HRESULT hr = GetRecordInfoFromGuids( LIBID_KagulaAxForTestLib,
1, 0,
0,
UDTVariable_IID,
&pUdtRecordInfo );
if( FAILED( hr ) ) {
HRESULT hr2 = Error( _T("Can not create RecordInfo interface for UDTVariable") );
return( hr ); //Return original HRESULT hr2 is for debug only
}
SAFEARRAYBOUND rgsabound[1];
rgsabound[0].lLbound = 0;
rgsabound[0].cElements =length;
*SequenceArr = ::SafeArrayCreateEx( VT_RECORD, 1, rgsabound, pUdtRecordInfo );
pUdtRecordInfo->Release(); //do not forget to release the interface
if( *SequenceArr == NULL ) {
HRESULT hr = Error( _T("Can not create array of UDTVariable structures") );
return( hr );
}
//
//the array has been created
//
SequenceByElement(start, length, *SequenceArr );
return S_OK;
}
HRESULT CKagulaHavingEvent::SequenceByElement( long start, long length, SAFEARRAY *SequenceArr )
{
long lBound = 0;
VARIANT a_variant;
UDTVariable a_udt;//在idl文件中添加UDTVariable结构定义后,build idl文件后,这里就可以用了。
HRESULT hr = SafeArrayGetLBound( SequenceArr, 1, &lBound );
if( FAILED( hr ) ) return( hr );
BSTR strDefPart = ::SysAllocString( L"Named " );
::VariantInit( &a_variant );
for( long i = lBound; i<length; i++, start++ ) {
//Value字段
a_udt.Value = start; //i holds the sequence value
//Special字段
if( i & 1 ) {
a_udt.Special.vt = VT_R8;
a_udt.Special.dblVal = double( start );
a_udt.Special.dblVal += 0.5;
} else {
a_udt.Special.vt = VT_I4;
a_udt.Special.lVal = start;
}
//Name字段
hr = ::VariantChangeType( &a_variant, &a_udt.Special, 0, VT_BSTR );
hr = ::VarBstrCat( strDefPart, a_variant.bstrVal, &a_udt.Name );
//放到数值里
hr = ::SafeArrayPutElement( SequenceArr, &i, (void*)&a_udt );
if( FAILED(hr) )
return( hr );
::VariantClear( &a_variant ); //frees the Name string
}
::SysFreeString( strDefPart );
return S_OK;
}
[4]在C#中你可以这样使用
Array array = he.UDTSequence(0, 9);
foreach (KagulaAxForTestLib.UDTVariable item in array)
{
Console.WriteLine("item.Name=[" + item.Name + "],item.Value=[" + item.Value + "],item.Special=[" + item.Special + "]");
}
如何在MFC App中以最简单的方式调用Com服务?
Step1: 对于不是完整的ActiveX控件,打开class view。
[Add Class From Typelib]->[Add class from Registry]->[Available type libraries]
选择Cat8637AxLib,Interfaces中选择你想要导入的interface。
Last Step:
加入新建的头文件引用"CKagulaLogger.h"
void CTestAxInMFCDlg::OnBnClickedBtnLog()
{
CKagulaLogger *logger = new CKagulaLogger();
//下面的参数来自于idl文件KagulaLogger的uuid,虽然我们在Typelib class wizard中选择的是IKagulaLogger
if (logger->CreateDispatch(L"{0648AEB4-CE9A-4FBE-BFB2-60E79F51CFD3}"))
{
logger->Init(L"d:\\a.log", L"d:\\a.bak.log", 1024 * 8);
logger->Trace(L"来自C++ com client的中文信息");
logger->Info(L"this is trace info");
logger->Error(L"this is error info");
logger->ReleaseDispatch();
}
delete logger;
}
如何调用ATL Dialog? 参考资料[3]这里摘录了一段代码:
STDMETHODIMP CSimpleATLDLGController::InvokeDialog(void)
{
CSimpleATLDialog atlDLg;
atlDLg.DoModal();
return S_OK;
}
如何响应外界的消息,参考资料[7]
{
QVariantList params;
params << QStringLiteral("ss这是来自QT5的信息");
_rtxObject->dynamicCall("Trace(string)", params);
}
{
QVariantList params;
QString qsStr = QString::fromLocal8Bit("中文测试");
params << qsStr;
_rtxObject->dynamicCall("Trace(string)", params);
}
[4]使用ConvertBSTRToString内存泄露问题
//使用_com_util::ConvertBSTRToString转数据类型,需要把临时指针delete掉,否则会内存泄露。
//BSTR level
//#include <comutil.h>
//#pragma comment(lib, "comsuppw.lib")
char *p =_com_util::ConvertBSTRToString(level);
string sLevel = p;
delete p;
参考资料
[1]《VS2010添加TSTCON( ACTIVEX CONTROL TEST CONTAINER )工具》
http://www.cnblogs.com/flying-roc/archive/2012/06/26/2563648.html
[2]《How to develop and deploy ActiveX Control using ATL》
http://blogs.msdn.com/b/asiatech/archive/2012/02/01/how-to-develop-and-deploy-activex-control-using-atl.aspx
[3]《A Beginner Tutorial for Writing Simple COM/ATL DLL For VS2012》
http://www.codeproject.com/Articles/505791/Writing-Simple-COM-ATL-DLL-for-VS
[4]《ATL Wizards and Dialog Boxes》
https://msdn.microsoft.com/en-us/library/jj154138.aspx
[5]《How to correctly access ATL control from ATL Dialog?》
http://stackoverflow.com/questions/16441811/how-to-correctly-access-atl-control-from-atl-dialog
[6]《在Qt中使用ActiveX控件》
http://blog.csdn.net/tingsking18/article/details/5403038
[7]《 Adding an Event (ATL Tutorial, Part 5)》
https://msdn.microsoft.com/en-us/library/9h7xedd1.aspx
[8]《Understanding COM Event Handling》
http://www.codeproject.com/Articles/9014/Understanding-COM-Event-Handling
[9]《 Using User Defined Types in COM & ATL》
http://www.codeproject.com/Articles/916/Using-User-Defined-Types-in-COM-ATL
[10]《Qt 5.5 > Active Qt > QAxBase》
http://doc.qt.io/qt-5/qaxbase.html
[11]《 Ax同Qt之间如何互换数组?》
http://stackoverflow.com/questions/16665627/qt-activex-data-types