The varbinary(max) Limitation with sp_OAMethod
The Problem
Chilkat ActiveX methods that return raw binary data — such as BinData's GetData and GetDataChunk — hand it back to COM callers as a SAFEARRAY of VT_UI1 bytes, which is to say, an array. Microsoft's own sp_OAMethod documentation is explicit about what happens next: "When the method return value is an array, if returnvalue is specified, it's set to NULL." This is not a size-dependent truncation the way the nvarchar string limitation is — it is an unconditional rule. Declaring the output variable as varbinary(max), or as a small fixed-length varbinary(n), makes no difference; a method that returns an array can never populate a scalar output variable through sp_OAMethod. In practice this often surfaces as error -2147211494 (hex 0x80040202) rather than a silent NULL, depending on the calling pattern, but the root cause is the same array-return rule.
varbinary value into a method as an input argument (for example, to AppendData) works fine — SQL Server can build a byte-array VARIANT for outbound parameters without trouble. It is only the return path, getting a byte array out of a method call, that fails.
DECLARE @bd int
EXEC @hr = sp_OACreate 'Chilkat.BinData', @bd OUT
DECLARE @success int
EXEC sp_OAMethod @bd, 'AppendEncoded', @success OUT, '010203040506', 'hex'
-- This raises error 0x80040202 -- SAFEARRAY(VT_UI1) cannot be returned this way:
DECLARE @binary_string varbinary(max)
EXEC sp_OAMethod @bd, 'GetData', @binary_string OUT
Why This Matters
Any method returning raw bytes directly — reading a binary BinData buffer, pulling a chunk out of it, or any similarly-shaped API on another Chilkat object — cannot be captured this way. The fix is not a different varbinary declaration; no declaration of the output parameter makes the direct byte-array return path work reliably through sp_OAMethod.
Workaround A: KeepBinaryResult and LastBinaryResult
Chilkat's BinData object exposes a LastBinaryResult property — a direct binary counterpart to the LastStringResult property used for the nvarchar limitation. It is populated only when Chilkat.Global's KeepBinaryResult property is set to 1, and it must be retrieved through a table variable via INSERT ... EXEC rather than a scalar OUT parameter, since that is the code path that isn't restricted to scalar, non-array results.
DECLARE @global int
EXEC @hr = sp_OACreate 'Chilkat.Global', @global OUT
EXEC sp_OASetProperty @global, 'KeepBinaryResult', 1
EXEC @hr = sp_OADestroy @global
-- Call GetData, but ignore its (NULL) return value -- the real bytes end up in LastBinaryResult:
DECLARE @dummy varbinary(1)
EXEC sp_OAMethod @bd, 'GetData', @dummy OUT
DECLARE @binResultTable TABLE (bin varbinary(max))
INSERT INTO @binResultTable EXEC sp_OAGetProperty @bd, 'LastBinaryResult'
DECLARE @binaryData varbinary(max)
SELECT @binaryData = bin FROM @binResultTable
This is generally the preferred workaround: it stays entirely in binary form, with no text-encoding round trip and none of the separate string-length limitation to worry about.
Workaround B: Encode as Base64, Then Convert
Alternatively, ask the method to hand back the same bytes encoded as a Base64 (or hex) string instead of a raw byte array. String return values are something sp_OAMethod can handle directly (subject to the separate nvarchar string-length limitation), and Base64 text can be converted back into varbinary(max) entirely within T-SQL using the built-in XML type's base64Binary function. This avoids depending on the KeepBinaryResult mechanism, at the cost of an extra encode/decode step.
Step 1: Get the data as Base64 instead of raw bytes
For BinData, call GetEncoded with 'base64' instead of GetData. Other Chilkat classes expose an analogous encode-to-string method for whatever binary content they hold.
DECLARE @hr int
DECLARE @sTmp0 nvarchar(4000)
DECLARE @bd int
EXEC @hr = sp_OACreate 'Chilkat.BinData', @bd OUT
IF @hr <> 0
BEGIN
PRINT 'Failed to create ActiveX component'
RETURN
END
DECLARE @success int
EXEC sp_OAMethod @bd, 'AppendEncoded', @success OUT, '010203040506', 'hex'
EXEC sp_OAMethod @bd, 'GetEncoded', @sTmp0 OUT, 'base64'
PRINT @sTmp0
Step 2: Convert the Base64 string to varbinary(max) in T-SQL
DECLARE @binaryData varbinary(max)
SET @binaryData = CAST('' AS XML).value('xs:base64Binary(sql:variable("@sTmp0"))', 'VARBINARY(MAX)')
EXEC @hr = sp_OADestroy @bd
This conversion is pure T-SQL — no COM call is involved — so it is not subject to the sp_OAMethod marshaling limitation at all.
Step 3: For large binary payloads, combine with KeepStringResult
Base64 text is still a string return value, so it is subject to the same roughly-4,000-character truncation limit described for nvarchar(max) output parameters. For binary payloads whose Base64 form exceeds that size, retrieve the encoded string through Chilkat.Global's KeepStringResult / LastStringResult mechanism first, then convert.
DECLARE @global int
EXEC @hr = sp_OACreate 'Chilkat.Global', @global OUT
EXEC sp_OASetProperty @global, 'KeepStringResult', 1
EXEC @hr = sp_OADestroy @global
DECLARE @dummy nvarchar(4000)
EXEC sp_OAMethod @bd, 'GetEncoded', @dummy OUT, 'base64'
DECLARE @resultTable TABLE (res ntext)
INSERT INTO @resultTable EXEC sp_OAGetProperty @bd, 'LastStringResult'
DECLARE @base64 nvarchar(max)
SELECT @base64 = res FROM @resultTable
DECLARE @binaryData varbinary(max)
SET @binaryData = CAST('' AS XML).value('xs:base64Binary(sql:variable("@base64"))', 'VARBINARY(MAX)')
varbinary variable can be passed directly as an input argument, as in AppendData below, because sp_OAMethod only has trouble constructing a byte array on the return path, not on the way in.
DECLARE @success int
EXEC sp_OAMethod @bd, 'AppendData', @success OUT, @binaryData, DATALENGTH(@binaryData)
Putting It Together
CREATE PROCEDURE ChilkatSample
AS
BEGIN
DECLARE @hr int
DECLARE @success int
DECLARE @bd int
DECLARE @global int
-- Enable KeepBinaryResult once per session.
EXEC @hr = sp_OACreate 'Chilkat.Global', @global OUT
EXEC sp_OASetProperty @global, 'KeepBinaryResult', 1
EXEC @hr = sp_OADestroy @global
EXEC @hr = sp_OACreate 'Chilkat.BinData', @bd OUT
IF @hr <> 0
BEGIN
PRINT 'Failed to create ActiveX component'
RETURN
END
-- Load some sample bytes into the BinData object.
EXEC sp_OAMethod @bd, 'AppendEncoded', @success OUT, '010203040506', 'hex'
-- GetData's return value is an array, so this OUT parameter is always NULL -- ignore it.
DECLARE @dummy varbinary(1)
EXEC sp_OAMethod @bd, 'GetData', @dummy OUT
-- Retrieve the real bytes from LastBinaryResult via a table variable.
DECLARE @binResultTable TABLE (bin varbinary(max))
INSERT INTO @binResultTable EXEC sp_OAGetProperty @bd, 'LastBinaryResult'
DECLARE @binaryData varbinary(max)
SELECT @binaryData = bin FROM @binResultTable
-- Sending varbinary back in as an input argument works directly -- no workaround needed.
EXEC sp_OAMethod @bd, 'AppendData', @success OUT, @binaryData, DATALENGTH(@binaryData)
PRINT 'OK.'
EXEC @hr = sp_OADestroy @bd
END
GO
Summary of Rules
- Never expect
sp_OAMethodto return a byte array into avarbinaryoutput parameter, at any size — per Microsoft's own documentation, when a method's return value is an array, a specifiedreturnvalue OUTPUTvariable is set toNULL. This is unconditional, not a size-based truncation like thenvarcharlimitation. - Prefer enabling
Chilkat.Global'sKeepBinaryResultproperty and retrieving the bytes fromLastBinaryResultthrough a table variable (INSERT ... EXEC) — this keeps the data in true binary form with no text encoding involved. - Alternatively, have the ActiveX method encode its binary content as a Base64 (or hex) string, retrieve that string, and convert it to
varbinary(max)in T-SQL withCAST('' AS XML).value('xs:base64Binary(sql:variable("@var"))', 'VARBINARY(MAX)'). - If using the Base64 route and the encoded text itself can exceed roughly 4,000 characters, retrieve it via
KeepStringResult/LastStringResult(see the companion article on thenvarcharlimitation) before converting it. - The limitation only affects the return path. Passing a
varbinaryvariable as an input argument to a method (e.g.,AppendData) works normally and needs no workaround.