Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 43 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
43
Dung lượng
823,04 KB
Nội dung
66 Excel Add-in Development in C/C++ 8. Return the array Variant to VB from C/C++. 9. Return the array Variant to Excel from VB. The following sub-sections cover in more detail the various steps involved as well as providing more background information. 3.7.1 Declaring VB arrays and passing them back to Excel VB arrays are fairly straightforward. They can be declared statically with statements such as these: Dim integer_array(0 To 5) As Integer ’ 6 elements, zero-indexed Dim square_array(1 To 3, 1 To 3) As Double ’ 9 elts, unit-indexed Dim variant_array(1 to 4) As Variant ’ 4 Variant elts The Option Base statement at the top of the code module tells VB what the lower bound on an omitted array index should be for all arrays in that module. For example ’ Specify a default array lower bound of 1 Option Base 1 then the array square_array above can, equivalently but more readably, be declared as follows: Dim square_array(3, 3) As Double ’ 9 elements, unit-indexed Arrays can also be declared without dimensions and then re-dimensioned dynamically later. A data type must be specified at declaration and cannot be changed. Here’s an example: ’ Don’t need to specify the number of or size of the dimensions Dim array() As Double ’ Allocate space for NumRows x NumCols elements ReDim array(NumRows, NumCols) Arrays can be declared with up to 60 dimensions, but for practical Excel add-in purposes, 1 or 2 is usually all you need given the two-dimensional nature of Excel worksheets. Arrays are most easily returned to Excel as array Variants as shown in the following examples. The conversion from VB array to array Variant is implicit in the assignment of the array to the Variant return value. The type of the array elements is inherited from the data type of the VB array. Excel understands how to copy the contents of array Variants into the calling cell(s). Note that these VB functions would need to be entered on the worksheet as array formulae. (See section 2.9.2 Array formulae – The Ctrl-Shift-Enter keystroke on page 21 for details of how to enter array formulae into a worksheet.) Note also that a 1-dimension VB array is interpreted by Excel as a single column vector, and that a 2-dimension array has its indices interpreted as row then column. Using VBA 67 Returning a rectangular array This example returns a 3x3 array of integers, populated row-by-row with the numbers 1to9. Function VB_ARRAY_RETURN_EXAMPLE(trigger as Variant) As Variant ’ a(num rows, num columns) Dim a(1 To 3, 1 To 3) As Integer ’ Row 1 a(1, 1) = 1 a(1, 2) = 2 a(1, 3) = 3 ’ Row 2 a(2, 1) = 4 a(2, 2) = 5 a(2, 3) = 6 ’ Row 3 a(3, 1) = 7 a(3, 2) = 8 a(3, 3) = 9 VB_ARRAY_RETURN_EXAMPLE = a End Function Returning a row vector To return a row vector, the array, if static, should be declared as in this example. Note that the base in this example is zero, not 1. It makes no difference to the worksheet cells what the base of the array is, provided that there are 3 elements. Function VB_ROW_VECTOR(trigger As Variant) As Variant Dim a(0 To 2) As Integer a(0) = 1 a(1) = 2 a(2) = 3 VB_ROW_VECTOR = a End Function Returning a column vector To return a column vector, the array, if static, should be declared as in this example: Function VB_COLUMN_VECTOR(trigger As Variant) As Variant ’ a(num rows, num columns) Dim a(1 To 3, 1 To 1) As Integer 68 Excel Add-in Development in C/C++ a(1, 1) = 1 a(2, 1) = 2 a(3, 1) = 3 VB_COLUMN_VECTOR = a End Function 3.7.2 Passing arrays and ranges from Excel to VB to C/C++ Arrays in Excel can either be literal arrays, e.g., {1,2,3;4,5,6}, or range references. A VB function must be declared as taking a Variant argument if it is to be able to accept either form of input. (Such functions can then also accept single cell references and single literal values too.) Literal arrays are passed as array Variants with Variant elements. The sub-types are inherited from the types of the literal array elements. (Single literal values are passed as simple Variants whose sub-type is that of the literal value.) Range references, including single cell references, are passed as object Variants of type VT_DISPATCH; easily detected using the function IsObject().Ifthesearetobe passed on to a C/C++ DLL function, they are best converted to array Variants. This is most easily done using the Range object’s Value property. The array’s elements are initialised with the data from the cells. The elements of the array are type Variant, and their sub-type is inherited from the corresponding cell. Note that the sub-type of an array element is, in general, affected by the display format of a cell – see section 3.6.4 on page 50 for details. The following code shows an example VB interface function that either passes a single Variant or an array Variant to a DLL function, depending on whether it was passed a range reference or a literal array or value. Note that a single-cell reference is converted to a 1x1 array. Declare Function C_vt_function Lib "example.dll" _ (ByRef arg As Variant) As Variant Function VtFunction(v As Variant) As Variant If IsObject(v) Then VtFunction = C_vt_function(v.Value) Else VtFunction = C_vt_function(v) End If End Function The C/C++ DLL function would be prototyped as follows: VARIANT __stdcall C_vt_function(VARIANT *pv); Using VBA 69 A VB interface function declared as taking a range argument, would not be able to receive literal values from the worksheet. If this were not a problem, then the VB code might look like this, given that there is no need to call IsObject(). Function VtFunction(r As Range) As Variant VtFunction = C_vt_function(r.Value) End Function The following line would have resulted in a Variant of type VT_DISPATCH being passed to the DLL function. VtFunction = C_vt_function(r) 3.7.3 Converting array Variants to and from C/C++ types Array Variants are Variants that contain an array. The array itself is an OLE data type called the SafeArray, declared as SAFEARRAY in the Windows header files. An under- standing of the internal workings of the SAFEARRAY is not necessary to bridge between VB and C/C++. All that’s required is a knowledge of some of the functions used to create them, obtain handles to their data, release data handles, find out their size (upper and lower bounds), find out what data-type the array contains, and, finally, destroy them. The key functions, all accessible in C/C++ via the header windows.h,are: SafeArrayCreate() SafeArrayDestroy() SafeArrayAccessData() SafeArrayUnaccessData() SafeArrayGetDim() SafeArrayGetElemsize() SafeArrayGetLBound() SafeArrayGetUBound() SafeArrayGetElement() SafeArrayPutElement() To convert an array Variant, the C/C++ DLL code needs to do the following: • Determine that the Variant is an array by testing its type for the VT_ARRAY bit. • Determine the element type by masking the VT_ARRAY bit from its type. • Determine the number of dimensions using the SafeArray cDims property or by using the SafeArrayGetDim() function. • Determine the size of the array using SafeArrayGetUBound() and SafeAr- rayGetLBound() for each dimension. • Convert each array element from the possible Variant types that could originate from a worksheet cell to the desired data type(s). 70 Excel Add-in Development in C/C++ To create an array Variant, the C/C++ DLL code needs to do the following: • Call SafeArrayCreate(), having initialised an array of SAFEARRAYBOUND struc- tures (one for each dimension), to obtain a pointer to the SafeArray. • Initialise a VARIANT using VariantInit(). • Assign the element type bit-wise or’d with VT_ARRAY to the Variant type. • Assign the SafeArray pointer to the Variant parray data member. • Set the array element data (and sub-types, if Variants). The final points in each set of steps above can be done element-by-element using SafeAr- rayGetElement() and SafeArrayPutElement(), or, more efficiently, by accessing the whole array in one memory block using SafeArrayAccessData() and SafeAr- rayUnaccessData() . When accessing the whole block in one go, it should be borne in mind that SafeArrays store their elements column-by-column, in contrast to Excel’s C API array types, the xl_array (see page 107) and the xltypeMulti xloper (see page 111), where the elements are stored row-by-row. Array Variant arguments passed by reference can be modified in place, provided that the passed-in array is first released using SafeArrayDestroy() before being replaced with the array to be returned. The cpp xloper class converts Variants of any type to or from an equivalent xloper type. (See sections 6.2.3 The xloper structure on page 111, and 6.4 AC++class wrapper for the xloper – cpp xloper on page 121. See also the Variant conversion routines in the example project source file, xloper.cpp.) The following example code demonstrates this: VARIANT __stdcall C_vt_array_example(VARIANT *pv) { static VARIANT vt; // Convert the passed-in Variant to an xloper within a cpp_xloper cpp_xloper Array(pv); // Access the elements of the xloper array using the cpp_xloper // accessor functions // Convert the xloper back to a Variant and return it Array.AsVariant(vt); return vt; } Note on memory management One advantage of passing Variant SafeArrays back to VB is that VB can safely delete the array and free its resources, and will do this automatically once it has finished with it. Equally, if a passed-in array parameter is used as the means to return an array, and an array is already assigned to it, the DLL must delete the array using SafeArrayDestroy()before creating and returning a new one. (The freeing of memory passed back to Excel directly from an XLL is a little more complex – see Chapter 7 Memory Management on page 161 for details.) 3.7.4 Passing VB arrays to and from C/C++ You may want to pass a VB array directly to or from a DLL function. When passing a VB array to a DLL, the C/C++ function should be declared in the VB module as shown in the following example. (The ByRef keyword is not required as it is the default.) Using VBA 71 Declare Function C_safearray_example "example.dll" _ (ByRef arg() As Double) As Double The corresponding C/C++ function would be prototyped as follows: double __stdcall C_SafeArray_Example(SAFEARRAY **pp_Arg); As you can see, the parameter ByRef arg() is delivered as a pointer to a pointer to a SAFEARRAY. Therefore it must be de-referenced once in all calls to functions that take pointers to SAFEARRAYs as arguments, for example, the OLE SafeArray functions. When returning VB arrays (i.e., SafeArrays) from the DLL to VB, the process is similar to that outlined in the previous sections for array Variants. SafeArray arguments passed by reference can also be modified in place, provided that the passed-in array is first released using SafeArrayDestroy(). In practice, once you have code that accepts and converts array Variants, it is simpler to first convert the VB array to array Variant. This is done by simple assignment of the array name to a Variant. 3.8 COMMANDS VERSUS FUNCTIONS IN VBA Section 2.8 Commands versus functions in Excel on page 19 describes the differences between commands and functions within Excel. The differences between the parallel concepts of commands and functions in VBA are summarised in the Table 3.10. Ta ble 3.10 Commands versus functions in VBA Commands Functions Purpose Code containing instructions to be executed in response to a user action or system event. Code intended to process arguments and/or return some useful information. May be worksheet functions or VB functions. VB code (see also sections below) Macro command: Sub CommandName( ) End Sub Command object event: Sub CmdObjectName event( ) End Sub Workbook/worksheet event action: Sub ObjectName event( ) End Sub Function FunctionName( )As return type FunctionName = rtn val End Function (continued overleaf ) 72 Excel Add-in Development in C/C++ Ta ble 3.10 (continued) Commands Functions VB code location Macro command: • Worksheet code object • Workbook code object • VB module in workbook • VB module outside workbook Command object event: • Code object of command object container Worksheet object event: • Worksheet code object Workbook object event: • Workbook code object Worksheet function: • VB module in workbook • VB module outside workbook VB project function: • Worksheet code object • Workbook code object • VB module in workbook • VB module outside workbook 3.9 CREATING VB ADD-INS (XLA FILES) VB macros can be saved as Excel add-ins simply by saving the workbook containing the VB modules as an XLA file, using the File/Save As menu and selecting the file type of Microsoft Excel Add-in (*.xla). When the XLA is loaded, the Add-in Manager makes the functions and commands contained in the XLA file available. There are no special things that the VB programmer has to do for the Add-in Manager to be able to recognise and load the functions. Note that the resulting code runs no faster than regular VB modules – still slower than, say, a compiled C add-in. 3.10 VB VERSUS C/C++: SOME BASIC QUESTIONS This chapter has outlined what you need to do in order to create custom worksheet functions and commands using only VB (as well as using VB as an interface to a C/C++ DLL). You might at this point ask yourself if you need to go any further in the direction of a full-blown C/C++ add-in. Breaking this down, the main questions to ask yourself before making this decision are: 1. Do I really need to write my own functions or are there Excel functions that, either on their own or in simple combination, will do what I need? 2. What Excel functionality/objects do I need to access: can I do this using the C API, or do I need to use VBA or the OLE interface? 3. Is execution speed important? 4. What kind of calculations or operations will my function(s) consist of and what kind of performance advantage can I expect? 5. Is development time important to me and what language skills do I have or have access to? 6. Is there existing source code that I want to reuse and how easily can it be ported to any of VB, C or C++? 7. Does my algorithm involve complex dynamic memory management or extensive use of pointers? Using VBA 73 8. Who will need to be able to access or modify the resulting code? 9. Is the Paste Function (Function Wizard) important for the functions I want to create? 10. Do I need to write worksheet functions that might need a long time to execute, and so need to be done on a background thread by a remote application? With regard to the second point, it should be noted that C API can only handle byte- counted strings with a maximum length of 255 characters. At one time, strings within Excel were limited to this length, but not any more. If you need to be able to process longer strings you will not be able to use the C API, but you will still be able to use your C/C++ routines accessing them via VB, as VB supports a BSTR string variable capable of supporting much longer strings. This book cannot answer these questions for you, however, question 4 is addressed in section 9.2 Relative performance of VB, C/C++: Tests and results on page 289. 4 Creating a 32-bit Windows (Win32) DLL Using Visual C++ 6.0 or Visual Studio .NET This chapter covers the steps involved in creating a stand-alone Win32 Dynamic-Link Library using Microsoft Visual C++. It explains, through the creation of an example project, how to create a DLL containing functions that can be accessed by VB without the need for the Excel C API library and header files. (Without these things, however, the DLL cannot call back into Excel via the C API.) Nevertheless, it is possible to create very powerful C/C++ add-ins with just these tools. A full description of DLLs and all the associated Windows terminology is beyond the scope of this book. Instead, this section sets out all the things that someone who knows nothing about DLLs needs to know to create add-ins for Excel; starting with the basics. 4.1 WINDOWS LIBRARY BASICS A library is a body of (compiled) code which is not in itself an executable application but provides some functionality and data to something that is. Libraries come in two flavours: static and dynamic-link. Static libraries (such as the C run-time library) are intended to be linked to an application when it is built, to become part of the resulting executable file. Such an application can be supplied to a user as just the executable file only. A dynamic-link library is loaded by the application when the application needs it, usually when the application starts up. An application that depends on functionality or data in a DLL must be shipped to a user as the executable file plus the DLL file for it to work. One DLL can load and dynamically link to another DLL. The main advantage of a DLL is that applications that use it only need to have one copy of it somewhere on disk, and have much smaller executable files as a result. A developer can also update a DLL, perhaps fixing a bug or making it more efficient, without the need to update all the dependent applications, provided that the interface doesn’t change. 4.2 DLL BASICS The use of DLLs breaks into two fairly straightforward tasks: • How to write a DLL that exports functions. • How to access functions within a DLL. DLLs contain executable code but are not executable files. They need to be linked to (or loaded by) an application before any of their code can be run. In the case of Excel, that linking is taken care of by Excel via the Add-in Manager or by VBA, depending on [...]... nearest second 88 Excel Add- in Development in C/ C++ 1 Open the Visual C+ + NET IDE which should appear something like this: 2 On the New Project dialog that appears, select the Win32 folder 3 Select Win32 Project and enter a name for the project in the Name: text box and select a location for the project as shown and press OK Creating a 32 -bit Windows (Win32) DLL Using VC 6.0 or VS NET 4 The following dialog... whose functions you want to access directly from an Excel worksheet, you will need Excel s C API library and header file, or COM (see section 9.5) (See also section 4.12 below, and Chapter 5 Turning DLLs into XLLs: The Add- in Manager interface on page 94.) Creating a 32 -bit Windows (Win32) DLL Using VC 6.0 or VS NET 83 4.9 CREATING A DLL USING VISUAL C+ + 6.0 This section refers to Visual C+ + 6.0 as VC Visual... the Excel session in which the add- in is first loaded is terminated with the add- in inactive, Excel will not record the fact that the add- in was ever loaded and, in the next session, the add- in will need to be loaded from scratch to be accessible If the Excel session was terminated with the add- in active then a record is made in the registry Even if subsequent sessions are terminated with the add- in inactive... xlAutoClose xlAutoAdd xlAutoRemove xlAddInManagerInfo xlAutoRegister xlAutoFree The following sections describe these functions, which can be omitted in most cases (Note: These functions need to be exported, say, by inclusion in the DLL’s DEF file, in order to be accessible by Excel. ) 5.4 WHEN AND IN WHAT ORDER DOES EXCEL CALL THE XLL INTERFACE FUNCTIONS? Table 5.1 XLL interface function calling Action... xlAutoOpen User loads the add- in for the first time xlAddInManagerInfo xlAutoAdd xlAutoOpen User starts Excel with the add- in already installed in previous session xlAutoOpen User closes Excel with the add- in installed but deactivated No calls made User closes Excel with the add- in installed and activated xlAutoClose xlAddInManagerInfo User starts to close Excel but cancels when prompted to save their... xlAutoClose 98 Excel Add- in Development in C/ C++ Note: If the user starts to close Excel, causing a call to xlAutoClose, but then cancels when prompted to save their work, Excel does not then call any of the xlAuto functions to reinitialise the add- in Even if xlAutoClose attempts to unregister the worksheet functions, a bug in the C API prevents this from being successful Therefore Excel continues... can integrate them – a process know as registration, covered in detail in section 8.5 Registering and un-registering DLL (XLL) functions on page 182 5 Turning DLLs into XLLs: The Add- in Manager Interface 5.1 ADDING THE EXCEL LIBRARY AND HEADER FILES TO A DLL PROJECT An XLL is simply a DLL that supports an interface through which Excel and the DLL can communicate effectively and safely This communication... taken place For this reason it is advisable to place your initialisation code into a single function and check in all the required places that this initialisation has occurred, using a global variable A satisfactory approach is to check in both xlAddInManagerInfo and xlAutoAdd, and to call xlAutoOpen explicitly if the add- in has not been initialised As well as being the place where all the initialisation... Functions called User invokes Add- in Manager dialog for the first time in this Excel session The add- in was loaded in previous session xlAddInManagerInfo In the Add- in Manager dialog, the user deactivates (deselects) the add- in and then closes the dialog xlAutoRemove xlAutoClose In the Add- in Manager dialog, the user activates the add- in and then closes the dialog xlAutoAdd xlAutoOpen User loads the add- in. ..76 Excel Add- in Development in C/ C++ how you access the DLL’s functions (Chapter 5 Turning DLLs into XLLs: The Add- in Manager interface, on page 95, provides a full explanation of what the Add- In Manager does.) If your DLL needs to access the C API it will either need to be linked statically at compile-time with Excel s 32 -bit library, xlcall32.lib, or link dynamically with the DLL version, xlcall.dll, . FunctionName = rtn val End Function (continued overleaf ) 72 Excel Add- in Development in C/ C++ Ta ble 3. 10 (continued) Commands Functions VB code location Macro command: • Worksheet code object •. parallel concepts of commands and functions in VBA are summarised in the Table 3. 10. Ta ble 3. 10 Commands versus functions in VBA Commands Functions Purpose Code containing instructions to be executed. by Excel via the Add- in Manager or by VBA, depending on 76 Excel Add- in Development in C/ C++ how you access the DLL’s functions. (Chapter 5 Turning DLLs into XLLs: The Add- in Manager interface,