1

We have an old C++ COM .dll that a customer calls from their C++ client.

We are trying to replace our old .dll with a new COM registered one written in .NET.

The C++ client can call our new .dll but crashes after a particular method call to us.

It seems we are returning something wrong in our variable "outNoOfChildren" or in the outChildList array below.

(The error message in the client reads: "Unknown exception caught. Number of childs Measured: -2147467259 Limits: = 2", it expects 2 which we are trying to return.)

Strange thing is that the method call in our new .NET .dll seems to work from another test client we have (VB6).

This is the function in the C++ .dll that we are trying to replace:

STDMETHODIMP marcomBase::XgetChildren(VARIANT inUser,
                                  VARIANT inId,
                                  VARIANT *outNoOfChildren,
                                  VARIANT *outChildList,
                                  VARIANT *outErrMsg,
                                  VARIANT *status)
  long stat= 1;
  char user[255+1];
  char id[255+1];
  long noOfChildren = 0;
  char errMsg[255+1] = "";
  _bstr_t temp_bstr;
  long inparamType;
  long dumInt;
  SAFEARRAY *pArray;
  long ix[2];
  VARIANT var;
  SAFEARRAYBOUND rgsabound[2];
  ChildT childList;
  ChildT *childListTemp = NULL;
  ChildT *childListTempNext = NULL;

  childList.nextChild = NULL;

  getInParam( inUser, &inparamType, user, &dumInt);
  if (inparamType != VT_BSTR)
  {
    strcpy(errMsg, "Parameter 1 incorrect, type must be VT_BSTR");
    stat = 0;
  }

  if (stat == 1)
  {
    getInParam( inId, &inparamType, id, &dumInt);
    if (inparamType != VT_BSTR)
    {
      strcpy(errMsg, "Parameter 2 incorrect, type must be VT_BSTR");
      stat = 0;
    }
  }

  if (stat == 1)
  {
    stat = barApiObj.getChildren(user, id, &noOfChildren, childList, errMsg);
  }

  outNoOfChildren->vt = VT_I4;
  outNoOfChildren->lVal = noOfChildren;

  rgsabound[0].lLbound = 1;
  rgsabound[0].cElements = noOfChildren;
  rgsabound[1].lLbound = 1;
  rgsabound[1].cElements = 3;

  pArray = ::SafeArrayCreate(VT_VARIANT, 2, rgsabound);
  outChildList->vt = VT_ARRAY|VT_VARIANT;
  outChildList->parray = pArray;

  ix[0] = 1;

  childListTemp = childList.nextChild;

  while (childListTemp != NULL)
  {
    temp_bstr = childListTemp->child;
    var.vt = VT_BSTR;
    var.bstrVal = temp_bstr.copy();
    ix[1] = 1;
    ::SafeArrayPutElement(pArray, ix, &var);

    temp_bstr = childListTemp->prodNo;
    var.vt = VT_BSTR;
    var.bstrVal = temp_bstr.copy();
    ix[1] = 2;
    ::SafeArrayPutElement(pArray, ix, &var);

    temp_bstr = childListTemp->rev;
    var.vt = VT_BSTR;
    var.bstrVal = temp_bstr.copy();
    ix[1] = 3;
    ::SafeArrayPutElement(pArray, ix, &var);

    ix[0] = ix[0] + 1;

    childListTempNext = childListTemp->nextChild;
    delete childListTemp;
    childListTemp = childListTempNext;
  }

  temp_bstr = errMsg;
  outErrMsg->vt = VT_BSTR;
  outErrMsg->bstrVal = temp_bstr.copy();

  status->vt = VT_I4;
  status->lVal = stat;

  return S_OK;
}

This is the .NET .dll that we have written:

.NET Interface:

...
[DispId(12)]
 [Description("method XgetChildren")]
object XgetChildren(object inUser, object inId, out object outNoOfChildren, out object outChildList, out object outErrMsg);
...

.NET Class

public object XgetChildren(object inUser, object inId, out object outNoOfChildren, out object outChildList, out object outErrMsg)
{
    outNoOfChildren = 0;
    outChildList = null;
    outErrMsg = "";

