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测试
上面的class id指的是coclass ZT410的uuid.
通过JavaScript向ATL传递字符串数组, 可不是件简单的事, 下面贴出C++ ATL代码, 免得同学们东找西找.
依赖的头
#include
#include
#include
#include
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 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& vecVars)
{
// convert variant to dispatch object
CComPtr 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(const_cast(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 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 vecVars;
ccv.Attach(arrContent);
VariantToArray(ccv, vecVars);
ccv.Detach(arrContent);
for (vector::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::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的实现方式.