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對象的方法" οnclick="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的實現方式.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章