Click here to Skip to main content
15,896,063 members
Articles / Programming Languages / SQL

Excel user defined functions unlimited

Rate me:
Please Sign up or sign in to vote.
4.81/5 (15 votes)
30 Nov 200619 min read 223.4K   5.7K   77  
Describes how to return tables of values as the result of user defined functions (UDF) in Excel.
//---------------------------------------------------------------------------
//  Excel Multivalue Formula Add-In
//  Copyright (C) <2005> <Herbert Danler>
//  Contact: danler@users.sourceforge.net
//  Project Home Page: http://excelmvf.sourceforge.net/
//
//  This program is free software; you can redistribute it and/or modify it under
//  the terms of the GNU General Public License as published by the Free Software
//  Foundation; either version 2 of the License, or (at your option) any
//  later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
//  or FITNESS FOR A PARTICULAR PURPOSE.
//  See the GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License along with
//  this program; if not, write to the Free Software Foundation, Inc.,
//  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//---------------------------------------------------------------------------


#include <vcl.h>
#include <windows.h>
#include <string>
#include "excelmvf.h"
#include "TRequestController.h"
#include "TExcelEventDispatcher.h"
#include "TSgADOConnection.h"
#include "TRangeSize.h"
#pragma hdrstop

#pragma argsused

//global constants
const char* const DBFILENAME="excelmvf.mdb";
// global variables
TRequestController requestController;
TExcelEventDispatcher ExcelEventDispatcher;


int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
    switch(reason){
        case DLL_PROCESS_ATTACH:{
                // We don't need thread notifications for what we're doing.  Thus, get
                // rid of them, thereby eliminating some of the overhead of this DLL
                DisableThreadLibraryCalls( hinst );

                //this add-in assumes that the DB-File is in the same directory as the dll
                //build connection string for ADO-datasource
                char dllfilename[_MAX_DRIVE + _MAX_DIR +_MAX_FNAME +_MAX_EXT], drive[_MAX_DRIVE],dir[_MAX_DIR];
                char mbsdatasoure[_MAX_DRIVE + _MAX_DIR +_MAX_FNAME +_MAX_EXT];
                wchar_t wcsdatasource[_MAX_DRIVE + _MAX_DIR +_MAX_FNAME +_MAX_EXT];
                std::wstring connectionString =L"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=";
                GetModuleFileName(hinst,dllfilename,_MAX_DRIVE + _MAX_DIR +_MAX_FNAME +_MAX_EXT);
                _splitpath(dllfilename,drive,dir,NULL,NULL);
                _makepath(mbsdatasoure,drive,dir,DBFILENAME,NULL);
                mbstowcs(wcsdatasource, mbsdatasoure,strlen(mbsdatasoure)+1);
                connectionString.append(wcsdatasource);
                connectionString.append(L";Persist Security Info=False");

                //set the connection string for the datasource
                TSgADOConnection::setConnectionString(connectionString.c_str());

                //for hard coded datasource all building stuff above can be eliminated
                //and the following used instead
                //TSgADOConnection::setConnectionString(L"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=""D:\\develop\\Experimente\\Artikel Exel Multivalue Formula\\orderManagement.mdb"";Persist Security Info=False");
                //the following Connection string would fit for a OLE-ODBC-Bridge
                //TSgADOConnection::setConnectionString(L"Provider=MSDASQL.1;Password=excelmvf;Persist Security Info=True;User ID=excelmvf;Data Source=excelmvf");
        }

        break;
        case DLL_PROCESS_DETACH:

            break;
        default:
                NULL;
    }
    
    return 1;
}

