VC2017下开发ATL程序注意事项

VC2017下开发ATL程序注意事项

kagula

2019-4-29

阅读对象

      早期做过ATL 项目开发的C++程序员.

 环境

     Windows10 Pro, Visual studio 2017/Visual studio 2019, IE11, C++ ATL x86项目.     

正文

    VC2017相对于VC2013对ATL开发不是很方便, 因为不支持为ATL Simple Object自动添加function.

不过手动添加member function也挺简单的, 只要三个步骤就OK了.

  本文应该也适用于从VC2013过渡到VC2019的C++ ATL程序员.

假设你已经通过VC2017的wizard添加了名为ZT410的ATL Simple Object. 

第一步:  在idl文件中定义外部接口

interface IZT410 : IDispatch
{
	HRESULT print([in]BSTR templateName, [in]VARIANT* arrContent, [out, retval]long *result);
	HRESULT sayHello([in]BSTR msg, [out,retval]BSTR *result);
};

通过wizard建立ZT410后,   wizard还会为你自动生成IZT410(接口)代码.

我为这个接口添加了print和sayHello方法, 用来演示ATL参数如何传递.

第二步: ZT410 Class中添加方法定义

打开ZT410.h找到CZT410 class的声明, 在public后面添加print和sayHello的方法声明

public:


	STDMETHODIMP print(BSTR templateName, VARIANT* arrContent, long *result);
	STDMETHODIMP sayHello(BSTR msg, BSTR *result);

第三步(最后一步)

打开ZT410.cpp,  在里面添加这两个member function的实现

STDMETHODIMP CZT410::print(BSTR templateName, VARIANT* arrContent, long *result)
STDMETHODIMP CZT410::sayHello(BSTR msg, BSTR *result)
{
	using namespace std;
	wstring head = L"收到来自JavaScript的信息=>";

	wstring content = head + OLE2W(msg);


	*result = W2BSTR(content.c_str());
	return S_OK;
}

print的实现比较复杂,  暂时不贴出来.

现在我们可以测试了

C#单元测试

新建C#单元测试项目, Unit Test Project(.Net Framework), 

Add Reference -> COM -> Type Libraries

you can see snippet below

        [TestMethod]
        public void TestMethod1()
        {
            NingboHuashuPrinterLib.ZT410 printer = new NingboHuashuPrinterLib.ZT410();

            String[] arrContent = { "head", "中文测试", "tail" };
            int nResult = printer.print("abc", arrContent);
            System.Diagnostics.Trace.WriteLine(nResult);
        }

IE11中JavaScript测试

<BODY>
    <object classid ="clsid:9bc84eed-d69e-4ef6-8e4e-9d0d615ca08c" id="printer" name="printer">
    </object>
        <script language ="JavaScript" type="text/javascript">
            function testSayHelloMethod(){
                var myArray = ["head","中文测试","tail"];
                var a = printer.print("AAA", myArray);

                alert(a);
            }
        </script>
    
    <input type=button value ="测试我们的第一个ATL对象的方法" onclick="testSayHelloMethod()" />

</BODY>

上面的class id指的是coclass ZT410的uuid.

补充

通过JavaScript向ATL传递字符串数组, 可不是件简单的事,  下面贴出C++ ATL代码, 免得同学们东找西找.

依赖的头

#include <list>
#include <vector>
#include <string>
#include <sstream>

using namespace std;

依赖的函数

std::string ws2s(const std::wstring& ws)
{
	std::string curLocale = setlocale(LC_ALL, NULL);        // curLocale = "C";
	setlocale(LC_ALL, "chs");
	const wchar_t* _Source = ws.c_str();
	size_t _Dsize = 2 * ws.size() + 1;
	char *_Dest = new char[_Dsize];
	memset(_Dest, 0, _Dsize);
	wcstombs(_Dest, _Source, _Dsize);
	std::string result = _Dest;
	delete[]_Dest;
	setlocale(LC_ALL, curLocale.c_str());
	return result;
}

CComPtr<IDispatch> VariantToDispatch(__in const CComVariant& var)
{
	if (var.vt == VT_DISPATCH)
	{
		return var.pdispVal;
	}
	return nullptr;
}

bool VariantToInt(CComVariant varIn, int &nOut)
{
	VARTYPE vtype5;
	vtype5 = VT_INT;

	if (varIn.ChangeType(vtype5) == S_OK)
	{
		nOut = varIn.intVal;
		return true;
	}
	return false;
}

