Atl Control 编程

Atl Control 编程

Last Date: 2015-09-17

Last Date: 2015-10-10

Revision:3
Author: 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]


Qt5传给ActiveX乱码的问题,参考下面的代码段解决:
  	{
		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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kagula086

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值