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的實現方式.