我正在使用Excel-DNA在Excel中开发一些UDF。从Excel传递到我的UDF的一个参数是一个范围。当使用特定范围时,UDF可以正常工作,例如" A1:C50"。以下是我的函数定义示例:
[ExcelCommand()]
public static object CalcSMA(object[,] range, int num_points) {
...
}
然而,我得到了一个" Out Of Memory"传递整个列范围时出错,例如" A:C"。我可以通过设置参数属性AllowReference = true来避免错误,并将参数类型更改为object,如下例所示:
[ExcelCommand()]
public static object CalcSMA([ExcelArgument("Range", AllowReference=true)]object range, int num_points) {
ExcelReference xref = (ExcelReference)range;
...
}
但现在我不知道UDF实际需要多少行。我可以尝试迭代工作表中的所有行,但这是非常低效的。有没有办法根据使用的范围剪切ExcelReference(外部参照)?我想避免使函数volatile(IsMacroType = true),但如果需要则会这样做。
答案 0 :(得分:2)
在VBA(或COM)中,您可以将Range参数与Range参数的Parent的UsedRange相交。但是在XLL中,获取使用范围并不简单,因为XLL接口不为工作表提供UsedRange方法。所以你必须使用COM接口(这在XLL UDF中是有问题的)。我构建了一个使用AfterCalculate事件来缓存每个工作表的使用范围的例程。
这里有一些关于这样做的讨论 https://fastexcel.wordpress.com/2014/09/26/getting-used-range-in-an-xll-udf-multi-threading-and-com/
请注意,如果您愿意将UDF设置为单线程宏类型UDF,则可以使用GETDOCUMENT(10)XLL api。但痛苦可能不值得获得。
答案 1 :(得分:2)
根据Charles和Govert的建议,我最终实施了以下内容:
public class UsedRangeCache
{
protected static Dictionary<IntPtr, ExcelReference> _usedRanges = new Dictionary<IntPtr, ExcelReference>();
protected static Application _app;
/// <summary>
/// Call this method when the XLL is initialized
/// </summary>
public static void Initialize(Application app)
{
_app = app;
for (int i = 0; i < app.Workbooks.Count; i++ )
{
app_WorkbookOpen(app.Workbooks[i + 1]);
}
app.WorkbookOpen += app_WorkbookOpen;
app.WorkbookBeforeClose += app_WorkbookBeforeClose;
app.AfterCalculate += app_AfterCalculate;
}
// Refresh references
static void app_AfterCalculate()
{
for (int i = 0; i < _app.Workbooks.Count; i++)
{
UpdateCache(_app.Workbooks[i + 1]);
}
}
// Remove references
static void app_WorkbookBeforeClose(Workbook book, ref bool Cancel)
{
for (int i = 0; i < book.Worksheets.Count; i++)
{
Worksheet sheet = book.Worksheets[i + 1] as Worksheet;
if (sheet != null)
{
ExcelReference xref = (ExcelReference)XlCall.Excel(XlCall.xlSheetId, sheet.Name);
if (_usedRanges.ContainsKey(xref.SheetId))
{
_usedRanges.Remove(xref.SheetId);
}
}
}
}
// Create references
static void app_WorkbookOpen(Workbook book)
{
UpdateCache(book);
}
// Update cache
private static void UpdateCache(Workbook book)
{
for (int i = 0; i < book.Worksheets.Count; i++)
{
Worksheet sheet = book.Worksheets[i + 1] as Worksheet;
if (sheet != null)
{
ExcelReference xref = (ExcelReference)XlCall.Excel(XlCall.xlSheetId, sheet.Name);
ExcelReference xused = new ExcelReference(
sheet.UsedRange.Row,
sheet.UsedRange.Row + sheet.UsedRange.Rows.Count,
sheet.UsedRange.Column,
sheet.UsedRange.Column + sheet.UsedRange.Columns.Count,
xref.SheetId);
if (_usedRanges.ContainsKey(xref.SheetId))
{
_usedRanges.Remove(xref.SheetId);
}
_usedRanges.Add(xref.SheetId, xused);
}
}
}
/// <summary>
/// Get used range
/// </summary>
public static ExcelReference GetUsedRange(ExcelReference xref)
{
ExcelReference ret = null;
_usedRanges.TryGetValue(xref.SheetId, out ret);
return ret;
}
}