none
Calling .NET delegates from unmanaged code - ref / out params RRS feed

  • Question

  • Hi,

    I am developing a server with both managed and unmanaged components.

    When calling from unmanaged into managed (C#) through IDispatch, ref/out parameters work correctly for me when I send VT_VARIANT|BYREF arguments.

    But when unmanaged code has been handed a .NET delegate (mscorlib::_Delegate), although calling into managed code with ref or out params doesn't error the semantics don't seem to be honoured (in short changes to those parameters don't make it back to unmanaged code).

    an example delegate:

    public delegate void onAction(IDispatch control, ref bool cancelDefault);

    and an example snippet on the calling side would be

    // Variant gets initialized to VT_BOOL
    CComVariant vRefBool(false);
    
    CComSafeArray<VARIANT> args(2, 0);
    args[0] = spDispControl;				// A CComPtr<IDispatch> variable
    args[1].vt = VT_BYREF|VT_VARIANT;		
    args[1].pvarVal = &vRefBool;
    
    HRESULT hr = spDotNetDelegate_->DynamicInvoke(&args.GetSafeArrayPtr(), pRetVal);

    (note the order of arguments is correctly the reverse of the args order in DISPPARAMS.rgvarg when the call is made through IDispatch, so I don't think the problem is there).

    The call dispatches with no errors and the C# side gets called fine, however changing argument cancelDefault on the C# side doesn't propagate back to the caller.

    Is there something I can do about this? I have tried a few variations (out doesn't work either).

    Thanks,

    Yiannis

    Friday, January 25, 2013 10:16 PM

Answers

  • Some articles suggests that “dynamically invoking methods with ref parameters only works if you can pass the parameters as an object array. After calling the method, you can simply read back the parameters from the array” [http://social.msdn.microsoft.com/forums/en-US/wpf/thread/677777bd-0e05-46f1-a2f2-acd4d4820b35/].

    Try to use DynamicInvoke or Invoke in C# first, and you will find a similar problem with ‘ref’ and ‘out’ parameters.

    It seems that before calling DynamicInvoke you have put a VT_BOOL value (a copy of your Boolean variable) into SAFEARRAY. After DynamicInvoke, copy the result back from array into Boolean variable.

    Maybe your dynamic invocation will also work, but you have to add the second loop that updates the values that were marked with VT_BYREF, probably using VariantCopyInd.

    Tuesday, January 29, 2013 7:18 AM

All replies

  • Have you tried this variation as well?

    BOOL b = FALSE;

    args[1].vt = VT_BYREF | VT_BOOL;

    args[1].pboolVal = &b;

    Saturday, January 26, 2013 7:00 PM
  • Thanks, I actually haven't (and it makes sense). Let me give it a try...
    Saturday, January 26, 2013 8:40 PM
  • It doesn't work unfortunately.
    Saturday, January 26, 2013 8:56 PM
  • Maybe you consider an alternative: converting the delegate into a native function pointer, using Marshal::GetFunctionPointerForDelegate?

    Sunday, January 27, 2013 7:36 PM
  • Thanks. The problem with this approach is that the callbacks are dispatched dynamically by the component I'm working on (and the possible signatures are quite a few) => I would have to reinvent the wheel (of dynamic dispatch) and push arguments manually on the stack (using assembly, I suppose).

    The framework exposes alternative ways to subscribe to callbacks for .NET clients (events or client-provided IDispatch implementation) so I can sort of afford it if this one delegate doesn't work, but it is not ideal. I would find it surprising if DynamicInvoke doesn't work for ref arguments - I still think I must be doing something wrong on the call site.

    Monday, January 28, 2013 6:17 PM
  • Is it correct to use “&” with args.GetSafeArrayPtr()? How is spDotNetDelegate_ and DynamicInvoke declared?

    Monday, January 28, 2013 8:31 PM
  • You're right. I tried to give a simple example and managed to copy the code incorrectly...

    Here's the code that does the delegation if applicable:

    // Class members
    CComPtr<_DelegateDotNet> spDotNetDelegate_;
    STDMETHODIMP CrxDelegate::invoke(_In_ DISPPARAMS *pDispParams,
    								 _Out_ VARIANT *pRetVal,
    								 _In_ void *delegateFuncInfo) {
    	HRESULT hr( S_OK );
    
    	_ASSERTE( pDispParams ); if ( !pDispParams ) { return E_INVALIDARG; }
    
    
    	// If the .NET delegate member is valid => dispatch to that
    	if ( spDotNetDelegate_ ) {
    
    		// Put all arguments in a SAFEARRAY
    		CComSafeArray<VARIANT> args(pDispParams->cArgs, 0);
    		
    		for ( int i = 0, j = static_cast<int>(pDispParams->cArgs-1); 
    				i < static_cast<int>(pDispParams->cArgs), j >= 0;
    				++i, --j ) {
    			args[i] = pDispParams->rgvarg[j];
    		}
    
    		// Go for it
    		CHECK_HR2( spDotNetDelegate_->DynamicInvoke(*args.GetSafeArrayPtr(), pRetVal), hr );
    
    	}
    
    	// Otherwise go down the traditional route
    	else {
    		CHECK_HR2( spDispatcher_->invoke(bstrMethodName_, pDispParams, pRetVal, delegateFuncInfo), hr );
    	}
    
    	return hr;
    }

    It works for all delegate signatures except for the one with the ref bool argument...

    Yiannis

    Monday, January 28, 2013 8:53 PM
  • Some articles suggests that “dynamically invoking methods with ref parameters only works if you can pass the parameters as an object array. After calling the method, you can simply read back the parameters from the array” [http://social.msdn.microsoft.com/forums/en-US/wpf/thread/677777bd-0e05-46f1-a2f2-acd4d4820b35/].

    Try to use DynamicInvoke or Invoke in C# first, and you will find a similar problem with ‘ref’ and ‘out’ parameters.

    It seems that before calling DynamicInvoke you have put a VT_BOOL value (a copy of your Boolean variable) into SAFEARRAY. After DynamicInvoke, copy the result back from array into Boolean variable.

    Maybe your dynamic invocation will also work, but you have to add the second loop that updates the values that were marked with VT_BYREF, probably using VariantCopyInd.

    Tuesday, January 29, 2013 7:18 AM
  • Thanks for the link. this seems to be a generic quirk of DynamicInvoke - not just when called from native code.

    Unfortunately removing the indirection of ref params doesn't work either. This is what I tried:

    //...
    CComSafeArray<VARIANT> args(pDispParams->cArgs, 0);	
    
    if( condition_to_catch_the_problematic_delegate ) {
    	VARIANT_BOOL bRet = VARIANT_TRUE;
    	args[0] = pDispParams->rgvarg[1];
    	args[1].vt = VT_BOOL;
    	args[1].boolVal = bRet;
    
    }
    //...
    
    // Go for it (calling code sets the ref arg to false)
    CHECK_HR2( spDotNetDelegate_->DynamicInvoke(*args.GetSafeArrayPtr(), pRetVal), hr );
    
    // Here args[0] still contains VARIANT_TRUE...
    
    Yiannis
    Tuesday, January 29, 2013 11:27 AM