lvDCOM
An EPICS support module to export LabVIEW values as process variables
lvDCOMInterface.cpp
Go to the documentation of this file.
1 /*************************************************************************\
2 * Copyright (c) 2013 Science and Technology Facilities Council (STFC), GB.
3 * All rights reverved.
4 * This file is distributed subject to a Software License Agreement found
5 * in the file LICENSE.txt that is included with this distribution.
6 \*************************************************************************/
7 
10 
16 
21 
28 
31 
35 
38 
41 
44 
47 
48 #include <stdio.h>
49 
50 //#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
51 #include <windows.h>
52 
53 #define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // some CString constructors will be explicit
54 #include <atlbase.h>
55 #include <atlstr.h>
56 #include <atlcom.h>
57 #include <atlwin.h>
58 #include <atltypes.h>
59 #include <atlctl.h>
60 #include <atlhost.h>
61 #include <atlconv.h>
62 #include <atlsafe.h>
63 #include <comdef.h>
64 
65 #include <string>
66 #include <vector>
67 #include <map>
68 #include <list>
69 #include <stdexcept>
70 #include <sstream>
71 #include <fstream>
72 #include <iostream>
73 #include <algorithm>
74 
75 #include "lvDCOMInterface.h"
76 #include "variant_utils.h"
77 
78 #include <macLib.h>
79 #include <epicsGuard.h>
80 
81 #define MAX_PATH_LEN 256
82 
83 static epicsThreadOnceId onceId = EPICS_THREAD_ONCE_INIT;
84 
86 static void __stdcall my_com_raise_error(HRESULT hr, IErrorInfo* perrinfo)
87 {
88  _com_error com_error(hr, perrinfo);
89  // std::string message = "(" + com_error.Source() + ") " + com_error.Description();
90  std::string message = com_error.Description(); // for LabVIEW generated messages, Description() already includes Source()
91  throw COMexception(message, hr);
92 }
93 
94 static void initCOM(void*)
95 {
96  CoInitializeEx(NULL, COINIT_MULTITHREADED);
97  _set_com_error_handler(my_com_raise_error); // replace default _com_raise_error
98 }
99 
100 // return "" if no value at path
101 std::string lvDCOMInterface::doXPATH(const std::string& xpath)
102 {
103  if (m_pxmldom == NULL)
104  {
105  throw std::runtime_error("m_pxmldom is NULL");
106  }
107  epicsGuard<epicsMutex> _lock(m_lock);
108  std::map<std::string,std::string>::const_iterator it = m_xpath_map.find(xpath);
109  if (it != m_xpath_map.end())
110  {
111  return it->second;
112  }
113  IXMLDOMNode *pNode = NULL;
114  std::string S_res;
115  BSTR bstrValue = NULL;
116  HRESULT hr = m_pxmldom->selectSingleNode(_bstr_t(xpath.c_str()), &pNode);
117  if (SUCCEEDED(hr) && pNode != NULL)
118  {
119  hr=pNode->get_text(&bstrValue);
120  if (SUCCEEDED(hr))
121  {
122  S_res = CW2CT(bstrValue);
123  SysFreeString(bstrValue);
124  }
125  pNode->Release();
126  }
127  // else
128  // {
129  // throw std::runtime_error("doXPATH: cannot find " + xpath);
130  // }
131  m_xpath_map[xpath] = S_res;
132  return S_res;
133 }
134 
135 bool lvDCOMInterface::doXPATHbool(const std::string& xpath)
136 {
137  if (m_pxmldom == NULL)
138  {
139  throw std::runtime_error("m_pxmldom is NULL");
140  }
141  epicsGuard<epicsMutex> _lock(m_lock);
142  std::map<std::string,bool>::const_iterator it = m_xpath_bool_map.find(xpath);
143  if (it != m_xpath_bool_map.end())
144  {
145  return it->second;
146  }
147  IXMLDOMNode *pNode = NULL;
148  bool res = false;
149  BSTR bstrValue = NULL;
150  std::string bool_str;
151  HRESULT hr = m_pxmldom->selectSingleNode(_bstr_t(xpath.c_str()), &pNode);
152  if (SUCCEEDED(hr) && pNode != NULL)
153  {
154  hr=pNode->get_text(&bstrValue);
155  if (SUCCEEDED(hr))
156  {
157  bool_str = CW2CT(bstrValue);
158  if (bool_str.size() == 0)
159  {
160  res = false;
161  }
162  // allow true / yes / non_zero_number
163  // note: atol() returns 0 for non numeric strings, so OK in a test for "true"
164  else if ( (bool_str[0] == 't') || (bool_str[0] == 'T') || (bool_str[0] == 'y') || (bool_str[0] == 'Y') || (atol(bool_str.c_str()) != 0) )
165  {
166  res = true;
167  }
168  else
169  {
170  res = false;
171  }
172  SysFreeString(bstrValue);
173  }
174  pNode->Release();
175  }
176  // else
177  // {
178  // throw std::runtime_error("doXPATHbool: cannot find " + xpath);
179  // }
180  m_xpath_bool_map[xpath] = res;
181  return res;
182 }
183 
184 #if 0
185 
186 std::string lvDCOMInterface::doXPATH_old(const std::string& xpath)
187 {
188  if (m_doc == NULL)
189  {
190  throw std::runtime_error("m_cfg is NULL");
191  }
192  std::map<std::string,std::string>::const_iterator it = m_xpath_map.find(xpath);
193  if (it != m_xpath_map.end())
194  {
195  return it->second;
196  }
197  m_lock.lock();
198  TinyXPath::xpath_processor xp_proc(m_root, xpath.c_str());
199  // TIXML_STRING S_res = TinyXPath::S_xpath_string(m_doc->RootElement(), xpath.c_str());
200  std::string S_res = xp_proc.S_compute_xpath().c_str();
201  m_xpath_map[xpath] = S_res;
202  m_lock.unlock();
203  return S_res;
204 }
205 
206 bool lvDCOMInterface::doXPATHbool_old(const std::string& xpath)
207 {
208  if (m_doc == NULL)
209  {
210  throw std::runtime_error("m_cfg is NULL");
211  }
212  std::map<std::string,bool>::const_iterator it = m_xpath_bool_map.find(xpath);
213  if (it != m_xpath_bool_map.end())
214  {
215  return it->second;
216  }
217  m_lock.lock();
218  TinyXPath::xpath_processor xp_proc(m_root, xpath.c_str());
219  bool res = xp_proc.o_compute_xpath();
220  m_xpath_bool_map[xpath] = res;
221  m_lock.unlock();
222  return res;
223 }
224 
225 #endif /* #if 0 */
226 
227 std::string lvDCOMInterface::doPath(const std::string& xpath)
228 {
229  std::string S_res = doXPATH(xpath);
230  char* exp_str = macEnvExpand(S_res.c_str());
231  S_res = exp_str;
232  free(exp_str);
233  std::replace(S_res.begin(), S_res.end(), '/', '\\');
234  return S_res;
235 }
236 
238 {
239  m_pxmldom = NULL;
240  HRESULT hr=CoCreateInstance(CLSID_DOMDocument, NULL, CLSCTX_SERVER,
241  IID_IXMLDOMDocument2, (void**)&m_pxmldom);
242  if (FAILED(hr))
243  {
244  throw std::runtime_error("Cannot load DomFromCom");
245  }
246  if (m_pxmldom != NULL)
247  {
248  m_pxmldom->put_async(VARIANT_FALSE);
249  m_pxmldom->put_validateOnParse(VARIANT_FALSE);
250  m_pxmldom->put_resolveExternals(VARIANT_FALSE);
251  }
252  else
253  {
254  throw std::runtime_error("Cannot load DomFromCom");
255  }
256 }
257 
258 
266 lvDCOMInterface::lvDCOMInterface(const char *configSection, const char* configFile, const char* host, int options, const char* progid, const char* username, const char* password) :
267 m_configSection(configSection), m_pidentity(NULL), m_pxmldom(NULL), m_options(options),
268  m_progid(progid != NULL? progid : ""), m_username(username != NULL? username : ""), m_password(password != NULL ? password : "")
269 
270 {
271  epicsThreadOnce(&onceId, initCOM, NULL);
272  if (host != NULL && host[0] != '\0')
273  {
274  m_host = host;
275  }
276  else
277  {
278  // char name_buffer[MAX_COMPUTERNAME_LENGTH + 1];
279  // DWORD name_size = MAX_COMPUTERNAME_LENGTH + 1;
280  // if ( GetComputerNameEx(ComputerNameNetBIOS, name_buffer, &name_size) != 0 )
281  // {
282  // m_host = name_buffer;
283  // }
284  // else
285  // {
286  // m_host = "localhost";
287  // }
288  m_host = "localhost";
289  }
290  // m_doc = new TiXmlDocument;
291  // if ( !m_doc->LoadFile(configFile) )
292  // {
293  // delete m_doc;
294  // m_doc = NULL;
295  // throw std::runtime_error("Cannot load " + std::string(configFile) + ": load failure");
296  // }
297  // m_root = m_doc->RootElement();
298  DomFromCOM();
299  short sResult = FALSE;
300  char* configFile_expanded = macEnvExpand(configFile);
301  m_configFile = configFile_expanded;
302  HRESULT hr = m_pxmldom->load(_variant_t(configFile_expanded), &sResult);
303  free(configFile_expanded);
304  if(FAILED(hr))
305  {
306  throw std::runtime_error("Cannot load \"" + m_configFile + "\" (expanded from \"" + std::string(configFile) + "\"): load failure");
307  }
308  if (sResult != VARIANT_TRUE)
309  {
310  throw std::runtime_error("Cannot load \"" + m_configFile + "\" (expanded from \"" + std::string(configFile) + "\"): load failure");
311  }
312  std::cerr << "Loaded config file \"" << m_configFile << "\" (expanded from \"" << configFile << "\")" << std::endl;
313  m_extint = doPath("/lvinput/extint/@path").c_str();
314  epicsAtExit(epicsExitFunc, this);
315  if (m_progid.size() > 0)
316  {
317  if ( CLSIDFromProgID(CT2W(m_progid.c_str()), &m_clsid) != S_OK )
318  {
319  throw std::runtime_error("Cannot find progId " + m_progid);
320  }
321  }
322  else
323  {
324  m_clsid = LabVIEW::CLSID_Application;
325  wchar_t* progid_str = NULL;
326  if ( ProgIDFromCLSID(m_clsid, &progid_str) == S_OK )
327  {
328  m_progid = CW2CT(progid_str);
329  CoTaskMemFree(progid_str);
330  }
331  else
332  {
333  m_progid = "LabVIEW.Application";
334  }
335  }
336  wchar_t* clsid_str = NULL;
337  if ( StringFromCLSID(m_clsid, &clsid_str) == S_OK )
338  {
339  std::cerr << "Using ProgID \"" << m_progid << "\" CLSID " << CW2CT(clsid_str) << std::endl;
340  CoTaskMemFree(clsid_str);
341  }
342  else
343  {
344  std::cerr << "Using ProgID \"" << m_progid << "\" but StringFromCLSID() failed" << std::endl;
345  }
346 }
347 
349 {
350  lvDCOMInterface* dcomint = static_cast<lvDCOMInterface*>(arg);
351  if (dcomint == NULL)
352  {
353  return;
354  }
355  if ( dcomint->checkOption(viAlwaysStopOnExit) )
356  {
357  dcomint->stopVis(false);
358  }
359  else if ( dcomint->checkOption(viStopOnExitIfStarted) )
360  {
361  dcomint->stopVis(true);
362  }
363 }
364 
365 void lvDCOMInterface::stopVis(bool only_ones_we_started)
366 {
367  for(vi_map_t::const_iterator it = m_vimap.begin(); it != m_vimap.end(); ++it)
368  {
369  LabVIEW::VirtualInstrumentPtr vi_ref = it->second.vi_ref;
370  if ( (!only_ones_we_started || it->second.started) && (vi_ref != NULL) )
371  {
372  if (vi_ref->ExecState != LabVIEW::eIdle) // don't try to stop it if it is already stopped
373  {
374  std::cerr << "stopping \"" << CW2CT(it->first.c_str()) << "\" as it was auto-started and is still running" << std::endl;
375  try
376  {
377  vi_ref->Abort();
378  }
379  catch(const std::exception& ex)
380  {
381  std::cerr << "error stopping vi: " << ex.what() << std::endl;
382  }
383  catch(...)
384  {
385  std::cerr << "error stopping vi: unknown" << std::endl;
386  }
387  }
388  }
389  }
390 }
391 
393 {
394  long n = 0;
395  char control_name_xpath[MAX_PATH_LEN];
396  _snprintf(control_name_xpath, sizeof(control_name_xpath), "/lvinput/section[@name='%s']/vi/param", m_configSection.c_str());
397  IXMLDOMNodeList* pXMLDomNodeList = NULL;
398  HRESULT hr = m_pxmldom->selectNodes(_bstr_t(control_name_xpath), &pXMLDomNodeList);
399  if (SUCCEEDED(hr) && pXMLDomNodeList != NULL)
400  {
401  pXMLDomNodeList->get_length(&n);
402  pXMLDomNodeList->Release();
403  }
404  return n;
405 }
406 
407 void lvDCOMInterface::getParams(std::map<std::string,std::string>& res)
408 {
409  res.clear();
410  char control_name_xpath[MAX_PATH_LEN];
411  _snprintf(control_name_xpath, sizeof(control_name_xpath), "/lvinput/section[@name='%s']/vi/param", m_configSection.c_str());
412  IXMLDOMNodeList* pXMLDomNodeList = NULL;
413  HRESULT hr = m_pxmldom->selectNodes(_bstr_t(control_name_xpath), &pXMLDomNodeList);
414  if (FAILED(hr) || pXMLDomNodeList == NULL)
415  {
416  return;
417  }
418  IXMLDOMNode *pNode, *pAttrNode1, *pAttrNode2;
419  long n = 0;
420  pXMLDomNodeList->get_length(&n);
421  for(long i=0; i<n; ++i)
422  {
423  pNode = NULL;
424  hr = pXMLDomNodeList->get_item(i, &pNode);
425  if (SUCCEEDED(hr) && pNode != NULL)
426  {
427  IXMLDOMNamedNodeMap *attributeMap = NULL;
428  pAttrNode1 = pAttrNode2 = NULL;
429  pNode->get_attributes(&attributeMap);
430  hr = attributeMap->getNamedItem(_bstr_t("name"), &pAttrNode1);
431  hr = attributeMap->getNamedItem(_bstr_t("type"), &pAttrNode2);
432  BSTR bstrValue1 = NULL, bstrValue2 = NULL;
433  hr=pAttrNode1->get_text(&bstrValue1);
434  hr=pAttrNode2->get_text(&bstrValue2);
435  res[std::string(COLE2CT(bstrValue1))] = COLE2CT(bstrValue2);
436  SysFreeString(bstrValue1);
437  SysFreeString(bstrValue2);
438  pAttrNode1->Release();
439  pAttrNode2->Release();
440  attributeMap->Release();
441  pNode->Release();
442  }
443  }
444  pXMLDomNodeList->Release();
445 }
446 
447 COAUTHIDENTITY* lvDCOMInterface::createIdentity(const std::string& user, const std::string& domain, const std::string& pass)
448 {
449  if (user.size() == 0)
450  {
451  return NULL;
452  }
453  COAUTHIDENTITY* pidentity = new COAUTHIDENTITY;
454  pidentity->Domain = (USHORT*)strdup(domain.c_str());
455  pidentity->DomainLength = static_cast<ULONG>(strlen((const char*)pidentity->Domain));
456  pidentity->Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
457  pidentity->Password = (USHORT*)strdup(pass.c_str());
458  pidentity->PasswordLength = static_cast<ULONG>(strlen((const char*)pidentity->Password));
459  pidentity->User = (USHORT*)strdup(user.c_str());
460  pidentity->UserLength = static_cast<ULONG>(strlen((const char*)pidentity->User));
461  return pidentity;
462 }
463 
464 HRESULT lvDCOMInterface::setIdentity(COAUTHIDENTITY* pidentity, IUnknown* pUnk)
465 {
466  HRESULT hr;
467  if (pidentity != NULL)
468  {
469  hr = CoSetProxyBlanket(pUnk, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL,
470  RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, pidentity, EOAC_NONE);
471  if (FAILED(hr))
472  {
473  std::cerr << "setIdentity failed" << std::endl;
474  return hr;
475  }
476  }
477  return S_OK;
478 }
479 
480 
481 void lvDCOMInterface::getViRef(BSTR vi_name, bool reentrant, LabVIEW::VirtualInstrumentPtr& vi)
482 {
483  UINT len = SysStringLen(vi_name);
484  std::wstring ws(vi_name, SysStringLen(vi_name));
485 
486  epicsGuard<epicsMutex> _lock(m_lock);
487  vi_map_t::iterator it = m_vimap.find(ws);
488  if(it != m_vimap.end())
489  {
490  vi = it->second.vi_ref;
491  try
492  {
493  vi->GetExecState();
494  }
495  catch(...)
496  {
497  //Gets here if VI ref is not longer valid
498  createViRef(vi_name, reentrant, vi);
499  }
500  }
501  else
502  {
503  createViRef(vi_name, reentrant, vi);
504  }
505 }
506 
507 
508 void lvDCOMInterface::createViRef(BSTR vi_name, bool reentrant, LabVIEW::VirtualInstrumentPtr& vi)
509 {
510  epicsThreadOnce(&onceId, initCOM, NULL);
511  std::wstring ws(vi_name, SysStringLen(vi_name));
512  HRESULT hr;
513  if ( (m_lv != NULL) && (m_lv->CheckConnection() == S_OK) )
514  {
515  ;
516  }
517  else if (m_host.size() > 0)
518  {
519  std::cerr << "(Re)Making connection to LabVIEW on " << m_host << std::endl;
520  CComBSTR host(m_host.c_str());
522  COAUTHINFO* pauth = new COAUTHINFO;
523  COSERVERINFO csi = { 0, NULL, NULL, 0 };
524  pauth->dwAuthnSvc = RPC_C_AUTHN_WINNT;
525  pauth->dwAuthnLevel = RPC_C_AUTHN_LEVEL_DEFAULT;
526  pauth->dwAuthzSvc = RPC_C_AUTHZ_NONE;
527  pauth->dwCapabilities = EOAC_NONE;
528  pauth->dwImpersonationLevel = RPC_C_IMP_LEVEL_IMPERSONATE;
529  pauth->pAuthIdentityData = m_pidentity;
530  pauth->pwszServerPrincName = NULL;
531  csi.pwszName = host;
532  csi.pAuthInfo = pauth;
533  MULTI_QI mq[ 1 ] = { 0 };
534  mq[ 0 ].pIID = &IID_IDispatch; // &LabVIEW::DIID__Application; // &IID_IDispatch;
535  mq[ 0 ].pItf = NULL;
536  mq[ 0 ].hr = S_OK;
537  hr = CoCreateInstanceEx( m_clsid, NULL, CLSCTX_REMOTE_SERVER | CLSCTX_LOCAL_SERVER, &csi, 1, mq );
538  if( FAILED( hr ) )
539  {
540  hr = CoCreateInstanceEx( m_clsid, NULL, CLSCTX_ALL, &csi, 1, mq );
541  }
542  if( FAILED( hr ) )
543  {
544  throw COMexception("CoCreateInstanceEx (LabVIEW) ", hr);
545  }
546  if( S_OK != mq[ 0 ].hr || NULL == mq[ 0 ].pItf )
547  {
548  throw COMexception("CoCreateInstanceEx (LabVIEW)(mq) ", mq[ 0 ].hr);
549  }
550  setIdentity(m_pidentity, mq[ 0 ].pItf);
551  m_lv.Release();
552  m_lv.Attach( reinterpret_cast< LabVIEW::_Application* >( mq[ 0 ].pItf ) );
553  }
554  else
555  {
556  std::cerr << "(Re)Making local connection to LabVIEW" << std::endl;
557  m_pidentity = NULL;
558  m_lv.Release();
559  hr = m_lv.CoCreateInstance(m_clsid, NULL, CLSCTX_LOCAL_SERVER);
560  if( FAILED( hr ) )
561  {
562  throw COMexception("CoCreateInstance (LabVIEW) ", hr);
563  }
564  }
565  if (reentrant)
566  {
567  vi = m_lv->GetVIReference(vi_name, "", 1, 8);
569  }
570  else
571  {
572  //If a VI is reentrant then always get it as reentrant
573  vi = m_lv->GetVIReference(vi_name, "", 0, 0);
575  if (vi->IsReentrant)
576  {
577  vi = m_lv->GetVIReference(vi_name, "", 1, 8);
579  reentrant = true;
580  }
581  }
582  ViRef viref(vi, reentrant, false);
583  // LabVIEW::ExecStateEnum::eIdle = 1
584  // LabVIEW::ExecStateEnum::eRunTopLevel = 2
585  if (vi->ExecState == LabVIEW::eIdle)
586  {
587  if ( checkOption(viStartIfIdle) )
588  {
589  std::cerr << "Starting \"" << CW2CT(vi_name) << "\" on " << (m_host.size() > 0 ? m_host : "localhost") << std::endl;
590  vi->Run(true);
591  viref.started = true;
592  }
593  else if ( checkOption(viWarnIfIdle) )
594  {
595  std::cerr << "\"" << CW2CT(vi_name) << "\" is not running on " << (m_host.size() > 0 ? m_host : "localhost") << " and autostart is disabled" << std::endl;
596  }
597  }
598  m_vimap[ws] = viref;
599 }
600 
601 
602 template <>
603 void lvDCOMInterface::getLabviewValue(const char* param, std::string* value)
604 {
605  if (value == NULL)
606  {
607  throw std::runtime_error("getLabviewValue failed (NULL)");
608  }
609  CComVariant v;
610  char vi_name_xpath[MAX_PATH_LEN], control_name_xpath[MAX_PATH_LEN];
611  _snprintf(vi_name_xpath, sizeof(vi_name_xpath), "/lvinput/section[@name='%s']/vi/@path", m_configSection.c_str());
612  _snprintf(control_name_xpath, sizeof(control_name_xpath), "/lvinput/section[@name='%s']/vi/param[@name='%s']/read/@target", m_configSection.c_str(), param);
613  CComBSTR vi_name(doPath(vi_name_xpath).c_str());
614  CComBSTR control_name(doXPATH(control_name_xpath).c_str());
615  getLabviewValue(vi_name, control_name, &v);
616  if ( v.ChangeType(VT_BSTR) == S_OK )
617  {
618  *value = CW2CT(v.bstrVal);
619  }
620  else
621  {
622  throw std::runtime_error("getLabviewValue failed (ChangeType BSTR)");
623  }
624 }
625 
626 template<typename T>
627 void lvDCOMInterface::getLabviewValue(const char* param, T* value, size_t nElements, size_t& nIn)
628 {
629  if (value == NULL)
630  {
631  throw std::runtime_error("getLabviewValue failed (NULL)");
632  }
633  CComVariant v;
634  char vi_name_xpath[MAX_PATH_LEN], control_name_xpath[MAX_PATH_LEN];
635  _snprintf(vi_name_xpath, sizeof(vi_name_xpath), "/lvinput/section[@name='%s']/vi/@path", m_configSection.c_str());
636  _snprintf(control_name_xpath, sizeof(control_name_xpath), "/lvinput/section[@name='%s']/vi/param[@name='%s']/read/@target", m_configSection.c_str(), param);
637  CComBSTR vi_name(doPath(vi_name_xpath).c_str());
638  CComBSTR control_name(doXPATH(control_name_xpath).c_str());
639  getLabviewValue(vi_name, control_name, &v);
640  if ( v.vt != (VT_ARRAY | CVarTypeInfo<T>::VT) )
641  {
642  throw std::runtime_error("getLabviewValue failed (type mismatch)");
643  }
644  CComSafeArray<T> sa;
645  sa.Attach(v.parray);
646  nIn = ( sa.GetCount() > nElements ? nElements : sa.GetCount() );
647  for(LONG i=0; i<nIn; ++i)
648  {
649  value[i] = sa.GetAt(i);
650  }
651  sa.Detach();
652 }
653 
654 template <typename T>
655 void lvDCOMInterface::getLabviewValue(const char* param, T* value)
656 {
657  if (value == NULL)
658  {
659  throw std::runtime_error("getLabviewValue failed (NULL)");
660  }
661  CComVariant v;
662  char vi_name_xpath[MAX_PATH_LEN], control_name_xpath[MAX_PATH_LEN];
663  _snprintf(vi_name_xpath, sizeof(vi_name_xpath), "/lvinput/section[@name='%s']/vi/@path", m_configSection.c_str());
664  _snprintf(control_name_xpath, sizeof(control_name_xpath), "/lvinput/section[@name='%s']/vi/param[@name='%s']/read/@target", m_configSection.c_str(), param);
665  CComBSTR vi_name(doPath(vi_name_xpath).c_str());
666  CComBSTR control_name(doXPATH(control_name_xpath).c_str());
667  getLabviewValue(vi_name, control_name, &v);
668  if ( v.ChangeType(CVarTypeInfo<T>::VT) == S_OK )
669  {
670  *value = v.*(CVarTypeInfo<T>::pmField);
671  }
672  else
673  {
674  throw std::runtime_error("getLabviewValue failed (ChangeType)");
675  }
676 }
677 
678 void lvDCOMInterface::getLabviewValue(BSTR vi_name, BSTR control_name, VARIANT* value)
679 {
680  HRESULT hr = S_OK;
681  LabVIEW::VirtualInstrumentPtr vi;
682  getViRef(vi_name, false, vi);
683  *value = vi->GetControlValue(control_name).Detach();
684  vi.Detach();
685  if (FAILED(hr))
686  {
687  throw std::runtime_error("getLabviewValue failed");
688  }
689 }
690 
692 {
693  CComBSTR m_bstr;
694 public:
695  StringItem(lvDCOMInterface* dcom, const char* xpath, const char* config_section, const char* param, bool filepath = false)
696  {
697  char base_xpath[MAX_PATH_LEN];
698  _snprintf(base_xpath, sizeof(base_xpath), xpath, config_section, param);
699  if (filepath)
700  {
701  m_bstr = dcom->doPath(base_xpath).c_str();
702  }
703  else
704  {
705  m_bstr = dcom->doXPATH(base_xpath).c_str();
706  }
707  };
708  const CComBSTR& bstr() { return m_bstr; }
709  size_t size() { return m_bstr.Length(); }
710  operator BSTR() { return m_bstr; }
711 };
712 
713 class BoolItem
714 {
715  bool m_value;
716 public:
717  BoolItem(lvDCOMInterface* dcom, const char* xpath, const char* config_section, const char* param)
718  {
719  char base_xpath[MAX_PATH_LEN];
720  _snprintf(base_xpath, sizeof(base_xpath), xpath, config_section, param);
721  m_value = dcom->doXPATHbool(base_xpath);
722  };
723  operator bool() { return m_value; }
724 };
725 
726 template <>
727 void lvDCOMInterface::setLabviewValue(const char* param, const std::string& value)
728 {
729  CComVariant v(value.c_str()), results;
730  StringItem vi_name(this, "/lvinput/section[@name='%s']/vi/@path", m_configSection.c_str(), "", true);
731  StringItem control_name(this, "/lvinput/section[@name='%s']/vi/param[@name='%s']/set/@target", m_configSection.c_str(), param);
732  StringItem post_button(this, "/lvinput/section[@name='%s']/vi/param[@name='%s']/set/@post_button", m_configSection.c_str(), param);
733  BoolItem post_button_wait(this, "/lvinput/section[@name='%s']/vi/param[@name='%s']/set/@post_button_wait", m_configSection.c_str(), param);
734  BoolItem use_ext(this, "/lvinput/section[@name='%s']/vi/param[@name='%s']/set/@extint", m_configSection.c_str(), param);
735  if (use_ext)
736  {
737  setLabviewValueExt(vi_name, control_name, v, &results);
738  if (post_button.size() > 0)
739  {
740  setLabviewValueExt(vi_name, post_button, v, &results);
741  }
742  }
743  else
744  {
745  setLabviewValue(vi_name, control_name, v);
746  if (post_button.size() > 0)
747  {
748  setLabviewValue(vi_name, post_button, v);
749  }
750  }
751  if (post_button_wait && (post_button.size() > 0) )
752  {
753  waitForLabviewBoolean(vi_name, post_button, false);
754  }
755 }
756 
757 void lvDCOMInterface::waitForLabviewBoolean(BSTR vi_name, BSTR control_name, bool value)
758 {
759  CComVariant v;
760  bool done = false;
761  while(!done)
762  {
763  getLabviewValue(vi_name, control_name, &v);
764  if ( v.ChangeType(VT_BOOL) == S_OK )
765  {
766  done = ( v.boolVal == (value ? VARIANT_TRUE : VARIANT_FALSE) );
767  v.Clear();
768  }
769  epicsThreadSleep(0.1);
770  }
771 }
772 
773 template <typename T>
774 void lvDCOMInterface::setLabviewValue(const char* param, const T& value)
775 {
776  CComVariant v(value), results;
777  StringItem vi_name(this, "/lvinput/section[@name='%s']/vi/@path", m_configSection.c_str(), "", true);
778  StringItem control_name(this, "/lvinput/section[@name='%s']/vi/param[@name='%s']/set/@target", m_configSection.c_str(), param);
779  StringItem post_button(this, "/lvinput/section[@name='%s']/vi/param[@name='%s']/set/@post_button", m_configSection.c_str(), param);
780  BoolItem post_button_wait(this, "/lvinput/section[@name='%s']/vi/param[@name='%s']/set/@post_button_wait", m_configSection.c_str(), param);
781  BoolItem use_ext(this, "/lvinput/section[@name='%s']/vi/param[@name='%s']/set/@extint", m_configSection.c_str(), param);
782  if (use_ext)
783  {
784  setLabviewValueExt(vi_name, control_name, v, &results);
785  if (post_button.size() > 0)
786  {
787  setLabviewValueExt(vi_name, post_button, v, &results);
788  }
789  }
790  else
791  {
792  setLabviewValue(vi_name, control_name, v);
793  if (post_button.size() > 0)
794  {
795  setLabviewValue(vi_name, post_button, v);
796  }
797  }
798  if (post_button_wait && (post_button.size() > 0) )
799  {
800  waitForLabviewBoolean(vi_name, post_button, false);
801  }
802 }
803 
804 void lvDCOMInterface::setLabviewValue(BSTR vi_name, BSTR control_name, const VARIANT& value)
805 {
806  HRESULT hr = S_OK;
807  LabVIEW::VirtualInstrumentPtr vi;
808  getViRef(vi_name, false, vi);
809  hr = vi->SetControlValue(control_name, value);
810  vi.Detach();
811  if (FAILED(hr))
812  {
813  throw std::runtime_error("SetLabviewValue failed");
814  }
815 }
816 
817 void lvDCOMInterface::setLabviewValueExt(BSTR vi_name, BSTR control_name, const VARIANT& value, VARIANT* results)
818 {
819 
820  CComSafeArray<BSTR> names(6);
821  names[0].AssignBSTR(_bstr_t(L"VI Name"));
822  names[1].AssignBSTR(_bstr_t(L"Control Name"));
823  names[2].AssignBSTR(_bstr_t(L"String Control Value"));
824  names[3].AssignBSTR(_bstr_t(L"Variant Control Value"));
825  names[4].AssignBSTR(_bstr_t(L"Machine Name"));
826  names[5].AssignBSTR(_bstr_t(L"Return Message"));
827 
828  _variant_t n;
829  n.vt = VT_ARRAY | VT_BSTR;
830  n.parray = names.Detach();
831 
832  CComSafeArray<VARIANT> values(6);
833  values[0] = vi_name;
834  values[1] = control_name;
835  //values[2] =
836  values[3] = value;
837  //values[4] =
838  //values[5] =
839 
840  _variant_t v;
841  v.vt = VT_ARRAY | VT_VARIANT;
842  v.parray = values.Detach();
843  //Must be called as reentrant!
844  callLabview(m_extint, n, v, true, results);
845 }
846 
847 void lvDCOMInterface::callLabview(BSTR vi_name, VARIANT& names, VARIANT& values, VARIANT_BOOL reentrant, VARIANT* results)
848 {
849  HRESULT hr = S_OK;
850  LabVIEW::VirtualInstrumentPtr vi;
851  if (reentrant)
852  {
853  getViRef(vi_name, true, vi);
854  }
855  else
856  {
857  getViRef(vi_name, false, vi);
858  }
859  hr = vi->Call(&names, &values);
860  vi.Detach();
861  CComVariant var(values);
862  var.Detach(results);
863  if (FAILED(hr))
864  {
865  throw std::runtime_error("CallLabviewValue failed");
866  }
867 }
868 
870 void lvDCOMInterface::report(FILE* fp, int details)
871 {
872  fprintf(fp, "XML ConfigFile: \"%s\"\n", m_configFile.c_str());
873  fprintf(fp, "XML ConfigFile section: \"%s\"\n", m_configSection.c_str());
874  fprintf(fp, "lvDCOMConfigure() Options: %d\n", m_options);
875  fprintf(fp, "DCOM Target ProgID: \"%s\"\n", m_progid.c_str());
876  fprintf(fp, "DCOM Target Host: \"%s\"\n", m_host.c_str());
877  fprintf(fp, "DCOM Target Username: \"%s\"\n", m_username.c_str());
878 // fprintf(fp, "Password: %s\n", m_password.c_str());
879  std::string vi_name;
880  for(vi_map_t::const_iterator it = m_vimap.begin(); it != m_vimap.end(); ++it)
881  {
882  vi_name = CW2CT(it->first.c_str());
883  fprintf(fp, "LabVIEW VI: \"%s\"\n", vi_name.c_str());
884  }
885  if (details > 0)
886  {
887  for(std::map<std::string,std::string>::const_iterator it = m_xpath_map.begin(); it != m_xpath_map.end(); ++it)
888  {
889  fprintf(fp, "Config XPath: \"%s\" = \"%s\"\n", it->first.c_str(), it->second.c_str());
890  }
891  for(std::map<std::string,bool>::const_iterator it = m_xpath_bool_map.begin(); it != m_xpath_bool_map.end(); ++it)
892  {
893  fprintf(fp, "Config XPath: \"%s\" = %s\n", it->first.c_str(), (it->second ? "true" : "false") );
894  }
895  }
896 }
897 
898 #ifndef DOXYGEN_SHOULD_SKIP_THIS
899 
900 template void lvDCOMInterface::setLabviewValue(const char* param, const double& value);
901 template void lvDCOMInterface::setLabviewValue(const char* param, const int& value);
902 
903 template void lvDCOMInterface::getLabviewValue(const char* param, double* value);
904 template void lvDCOMInterface::getLabviewValue(const char* param, int* value);
905 
906 template void lvDCOMInterface::getLabviewValue(const char* param, double* value, size_t nElements, size_t& nIn);
907 
908 template void lvDCOMInterface::getLabviewValue(const char* param, int* value, size_t nElements, size_t& nIn);
909 
910 #endif /* DOXYGEN_SHOULD_SKIP_THIS */
static void initCOM(void *)
create an C++ exception from a COM HRESULT.
Definition: variant_utils.h:15
COAUTHIDENTITY * m_pidentity
std::string m_configSection
section of configFile to load information from
(4) On IOC exit, stop any LabVIEW VIs that we started due to viStartIfIdle being specified ...
CComPtr< LabVIEW::_Application > m_lv
BoolItem(lvDCOMInterface *dcom, const char *xpath, const char *config_section, const char *param)
std::string m_progid
Header file for various COM utilities.
bool started
did we start this vi because it was idle and viStartIfIdle was specified
void waitForLabviewBoolean(BSTR vi_name, BSTR control_name, bool value)
static void __stdcall my_com_raise_error(HRESULT hr, IErrorInfo *perrinfo)
The Microsoft ATL _com_error is not derived from std::exception hence this bit of code to throw our o...
(8) On IOC exit, stop any LabVIEW VIs that we have connected to
std::string doPath(const std::string &xpath)
const CComBSTR & bstr()
std::string m_password
IXMLDOMDocument2 * m_pxmldom
void callLabview(BSTR vi_name, VARIANT &names, VARIANT &values, VARIANT_BOOL reentrant, VARIANT *results)
void report(FILE *fp, int details)
Helper for EPICS driver report function.
#define MAX_PATH_LEN
header for lvDCOMInterface class.
int m_options
the various lvDCOMOptions currently in use
std::map< std::string, bool > m_xpath_bool_map
void getParams(std::map< std::string, std::string > &res)
std::string doXPATH(const std::string &xpath)
std::map< std::string, std::string > m_xpath_map
static void epicsExitFunc(void *arg)
void stopVis(bool only_ones_we_started)
Manager class for LabVIEW DCOM Interaction. Parses an lvinput.xml file and provides access to the Lab...
void setLabviewValue(const char *param, const T &value)
HRESULT setIdentity(COAUTHIDENTITY *pidentity, IUnknown *pUnk)
(1) If the LabVIEW VI is idle when we connect to it, issue a warning message
Hold a reference to a LabVIEW VI.
void getLabviewValue(const char *param, T *value)
(2) If the LabVIEW VI is idle when we connect to it, attempt to start it
std::string m_configFile
static epicsThreadOnceId onceId
std::string m_host
COAUTHIDENTITY * createIdentity(const std::string &user, const std::string &domain, const std::string &pass)
bool checkOption(lvDCOMOptions option)
void createViRef(BSTR vi_name, bool reentrant, LabVIEW::VirtualInstrumentPtr &vi)
void setLabviewValueExt(BSTR vi_name, BSTR control_name, const VARIANT &value, VARIANT *results)
std::string m_username
bool doXPATHbool(const std::string &xpath)
lvDCOMInterface(const char *configSection, const char *configFile, const char *host, int options, const char *progid, const char *username, const char *password)
section name of configFile to use to configure this asyn port
void getViRef(BSTR vi_name, bool reentrant, LabVIEW::VirtualInstrumentPtr &vi)
StringItem(lvDCOMInterface *dcom, const char *xpath, const char *config_section, const char *param, bool filepath=false)