bool VariantToArray(__in const CComVariant& var, __out vector<CComVariant>& vecVars)
{
	// convert variant to dispatch object
	CComPtr<IDispatch> pDispatch = VariantToDispatch(var);
	if (!pDispatch)
		return false;

	// get DISPID of length parameter from array object
	LPOLESTR sLengthName = L"length";
	DISPID dispidLength = 0;
	HRESULT hr = pDispatch->GetIDsOfNames(IID_NULL, &sLengthName, 1, LOCALE_USER_DEFAULT, &dispidLength);
	if (FAILED(hr))
		return false;

	// get the number of elements using the DISPID of length parameter
	CComVariant varLength;
	DISPPARAMS dispParams = { 0 };
	hr = pDispatch->Invoke(dispidLength, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, &dispParams, &varLength, NULL, NULL);
	if (FAILED(hr))
		return false;

	int nLength = 0; // length of the array
	bool bGotInt = VariantToInt(varLength, nLength);
	if (!bGotInt)
		return false;

	// get items of array
	for (int i = 0; i < nLength; ++i)
	{
		// get DISPID of item[i] from array object
		wstring strIndex = std::to_wstring(i);
		DISPID dispidIndex = 0;
		LPOLESTR pIndex = reinterpret_cast<LPOLESTR>(const_cast<WCHAR *>(strIndex.data()));
		hr = pDispatch->GetIDsOfNames(IID_NULL, &pIndex, 1, LOCALE_USER_DEFAULT, &dispidIndex);
		if (FAILED(hr))
			continue;

		CComVariant varItem;
		hr = pDispatch->Invoke(dispidIndex, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, &dispParams, &varItem, NULL, NULL);
		if (FAILED(hr))
			continue;

		vecVars.push_back(varItem);
	}

	return true;
}

现在我们调用上面的函数,实现对字符串数组的读取

STDMETHODIMP CZT410::print(BSTR templateName, VARIANT* arrContent, long *result)
{
	//
	string strTemplateName;
	[templateName, &strTemplateName]() {

	}();

	//
	list<string> listContent;
	[arrContent, &listContent]() {
		if (arrContent->vt == (VT_ARRAY | VT_BSTR))
		{
			//CSharp caller branch.

			//retrieve array size
			long *larr;
			long i;
			long lbound, ubound;
			::SafeArrayGetUBound(arrContent->parray, 1, &ubound);//inarr->parray就是safearray,第二个参数是第几维度
			::SafeArrayGetLBound(arrContent->parray, 1, &lbound);
			const long arrSize = ubound - lbound + 1;

#ifdef _DEBUG
			char buffer[64] = { 0 };
			snprintf(buffer, sizeof(buffer), "%d %d", arrSize, arrContent->vt);
			MessageBoxA(NULL, buffer, NULL, 0);
#endif
			//使用SafeArrayAccessData方法, 取出字符串元素, C#中测试通过
			BSTR HUGEP *pData;
			SafeArrayAccessData(arrContent->parray, (void HUGEP* FAR*)&pData);
			for (long i = lbound; i <= ubound; i++)
			{
				BSTR pBSTR = pData[i];
				wstring wsTemp = pBSTR;
				listContent.push_back(ws2s(wsTemp));
			}
			SafeArrayUnaccessData(arrContent->parray);
		}
		else if (arrContent->vt == VT_DISPATCH)
		{
			//JS caller branch.

			CComVariant ccv;
			vector<CComVariant> vecVars;

			ccv.Attach(arrContent);
			VariantToArray(ccv, vecVars);
			ccv.Detach(arrContent);
			for (vector<CComVariant>::iterator iter = vecVars.begin(); 
				iter < vecVars.end(); iter++)
			{
				if (iter->vt == VT_BSTR)
				{
					std::wstring wsTemp = (LPCTSTR)iter->bstrVal;
					listContent.push_back(ws2s(wsTemp));
				}
			}//for
		}//if


		//不知道为什么使用SafeArrayGetElement方法取不出字符串元素.
	}();

#ifdef _DEBUG
	stringstream ss;
	for (list<string>::iterator iter = listContent.begin(); 
		iter != listContent.end(); iter++)
	{
		ss << iter->c_str() << "  ";
	}
#endif

	//对listContent中的字符串进行处理.
	//...ignore...


	//返回listContent中字符串的个数.
	*result = listContent.size();

	return S_OK;
}

注意事项

   上面的ws2s的实现已经过时,   有时间修改为C++11的实现方式.

猜你喜欢

转载自blog.csdn.net/lee353086/article/details/89671186
今日推荐