一个用MFC编写的ActiveX控件有一接口返回参数为long型及一个安全数组,如
long CTest::GetArrayData(const VARIANT FAR& vtData){
  //对vtData数据进行处理后返回已处理个数
  return 0;
}
在VB或VC下都可以正常调用,但在javascript调用时,控件里得到的不是安全数组类型,所以无法处理。请问该怎么解决

解决方案 »

  1.   

    Using and Returning SafeArrays
    Nothing seems to cause more confusion than the concept of SafeArrays. First, documentation is very sparse. Second, SafeArrays can contain pointers to objects on the system heap (BSTRs) making resource allocation/deallocation critical. You can look at a SafeArray as a Visual Basic Array, which contains information about the array. Alternatively, you can look at a SafeArray as a wrapper class providing high level protected access to an array. In the context of ASP COM programming, the SafeArray is a way to package data for consumption by an ASP script. It is also useful within a COM component that must past data to an ASP Response object. A list of useful references are available at this website. What follows is some commented sample code. It represents my best understanding of SafeArrays and resource allocation, after struggling through the available documentation and source code. Constructing a SafeArray using SafeArrayPutElement 
     Returning a SafeArray of VARIANT BSTR 
     Returning a Two Dimensional SafeArrray 
     Low Level Construction of a SafeArray of BYTES Constructing a SafeArray using SafeArrayPutElements
    You can put elements in a SafeArray using SafeArrayPutElements. This simplifies data access, but adds overhead to the array construction. A common requirement is to construct a SafeArray of VARIANTs of TYPE VT_BSTR. Such a SafeArray can then be returned to an ASP script as a VARIANT of TYPE VT_ARRAY | VT_VARIANT. This complex structure is required for compatibility with various scripting languages such as Visual Basic Script. Such a construct can also be used by JScript, but with more difficulty. In the following code sample, I construct a SafeArray of ten elements. The sample converts the element index to an ANSI string using ltoa(). It then uses the ATL conversion macro A2W to convert the ANSI string to a Unicode "wide character" string. Finally, the Unicode string is "converted" to a BSTR by allocating memory for a length prefixed string on the system heap, returning a pointer to the string. This could also be accomplished using A2BSTR, but this macro hides the memory allocation that I am trying to demonstrate. The important point here is that the BSTR exist in memory independent of the COM component. When returned as an [out, retval] it is the responsibility of the calling script engine to release the system memory holding the actual string data. You can look at a BSTR as calling new() in the COM component and calling delete() in the ASP script. Here is the verbose first pass at the code to create a SafeArray of BSTR VARIANTs:
    USES_CONVERSION;  // enables use of ATL conversion macro A2W
    char buffer[20];  // used to store ANSI string
    HRESULT hr= S_OK;// Create SafeArray of VARIANT BSTRs
    SAFEARRAY *pSA;
    SAFEARRAYBOUND aDim[1];    // a one dimensional array
    aDim[0].lLbound= 0;  // Visual Basic arrays start with index 0
    aDim[0].cElements= 10;
    pSA= SafeArrayCreate(VT_VARIANT,1,aDim);  // create a 1D SafeArray of VARIANTS
    if (pSA != NULL) {
        long aLong[1];
        // iterate over array adding VARIANTs of type VT_BSTR
        for (long l= aDim[0].lLbound; l< (aDim[0].cElements + aDim[0].lLbound); l++) { 
            VARIANT vOut;
            VariantInit(&vOut);
            vOut.vt= VT_BSTR;  // set type
            ltoa(l,buffer,10);  // convert long to ANSI string value
            vOut.bstrVal= ::SysAllocString(A2W(buffer)); // system wide "new"
            aLong[0]= l;  // set index value
            if (hr= SafeArrayPutElement(pSA, aLong, &vOut)) { // "correctly" copies VARIANT
                VariantClear(&vOut);  // release BSTR from memory on error
                SafeArrayDestroy(pSA); // does a deep destroy on error
                return hr;
            }
            VariantClear(&vOut);  // does a deep destroy of source VARIANT
        } // end iteration
    }
    // clean up here only if you do not return SafeArray as an [out, retval]
    SafeArrayDestroy(pSA); // again does a deep destroyThe key point here, at least according to my reading of the docs, is that SafeArrayPutElement "correctly" copies the VARIANT into the SafeArray, including presumably a deep copy of the BSTR. Any existing element in the SafeArray is released "correctly". To avoid a memory leak, you must call VariantClear() on the source VARIANT to reclaim the source BSTR memory after each PUT.The class _variant_t is a wrapper class that automates memory management much like CComVariant. Using _variant_t greatly simplifies the above code. To use the _variant_t class you must add the #include <comdef.h> to the header file. I also eliminated the array of longs in this simplified version. I modeled the following code after Shelley Powers "Developing ASP Components Second Edition".
    USES_CONVERSION;
    char buffer[20];
    HRESULT hr= S_OK;// Create SafeArray of VARIANT BSTRs
    SAFEARRAY *pSA;
    SAFEARRAYBOUND aDim[1];
    aDim[0].lLbound= 0;
    aDim[0].cElements= 10;
    pSA= SafeArrayCreate(VT_VARIANT,1,aDim);
    if (pSA != NULL) {
        _variant_t vOut;
        for (long l= aDim[0].lLbound; l< (aDim[0].cElements + aDim[0].lLbound); l++) { 
            ltoa(l,buffer,10);
            vOut= A2W(buffer);  // assignment operator automates memory allocation for BSTR
            if (hr= SafeArrayPutElement(pSA, &l, &vOut)) {
                SafeArrayDestroy(pSA); // does a deep destroy on error
                return hr;
            }
        }
    }
    // clean up here only if you do not return SafeArray as an [out, retval]
    SafeArrayDestroy(pSA); // again does a deep destroy
      

  2.   

    Returning a SafeArray of VARIANT BSTRs
    The following code demonstrates how to return the SafeArray of VARIANTs of type VT_BSTR constructed in the preceding section "Constructing A SafeArray Using SafeArrayPutElements", The following code takes a long as an input parameter and creates a SafeArray of VARIANTS of the corresponding length. The IDL looks like this:[id(1), helpstring("method GetArray")] HRESULT GetArray([in] int newVal, [out, retval] VARIANT *pVal);It is important to note the the out VARIANT *pVal is not initialized on entry to the method. If you call a method such as CComVariant.Detach(pVal), the out VARIANT will be cleared. Since the out VARIANT is not yet initialized, it contains random data. Clearing a VARIANT of random data is not recommended! The following code sample also calls the MACROS V_VT and V_ARRAY. These are poorly documented, but available for review in the header file oleauto.h. Again, I modeled the C++ and ASP code after Shelley Powers "Developing ASP Components Second Edition".
    // ASSERT newVal > 0
    STDMETHODIMP CArray::GetArray(int newVal, VARIANT *pVal)
    {
        // TODO: Add your implementation code here
        USES_CONVERSION;
        char buffer[20];
        HRESULT hr= S_OK;
        if (newVal < 1) {
            newVal= 1;
        }        // Create SafeArray of VARIANT BSTRs
        SAFEARRAY *pSA;
        SAFEARRAYBOUND aDim[1];
        aDim[0].lLbound= 0;
        aDim[0].cElements= newVal;
        pSA= SafeArrayCreate(VT_VARIANT,1,aDim);
        if (pSA != NULL) {
            _variant_t vOut;
            for (long l= aDim[0].lLbound; l< (aDim[0].cElements + aDim[0].lLbound); l++) { 
                ltoa(l,buffer,10);
                vOut= A2W(buffer);
                if (hr= SafeArrayPutElement(pSA, &l, &vOut)) {
                    SafeArrayDestroy(pSA); // does a deep destroy
                    return hr;
                }
            }
        }// return SafeArray as VARIANT
    //VariantInit(pVal); // WARNING You must initialize *pVal before calling Detach
    V_VT(pVal)= VT_ARRAY | VT_VARIANT; //pVal->vt= VT_ARRAY | VT_VARIANT; // oleauto.h
    V_ARRAY(pVal)= pSA; // (pSA may be null) //pVal->parray= pSA; // oleauto.h
    return S_OK;
    }You can call the code from VBScript like this:<%@ LANGUAGE="VBScript" %>
    <HTML>
    <HEAD>
    <META HTTP-EQUIV="Content-Type" content="text/html; charset=iso-8859-1">
    <TITLE>Test Array DLL</TITLE>
    </HEAD>
    <BODY TopMargin="0" Leftmargin="0">
        <%
        Set objArray= Server.CreateObject("JALArray.Array") 
        Dim arrayBSTR
        Dim x
        arrayBSTR= objArray.GetArray(10)
        for x= LBound(arrayBSTR) to UBound(arrayBSTR)
            Response.write "<BR>"&x&": "&arrayBSTR(x) 
        next
        Set objArray= nothing 
        %> 
    </BODY>
    </HTML>Calling the code from JScript is more complicated:<%@ LANGUAGE="JSCRIPT" %>
    <HTML>
    <HEAD>
    <META HTTP-EQUIV="Content-Type" content="text/html; charset=iso-8859-1">
    <TITLE>Test Array DLL</TITLE>
    </HEAD>
    <BODY TopMargin="0" Leftmargin="0">
        <%
        var objArray= Server.CreateObject("JALArray.Array") ;
        var arrayVariants= new VBArray(objArray.GetArray(10)); 
        var arrayBSTR= arrayVariants.toArray(); 
        for (i= arrayVariants.lbound();i<= arrayVariants.ubound(); i++) {
            Response.write("<BR>"+i+": "+arrayBSTR[i]+""); 
        }
        objArray= null;
        %> 
    </BODY>
    </HTML>The preceding JScript code first creates a VB array from the SafeArray. The call toArray() then returns a JScript array from the VB array.
      

  3.   

    Returning a Two Dimensional Array
    Returning a rectangular two dimensional array is not that much different than the preceding code. Here is the two dimensioned version:// ASSERT newVal > 0
    STDMETHODIMP CArray::GetArray(int newVal, VARIANT *pVal)
    {
        // TODO: Add your implementation code here
        USES_CONVERSION;
        char buffer[20];
        HRESULT hr= S_OK;
        if (newVal < 1) {
            newVal= 1;
        }    // Create SafeArray of VARIANT BSTRs
        SAFEARRAY *pSA;
        SAFEARRAYBOUND aDim[2];    // two dimensional array
        aDim[0].lLbound= 0;
        aDim[0].cElements= newVal;
        aDim[1].lLbound= 0;
        aDim[1].cElements= newVal;    // rectangular array
        pSA= SafeArrayCreate(VT_VARIANT,2,aDim);  // again, 2 dimensions
        long aLong[2];
        long lSum;
        if (pSA != NULL) {
            _variant_t vOut;
            for (long x= aDim[0].lLbound; x< (aDim[0].cElements + aDim[0].lLbound); x++) { 
                aLong[0]= x;    // set x index
                for (long y= aDim[1].lLbound; y< (aDim[1].cElements + aDim[1].lLbound); y++) {
                    aLong[1]= y;    // set y index
                    lSum= x+y;
                    ltoa(lSum,buffer,10);
                    vOut= A2W(buffer);
                    if (hr= SafeArrayPutElement(pSA, aLong, &vOut)) {
                        SafeArrayDestroy(pSA); // does a deep destroy
                        return hr;
                    }
                }
            }
        }        // return SafeArray as VARIANT
        //VariantInit(pVal); // WARNING You must initialize *pVal before calling Detach
        V_VT(pVal)= VT_ARRAY | VT_VARIANT; //pVal->vt= VT_ARRAY | VT_VARIANT; 
        V_ARRAY(pVal)= pSA; // (pSA may be null) //pVal->parray= pSA; // UNION oleauto.h
        return S_OK;
    }Looping through a two dimensional array in the client script is a bit more complicated. Here is a VBScript sample that can process either a one or two dimensional array modified from "Programming MS Visual Basic 6.0" by Francesco Balena (MS Press).<%@ LANGUAGE="VBScript" %>
    <HTML>
    <HEAD>
    <META HTTP-EQUIV="Content-Type" content="text/html; charset=iso-8859-1">
    <TITLE>Test MultiDimensionalArray DLL</TITLE>
    </HEAD>
    <BODY TopMargin="0" Leftmargin="0">
    <%
        Set objArray= Server.CreateObject("JALArray.Array") 
        Dim arrayBSTR
        Dim x
        arrayBSTR= objArray.GetArray(10)
        Select Case NumberOfDims(arrayBSTR)     ' Function defined below
             Case 1 'one dimensional array as table
                Response.write("<TABLE BORDER=1><TR>")
                for x= LBound(arrayBSTR) to UBound(arrayBSTR)
                    Response.write "<TD>"&arrayBSTR(x)&"</TD>" 
                next 
                Response.write("</TR></TABLE>") 
            Case 2 'two dimensional array as table
                Response.write("<TABLE BORDER=1>")
                for x= LBound(arrayBSTR) to UBound(arrayBSTR)
                    Response.write("<TR>")
                    for y= LBound(arrayBSTR,2) to UBound(arrayBSTR,2)
                        Response.write "<TD>"&arrayBSTR(x,y)&"</TD>"
                    next
                    Response.write("</TR>") 
                next
                Response.write("</TABLE>")
              Case Else
                Err.Raise 1001    ' two or more dimensions ? not an array
        End Select 
        Set objArray= nothing
    %> 
    <SCRIPT LANGUAGE="VBScript" RUNAT="Server"> 
    'Modified From Programming MS Visual Basic 6.0 Balena
    Function NumberOfDims(array) 
        Dim temp
        On Error Resume Next
        Do 
            temp= UBound(array, NumberOfDims +1)
            If Err Then Exit Do
                NumberOfDims= NumberOfDims+1
            Loop
    End Function
    </SCRIPT> 
    </BODY>
    </HTML>
     Low Level Construction of a SafeArray of BYTES
    While SafeArrayPutElement simplifies array access, it is not as efficient as directly accessing the underlying array. The following code demonstrates how to fill a SafeArray with BYTES of data using low level access and then write out the SafeArray using the Response object. This requires locking/unlocking of the SafeArray by calling SafeArrayAccessData and SafeArrayUnaccessData. Locking the SafeArray prevents the destruction of the SafeArray during manipulation. Calling SafeArrayDelete() on a locked SafeArray returns an error. In this sample, the raw data is stored in a array of char of size m_nChunkSize.// Create char Array 
    char* out= new (nothrow) char[m_nChunkSize]; // Dynamic Allocation!
    if(out == NULL) {
        return E_OUTOFMEMORY;
    }... code to fill char array removed for clarityVARIANT vOut;
    VariantInit(&vOut); // extra callSAFEARRAY *pSA;
    SAFEARRAYBOUND aDim[1];    // one dimensional array
    aDim[0].lLbound= 0;
    aDim[0].cElements= m_nChunkSize;
    char * pActualData= NULL;
    HRESULT hres= S_OK;
    // Create SafeArray
    pSA= SafeArrayCreate(VT_UI1,1,aDim);
    if (pSA == NULL) {
        delete [] out;
        return E_OUTOFMEMORY;
    }
    try {
        VariantInit(&vOut); // may not be necessary
        vOut.vt= (VT_ARRAY|VT_UI1); // V_VT(&vOut)= (VT_ARRAY|VT_UI1);
        vOut.parray= pSA; // V_ARRAY(&vOut)= pSA; 
        if (hres=SafeArrayAccessData(pSA,(void **)&pActualData)) throw hres;
        for (int i=0; i<m_nChunkSize; i++) {
            pActualData[i]= out[i];
        }
        if (hres=SafeArrayUnaccessData(pSA)) throw hres;
        m_piResponse->BinaryWrite(vOut);
    catch(...) {
    }// Clean Up
    delete [] out;
    VariantClear(&vOut); // calls SafeArrayDestroy!
    return hres;
    }
     Have fun,
    Jeff
      

  4.   

    Jeff,Thank you so much for ur reply.
    But I just need the OCX working on .thm file,so I can not create any object.
    such as, var objArray= Server.CreateObject("JALArray.Array")
    I was wondering is there any other way?
    Thx again