    List<IndividualInfo> children = null;
    try
    {
        PrevasMesExternalServiceClient client = getClient();
        children = client.GetChildren(new SerialNo() { Number = inId.ToString() });
    }
    catch (Exception ex)
    {
       return Constants.STATUS_FAIL;
    }

    int[] myLengthsArray = { children.Count, 3 }; //Length of each array 
    int[] myBoundsArray = { 1, 1 }; //Start index of each array

    Array myArray = Array.CreateInstance(typeof(string), myLengthsArray, myBoundsArray); //Create 1-based array of arrays

    for (int i = 0; i < children.Count; i++)
    {
        myArray.SetValue(Utils.getStrValue(children[i].SerialNumber, Constants.pmesIDNO_LENGTH), i+1, 1);
        myArray.SetValue(Utils.getStrValue(children[i].ProductNumber, Constants.pmesPRODUCTNO_LENGTH), i+1, 2);
        myArray.SetValue(Utils.getStrValue(children[i].Revision, Constants.pmesRSTATE_LENGTH), i+1, 3);
    }

    outChildList = myArray;
    outNoOfChildren = children.Count;

    return Constants.STATUS_OK;
}

Update: The original C++ .dll looks like this in OLE/COM Object Viewer:

    [id(0x0000000c), helpstring("method XgetChildren")]
    HRESULT XgetChildren(
                    [in] VARIANT inUser, 
                    [in] VARIANT inId, 
                    [out] VARIANT* outNoOfChildren, 
                    [out] VARIANT* outChildList, 
                    [out] VARIANT* outErrMsg, 
                    [out, retval] VARIANT* status);

After registering our new .NET .dll it looks like this in OLE/COM Object Viewer:

    [id(0x0000000c), helpstring("method ")]
    HRESULT XgetChildren(
                    [in] VARIANT inUser, 
                    [in] VARIANT inId, 
                    [out] VARIANT* outNoOfChildren, 
                    [out] VARIANT* outChildList, 
                    [out] VARIANT* outErrMsg, 
                    [out, retval] VARIANT* pRetVal);

Update 2: I'm beginning to suspect the error may not be in "outNoOfChildren" but in the array "outChildList". I have updated the question and modified the code samples for this.

Any ideas much appreciated!

3
  • What happened to the status parameter? Commented May 17, 2012 at 2:39
  • I think that last parameter is the return value from the function. Commented May 17, 2012 at 6:10
  • I updated question with how it looks in OLE/COM Object Viewer before and after swapping to the .NET .dll. I assumed the C# function return value got translated into "[out, retval] VARIANT*" and that it was ok. But I agree it looks strange in the C++ snippet that there is both the status parameter and the return of S_OK? Commented May 17, 2012 at 6:35

1 Answer 1

1

Measured: -2147467259 Limits: = 2"

That's a magic number. Convert it to hex and you get 0x80004005. That's an error code, the infamous E_FAIL or "Unspecified error". So somewhere in the dots that we can't see, you are interpreting an error return value as a number. Possibly a variant of type vtError and ignoring the variant type. That's all that can be concluded from the question, review the error handling in your code.

Sign up to request clarification or add additional context in comments.

5 Comments

Hans, thank you for your response. But that error is what we see in the customer's client (it's nothing we can do about it). The question is why the error is raised? I'm guessing maybe we from .NET COM .dll are returning something other than the client expects? (I've included the entire original C++ .dll code and our new .NET replacement code). But I'm really fumbling in darkness...
In C++ .dll they use something called "SafeArray" (see pArray). Could that be the problem? E.g. the customer's client expects "SafeArray" but our .NET .dll simply returns a "regular" array? Must I do anything in .NET .dll to return "SafeArray"?
No, the CLR makes that conversion automatically.
Hans, thank you! Your comment let us believe we were not doing anything fundamentally wrong. It turns out the C++ client expected safearray of VARIANT but we were returning safearray of BSTR. Changing Array myArray = Array.CreateInstance(typeof(string)...) in .NET .dll to Array myArray = Array.CreateInstance(typeof(object)...) did the trick. (Why it worked from VB6 remains a mystery, but...)
VB6 like variants. Or rather, VB6 programmers like Dim without As.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.