Reputation: 83
Excel shows #VALUE!
when the my UDF returns more than 255 chars string.
xlwings is 0.7.1 and excel is 2007 which, as per Microsoft, can contain up to 32767 chars in a cell.
Where could be the problem?
Upvotes: 0
Views: 449
Reputation: 83
Based on @schoolie's suggestion above of converting 2D Variant array to 2D String array, I modified the source of VBA function generation logic in my local xlwings:
In udfs.generate_vba_wrapper()
replace:
vba.write('{fname} = Py.CallUDF("{module_name}", "{fname}", {args_vba}, ThisWorkbook)\n',
module_name=module_name,
fname=fname,
args_vba=args_vba,
)
with:
vba.write('r = Py.CallUDF("{module_name}", "{fname}", {args_vba}, ThisWorkbook)\n',
module_name=module_name,
fname=fname,
args_vba=args_vba,
)
vba.write('ReDim strarray(UBound(r, 1), UBound(r, 2)) As String\n')
vba.write('For i = 0 To UBound(r, 1)\n')
vba.write(' For j = 0 To UBound(r, 2)\n')
vba.write(' strarray(i, j) = CStr(r(i, j))\n')
vba.write(' Next\n')
vba.write('Next\n')
vba.write('{fname} = strarray\n', fname=fname)
The other option is to patch the generated VB macro in VB editor after doing an 'Import Python UDFs'. However This change will be lost if you reimport. Code is already given above by @schoolie
Upvotes: 2
Reputation: 462
As best I can tell, Py.CallUDF (used by xlwings udfs) returns a 2D Variant array.
It also appears that for some reason returning a Variant array with string lengths greater than 255 from a pure VBA UDF results in a #VALUE error when called in excel. Placing a watch on the array in the VBA editor shows the data is intact, it's just not getting passed to excel correctly. A little searching returned several questions around max string lengths in VBA, but nothing that specifically addressed this issue.
Returning String arrays or single Strings with > 255 characters appears to work fine though.
Here are a few pure VBA examples showing the problem:
Return Variant Array:
Function variant_long_string(n)
Dim temp(0 To 0, 0 To 0) As Variant
temp(0, 0) = String(n, "a")
variant_long_string = temp
End Function
Calling from Excel, returns (fails for N > 255):
255 aaaaaaaaaaaaa....aaaaaaaaa
256 #VALUE!
Return Element of Variant Array:
Function variant_long_string_element(n)
Dim temp(0 To 0, 0 To 0) As Variant
temp(0, 0) = String(n, "a")
variant_long_string_element = temp(0, 0)
End Function
Calling from Excel, returns (succeeds for N > 255):
255 aaaaaaaaaaaaa....aaaaaaaaa
256 aaaaaaaaaaaaa....aaaaaaaaaa
Return String Array:
Function string_long_string(n)
Dim temp(0 To 0, 0 To 0) As String
temp(0, 0) = String(n, "a")
string_long_string = temp
End Function
Calling from Excel, returns (succeeds for N > 255):
255 aaaaaaaaaaaaa....aaaaaaaaa
256 aaaaaaaaaaaaa....aaaaaaaaaa
Workaround
If your python UDF only returns a single string value, like this:
@xw.func
def build_long_string(n):
res = 'a'*int(n)
return res
xlwings will autogenerate the following VBA Macro in the xlwings_udfs module:
Function build_long_string(n)
If TypeOf Application.Caller Is Range Then On Error GoTo failed
build_long_string = Py.CallUDF(PyScriptPath, "build_long_string", Array(n), ThisWorkbook)
Exit Function
failed:
build_long_string = Err.Description
End Function
As a quick patch to get your UDF working, changing that macro slightly to this:
Function build_long_string(n)
If TypeOf Application.Caller Is Range Then On Error GoTo failed
temp = Py.CallUDF(PyScriptPath, "build_long_string", Array(n), ThisWorkbook)
build_long_string = temp(0, 0)
Exit Function
failed:
build_long_string = Err.Description
End Function
allows string >255 length to make it to Excel successfully. You could do something similar for an array result, you'd just have to convert the Variant array to a String array by looping/reassigning all the values from temp to the result.
Upvotes: 2