By Adam Nagy
While in case of an Inventor AddIn you can use early binding to access the iLogic COM objects, in case of an external application you need to use late binding. That's because the iLogic objects are not real COM objects, but COM wrappers around .NET objects. In case of VB.NET it's been really simple from the start to do late binding, because it's been enough to declare the variables as Object instead of the specific type. Since version 4 of C# it's just as simple there because of the arrival of the dynamic keyword.
Below is the external application equivalent of the code used in this blog post: http://adndevblog.typepad.com/manufacturing/2013/04/call-ilogic-from-net.html
static void test_iLogic()
{
Inventor.Application oApp =
System.Runtime.InteropServices.Marshal.
GetActiveObject("Inventor.Application") as Inventor.Application;
//iLogic is also an addin which has its guid
string iLogicAddinGuid = "{3BDD8D79-2179-4B11-8A5A-257B1C0263AC}";
Inventor.ApplicationAddIn addin = null;
try
{
// try to get iLogic addin
addin = oApp.ApplicationAddIns.get_ItemById(iLogicAddinGuid);
}
catch
{
// any error...
}
if (addin != null)
{
// activate the addin
if (!addin.Activated)
addin.Activate();
// entrance of iLogic
dynamic _iLogicAutomation = addin.Automation;
Document oCurrentDoc = oApp.ActiveDocument;
dynamic myRule = null;
//dump all rules
foreach (dynamic eachRule in _iLogicAutomation.Rules(oCurrentDoc))
{
if (eachRule.Name == "MyRule")
{
myRule = eachRule;
//list the code of rule to the list box
MessageBox.Show(myRule.Text);
break;
}
}
if (myRule != null)
_iLogicAutomation.RunRule(oCurrentDoc, "MyRule");
}
}
In C++ it's not so simple. Fortunately I found this MSDN article which makes things a bit easier: http://support.microsoft.com/kb/238393
// http://support.microsoft.com/kb/238393
//
// AutoWrap() - Automation helper function...
//
HRESULT AutoWrap(int autoType, VARIANT *pvResult, IDispatch *pDisp,
LPOLESTR ptName, int cArgs...)
{
// Begin variable-argument list...
va_list marker;
va_start(marker, cArgs);
if(!pDisp) {
MessageBox(NULL, _T("NULL IDispatch passed to AutoWrap()"),
_T("Error"), 0x10010);
_exit(0);
}
// Variables used...
DISPPARAMS dp = { NULL, NULL, 0, 0 };
DISPID dispidNamed = DISPID_PROPERTYPUT;
DISPID dispID;
HRESULT hr;
TCHAR buf[200];
// Get DISPID for name passed...
hr = pDisp->GetIDsOfNames(
IID_NULL, &ptName, 1, LOCALE_USER_DEFAULT, &dispID);
if(FAILED(hr)) {
_stprintf(buf,
_T("IDispatch::GetIDsOfNames(\"%s\") failed w/err0x%08lx"),
ptName, hr);
MessageBox(NULL, buf, _T("AutoWrap()"), 0x10010);
_exit(0);
return hr;
}
// Allocate memory for arguments...
VARIANT *pArgs = new VARIANT[cArgs+1];
// Extract arguments...
for(int i=0; i<cArgs; i++) {
pArgs[i] = va_arg(marker, VARIANT);
}
// Build DISPPARAMS
dp.cArgs = cArgs;
dp.rgvarg = pArgs;
// Handle special-case for property-puts!
if(autoType & DISPATCH_PROPERTYPUT) {
dp.cNamedArgs = 1;
dp.rgdispidNamedArgs = &dispidNamed;
}
// Make the call!
hr = pDisp->Invoke(dispID, IID_NULL, LOCALE_SYSTEM_DEFAULT,
autoType, &dp, pvResult, NULL, NULL);
if(FAILED(hr)) {
_stprintf(buf,
_T("IDispatch::Invoke(\"%s\"=%08lx) failed w/err 0x%08lx"),
ptName, dispID, hr);
MessageBox(NULL, buf, _T("AutoWrap()"), 0x10010);
_exit(0);
return hr;
}
// End variable-argument section...
va_end(marker);
delete [] pArgs;
return hr;
}
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
HRESULT Result = NOERROR;
::CoInitialize(NULL);
// Access Inventor
{
CLSID InvAppClsid;
Result = CLSIDFromProgID (
_T("Inventor.Application"), &InvAppClsid);
if (FAILED(Result)) return Result;
CComPtr<IUnknown> pInvAppUnk;
Result = ::GetActiveObject (InvAppClsid, NULL, &pInvAppUnk);
if (FAILED (Result))
_tprintf_s(_T("Could not get the active Inventor instance\n"));
if (FAILED(Result)) return Result;
CComPtr<Application> pInvApp;
Result = pInvAppUnk->QueryInterface(
__uuidof(Application), (void **) &pInvApp);
if (FAILED(Result)) return Result;
CComPtr<ApplicationAddIn> pAddIn =
pInvApp->ApplicationAddIns->ItemById[
_T("{3BDD8D79-2179-4B11-8A5A-257B1C0263AC}")];
// Calling iLogic functions
{
CComPtr<IDispatch> pAuto = pAddIn->Automation;
CComPtr<Document> pDoc = pInvApp->ActiveDocument;
VARIANT ret;
VARIANT param1;
param1.vt = VT_DISPATCH;
param1.pdispVal = pDoc;
Result = AutoWrap(
DISPATCH_PROPERTYGET, &ret, pAuto, _T("Rules"), 1, param1);
// Rules() returns an IEnumarator - it will be wrapped as
// IEnumVARIANT returned by GetEnumerator()
// http://stackoverflow.com/questions/7399447/com-use-ienumerable-in-atl-c-project
CComPtr<IDispatch> pRules = ret.pdispVal;
Result = AutoWrap(
DISPATCH_METHOD, &ret, pRules, _T("GetEnumerator"), 0);
CComQIPtr<IEnumVARIANT> pRulesEnum = ret.punkVal;
VARIANT rule;
ULONG celt = 1, celtFetched;
while (pRulesEnum->Next(celt, &rule, &celtFetched) == S_OK)
{
CComPtr<IDispatch> pRule = rule.pdispVal;
Result = AutoWrap(
DISPATCH_PROPERTYGET, &ret, pRule, _T("Name"), 0);
BSTR name = ret.bstrVal;
if (_tcscmp(name, _T("MyRule")) == 0)
{
Result = AutoWrap(
DISPATCH_PROPERTYGET, &ret, pRule, _T("Text"), 0);
BSTR text = ret.bstrVal;
MessageBox(NULL, text, _T("Rule Text"), MB_OK);
VARIANT param2;
param2.vt = VT_BSTR;
param2.bstrVal = name;
// Parameters need to be added in reverse order.
// In the C# code you'd call it like
// _iLogicAutomation.RunRule(oCurrentDoc, "MyRule");
Result = AutoWrap(
DISPATCH_METHOD, &ret, pAuto, _T("RunRule"),
2, param2, param1);
}
}
}
}
::CoUninitialize();
return nRetCode;
}