extern "C" {

void __export __stdcall setExcelApp(VARIANT pExcelApp){
        ExcelEventDispatcher.Connect(pExcelApp.pdispVal);
}

VARIANT __export __stdcall getOrderItems_dll(TDateTime pstartdate, TDateTime penddate, char* pfieldlist, char* psortcriterias, bool pheader, VARIANT pcell)
{

        bool rangeSizechanged=false;
        VARIANT retval,result;
        VARIANT callingCellAddress,callingCellWorksheet,callingCellFormula,callingCellRow,callingCellCol;
        VARIANT excelColRange,excelrangeNumCols, excelRowRange, excelrangeNumRows;
        HRESULT hr;

        HWND funcparamWindow = 0;
        //the function argument window is of class bosa_sdm_XL9. If a window of this class is found the user enters
        //arguments in this window. In this case we always return #NA
        funcparamWindow = FindWindow("bosa_sdm_XL9",NULL);
        if (funcparamWindow){
            retval.vt = VT_ERROR;
            retval.scode=excelmvf::VALNOTAVAILABLE;
            return retval;
        }

        //read values we need to know for storage in the FuncParam variable to later access this information
        //in the ExcelEventHandler
        TComHelper::AutoWrap(DISPATCH_PROPERTYGET,&callingCellAddress,pcell.pdispVal,L"Address",0);
        TComHelper::AutoWrap(DISPATCH_PROPERTYGET,&callingCellFormula,pcell.pdispVal,L"Formula",0);
        TComHelper::AutoWrap(DISPATCH_PROPERTYGET,&callingCellCol,pcell.pdispVal,L"Column",0);
        TComHelper::AutoWrap(DISPATCH_PROPERTYGET,&callingCellRow,pcell.pdispVal,L"Row",0);
        TComHelper::AutoWrap(DISPATCH_PROPERTYGET,&callingCellWorksheet,pcell.pdispVal,L"Worksheet",0);

        //query current range and determine number of cols and rows
        //if pcell is part of an array formula, Count delivers size of array
        TComHelper::AutoWrap(DISPATCH_PROPERTYGET,&excelColRange,pcell.pdispVal,L"Columns",0);
        TComHelper::AutoWrap(DISPATCH_PROPERTYGET,&excelrangeNumCols,excelColRange.pdispVal,L"Count",0);
        TComHelper::AutoWrap(DISPATCH_PROPERTYGET,&excelRowRange,pcell.pdispVal,L"Rows",0);
        TComHelper::AutoWrap(DISPATCH_PROPERTYGET,&excelrangeNumRows,excelRowRange.pdispVal,L"Count",0);

        //fetch data
        const TOrderItemDataset* dataset = requestController.getOrderItems(pstartdate,penddate,std::string(psortcriterias ? psortcriterias :""));
        //if the formula is returned in an Array of Variants (MultiArrayFormula) then read the formula from the first element of the array
        if (callingCellFormula.vt == (VT_ARRAY | VT_VARIANT)){
            //it's an 2-dimensional array of Variants (dim1=rows, dim2 cols)
            //Lbounds of Dimensions start with 1
            long indices[] = {1,1};
            hr = SafeArrayGetElement(callingCellFormula.parray,indices ,&callingCellFormula);
        }
        //compare the size of the range with the required size
        if (dataset!=NULL && !dataset->isempty() && dataset->isvalid() ){
            TRangeSize neededRangeSize = dataset->getRangeSize(std::string(pfieldlist ? pfieldlist:""),pheader);
            if(neededRangeSize.rows != excelrangeNumRows.lVal || neededRangeSize.cols != excelrangeNumCols.lVal)
                rangeSizechanged=true;
        }
        else
            rangeSizechanged = false;
        //if the function call was not invoked by the ExcelEventDispatchers calculate-event
        //and we are working on the active cell, store all function parameters for later
        //processing in ExcelEventDispatchers calculate event
        if (TRequestController::funcParams.getCalculating()==false && rangeSizechanged == true){
            //store parameters for later processing in the Calculate Event

             TRequestController::funcParams.setFuncParams(pstartdate,
                                   penddate,
                                   std::string(pfieldlist ? pfieldlist : ""),
                                   std::string(psortcriterias ? psortcriterias :""),
                                   pheader,
                                   excelmvf::getOrderItems,
                                   callingCellFormula,
                                   callingCellAddress,
                                   callingCellCol,
                                   callingCellRow,
                                   excelrangeNumCols,
                                   excelrangeNumRows,
                                   callingCellWorksheet
                                   );
            TRequestController::funcParams.setState(true);
            
            //the correct values are retrieved by a second function call triggered by the Calculate Event
            //in the meantime we return N/A
            retval.vt = VT_ERROR;
            retval.scode=excelmvf::VALNOTAVAILABLE;
            return retval;
        }


        //the function was either called by automatic calculation or after rewriting the formula in the Calculate-Event
        //which triggers a further function call. Return the results in an Array
        TRequestController::funcParams.setCalculating(false);

        if (dataset==NULL || dataset->isempty() || (dataset->isvalid() == false)){
                
                retval.vt=VT_ERROR;
                retval.scode=excelmvf::VALNOTAVAILABLE;

                return retval;
        }
        retval = dataset->getResultArray(std::string(pfieldlist ? pfieldlist : ""),
                                             pheader
                                             );

        return retval;

}

////////////////////////////////////////////////////////////////////////
// Name:       getCustomers_dll()
// Purpose:    Implementation of getCustomers_dll()
// Return:     VARIANT
////////////////////////////////////////////////////////////////////////
VARIANT __export __stdcall getCustomers_dll( char* pfieldlist, char* psortcriterias, bool pheader, VARIANT pcell)
{

        bool rangeSizechanged=false;
        VARIANT retval,result;
        VARIANT callingCellAddress,callingCellWorksheet,callingCellFormula,callingCellRow,callingCellCol;
        VARIANT excelColRange,excelrangeNumCols, excelRowRange, excelrangeNumRows;
        HRESULT hr;

        HWND funcparamWindow = 0;
        //the function argument window is of class bosa_sdm_XL9. If a window of this class is found the user enters
        //arguments in this window. In this case we always return #NA
        funcparamWindow = FindWindow("bosa_sdm_XL9",NULL);
        if (funcparamWindow){
            retval.vt = VT_ERROR;
            retval.scode=excelmvf::VALNOTAVAILABLE;
            return retval;
        }

        //read values we need to know for storage in the FuncParam variable to later access this information
        //in the ExcelEventHandler
        TComHelper::AutoWrap(DISPATCH_PROPERTYGET,&callingCellAddress,pcell.pdispVal,L"Address",0);
        TComHelper::AutoWrap(DISPATCH_PROPERTYGET,&callingCellFormula,pcell.pdispVal,L"Formula",0);
        TComHelper::AutoWrap(DISPATCH_PROPERTYGET,&callingCellCol,pcell.pdispVal,L"Column",0);
        TComHelper::AutoWrap(DISPATCH_PROPERTYGET,&callingCellRow,pcell.pdispVal,L"Row",0);
        TComHelper::AutoWrap(DISPATCH_PROPERTYGET,&callingCellWorksheet,pcell.pdispVal,L"Worksheet",0);

        //query current range and determine number of cols and rows
        //if pcell is part of an array formula, Count delivers size of array
        TComHelper::AutoWrap(DISPATCH_PROPERTYGET,&excelColRange,pcell.pdispVal,L"Columns",0);
        TComHelper::AutoWrap(DISPATCH_PROPERTYGET,&excelrangeNumCols,excelColRange.pdispVal,L"Count",0);
        TComHelper::AutoWrap(DISPATCH_PROPERTYGET,&excelRowRange,pcell.pdispVal,L"Rows",0);
        TComHelper::AutoWrap(DISPATCH_PROPERTYGET,&excelrangeNumRows,excelRowRange.pdispVal,L"Count",0);

        //fetch data
        const TCustomerDataset* dataset = requestController.getCustomers(std::string(psortcriterias ? psortcriterias :""));
        //if the formula is returned in an Array of Variants (MultiArrayFormula) then read the formula from the first element of the array
        if (callingCellFormula.vt == (VT_ARRAY | VT_VARIANT)){
            //it's an 2-dimensional array of Variants (dim1=rows, dim2 cols)
            //Lbounds of Dimensions start with 1
            long indices[] = {1,1};
            hr = SafeArrayGetElement(callingCellFormula.parray,indices ,&callingCellFormula);
        }
        //compare the size of the range with the required size
        if (dataset!=NULL && !dataset->isempty() && dataset->isvalid() ){
            TRangeSize neededRangeSize = dataset->getRangeSize(std::string(pfieldlist ? pfieldlist:""),pheader);
            if(neededRangeSize.rows != excelrangeNumRows.lVal || neededRangeSize.cols != excelrangeNumCols.lVal)
                rangeSizechanged=true;
        }
        else
            rangeSizechanged = false;
        //if the function call was not invoked by the ExcelEventDispatchers calculate-event
        //and we are working on the active cell, store all function parameters for later
        //processing in ExcelEventDispatchers calculate event
        if (TRequestController::funcParams.getCalculating()==false && rangeSizechanged == true){
            //store parameters for later processing in the Calculate Event
            TRequestController::funcParams.setFuncParams(NULL,
                                   NULL,
                                   std::string(pfieldlist ? pfieldlist : ""),
                                   std::string(psortcriterias ? psortcriterias :""),
                                   pheader,
                                   excelmvf::getCustomers,
                                   callingCellFormula,
                                   callingCellAddress,
                                   callingCellCol,
                                   callingCellRow,
                                   excelrangeNumCols,
                                   excelrangeNumRows,
                                   callingCellWorksheet
                                   );
            TRequestController::funcParams.setState(true);

            //the correct values are retrieved by a second function call triggered by the Calculate Event
            //in the meantime we return N/A
            retval.vt = VT_ERROR;
            retval.scode=excelmvf::VALNOTAVAILABLE;
            return retval;
        }

        //the function was either called by automatic calculation or after rewriting the formula in the Calculate-Event
        //which triggers a further function call. Return the results in an Array
        TRequestController::funcParams.setCalculating(false);

        if (dataset==NULL || dataset->isempty() || (dataset->isvalid() == false)){

                retval.vt=VT_ERROR;
                retval.scode=excelmvf::VALNOTAVAILABLE;

                return retval;
        }
        retval = dataset->getResultArray(std::string(pfieldlist ? pfieldlist : ""),
                                             pheader
                                             );

        return retval;
}

} //extern "C"
//---------------------------------------------------------------------------

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior)
Austria Austria
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions