none
VARIANT& containing a SAVEARRAY RRS feed

  • Question

  • Hi,

    i'm trying to call method on a third party ActiveX component from C#.

    the cpp signature is as follows:

    long GetVarAudioData(const VARIANT& vData);
    
    
    
    
    
    

    in the interop assembly the signature looks like this:

    [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime), DispId(11)]
    
    public virtual extern int GetVarAudioData([In, MarshalAs(UnmanagedType.Struct)] object vData);
    
    
    
    
    
    

    in cpp the method is easily called with the following code:

    VARIANT var;
    
    SAFEARRAYBOUND rgsabound[1] = { lDataSize, 0 };
    
    VariantInit(&var);
    
    var.vt = VT_ARRAY|VT_UI1;
    
    var.parray = SafeArrayCreate(VT_UI1, 1, rgsabound);
    
    m_Speech.GetVarAudioData(var);	
    
    
    
    

    my question is now how do i do the same thing from c#?

    Regards,

    Jesper

    Wednesday, March 23, 2011 1:12 PM

Answers

  • Hello Jesper,

     

    1. >> I was thinking if it would be possible to wrap the ActiveX component in an unmanaged dll to "change" the API of the class/method that creates the problem? and how would I do that the easiest way?

    1.1 After doing some research work on this, I found that the best way forward for you would be to write a wrapper ActiveX object object that encapsulates the original ActiveX object.

    1.2 This new wrapper ActiveX would internally create an instance of the original ActiveX.

    1.3 It must provide all the methods with exact same signatures of the original ActiveX. The new wrapper methods can simply forward all method calls to the original ActiveX methods.

    1.4 An exception to this would be the GetVarAudioData() method :

    1.4.1 Here, the wrapper GetVarAudioData() method would expect an input VARIANT of type : VT_BYREF|VT_ARRAY|VT_UI1.

    1.4.2 Therefore, the input VARIANT would internally contain a pointer to a pointer to a SAFEARRAY.

    1.4.3 The wrapper GetVarAudioData() would then create a new VARIANT that will contain the original SAFEARRAY and then pass the new VARIANT to the original GetVarAudioData().

    1.4.4 The original GetVarAudioData() would then modify the SAFEARRAY as required.

    1.4.5 An example implementation of the wrapper method is provided in the next section.

     

    2. Implementation of Wrapper GetVarAudioData() Method.

    2.1 The code below is a sample implementation of a wrapper GetVarAudioData() method :

    LONG JesperMFCObjectWrapper::GetVarAudioData(VARIANT &vData)
    {
     AFX_MANAGE_STATE(AfxGetStaticModuleState());

     // TODO: Add your dispatch handler code here
     
     // Confirm that the incoming vData contains
     // an array of bytes passed by reference.
     if (V_VT(&vData) == (VT_BYREF|VT_ARRAY|VT_UI1))
     {
      // Get a pointer to the pointer to the safearray.
      SAFEARRAY** ppSafeArrayOfBytes = V_ARRAYREF(&vData);
      // Create a new VARIANT that will contain the
      // safearray.
      VARIANT  varData;
      
      VariantInit(&varData);
      V_VT(&varData) = (VT_ARRAY|VT_UI1);
      V_ARRAY(&varData) = *ppSafeArrayOfBytes;
      
      // Call the original method.
      return m_pOriginalObject -> GetVarAudioData(varData);
      // After the above call, the safearray inside
      // ppSafeArrayOfBytes will be modified by the
      // original method.
      // We can simply return.
     }

     return 0;
    }

     

    3. Call From C# Code.

    3.1 The C# client code will naturally need to create and use the wrapper ActiveX object instead of the original one.

    3.2 When it comes time to call the GetVarAudioData() method, you need to do so (via reflection) by late binding to the IDispatch interface of the wrapper ActiveX component as indicated by my post dated Thursday, March 24, 2011 10:31 AM.

    3.3 Use Type.InvokeMember() to call the GetVarAudioData() method. The particular flavour of Type.InvokeMember()  to call is the one that takes an array of ParameterModifier types :

    Object InvokeMember(
     string name,
     BindingFlags invokeAttr,
     Binder binder,
     Object target,
     Object[] args,
     ParameterModifier[] modifiers,
     CultureInfo culture,
     string[] namedParameters
    );

    3.4 Here is a sample listing demonstrating how to call GetVarAudioData() using reflection :

    object[] parameters = { new byte[10] };
    // Initialize a ParameterModifier with the number of parameters.
    ParameterModifier p = new ParameterModifier(1);
    // Set the VT_BYREF flag on the first parameter
    p[0] = true;
    ParameterModifier[] mods = { p };
    // Call the GetVarAudioData() method via late binding.
    // The byte array contained inside "parameters" will be passed by reference : (VT_BYREF|VT_ARRAY|VT_UI1).
    object objRet = m_SpeechWrapper.GetType().InvokeMember("GetVarAudioData", BindingFlags.InvokeMethod, null, m_SpeechWrapper, parameters, mods, null, null);
    // When the call is done, the byte array inside "parameters" will be the modified values set by GetVarAudioData().
    byte [] byte_array = (byte[])(parameters[0]);

     

    4. Additional Points About Apartment Marshaling.

    4.1 It is important that the wrapper ActiveX and the original ActiveX live in the same apartment. To ensure this, within the wrapper ActiveX, create the original ActiveX at the point when the wrapper object is created (i.e. in the wrapper class' constructor).

    4.2 With the wrapper and the original ActiveX objects living in the same apartment, when the wrapper calls the original object's GetVarAudioData() method, no COM marshaling will take place. This will ensure that the SAFEARRAY contained in the VARIANT passed from the wrapper to the original can be modified by the original.

    4.3 Note that point 4.2 describes a hack. It is not standard practice. It is best to contact the original ActiveX vendors and clarify things.

    4.4 A call to the wrapper's GetVarAudioData() method from the C# client will go through successfully. Note that interop marshaling will be involved and because we have modified the object parameter (passed to the GetVarAudioData() via InvokeMember()) to be passed by reference, the C# array will processed and passed to GetVarAudioData() and then returned with modified data.

     

    5. Alternative Technique.

    5.1 An alternative way to declare the wrapper GetVarAudioData() method would be to declare the input VARIANT as a pointer :

    LONG GetVarAudioData(VARIANT* vData);

    5.2 The equivalent C# declaration will be :

    int GetVarAudioData(ref object vData);

    5.3 This declaration works well for a C++ client (especially an MFC client). But it does not work well for a C# client. The problem has to do with the fact that an ActiveX object's methods are called via late binding (i.e. via IDispatch::Invoke()).

    5.4 To see the problem, let's look at the following C# call to GetVarAudioData() :

                byte[] byte_array = new byte[10];
                              ...
                              ...
                              ...
                // Call the method, passing the byte array boxed into an object.
                object obj = byte_array;

                int iRetTemp = m_SpeechWrapper.GetVarAudioData(ref obj);

    5.5 In such a situation, the CLR interop marshaler will internally create a VARIANT of type VT_BYREF|VT_ARRAY|VT_UI1 for "obj".

    5.6 This looks promising but over at the unmanaged side, the COM marshaler will expect a VARIANT of type VT_BYREF|VT_VARIANT. This matches a dispinterface method parameter of type VARIANT*.

    5.7 A type mismatch exception will occur even before the wrapper's GetVarAudioData() method gets invoked.

    5.8 I anticipate that any way around this problem, if one can be found, will require alot of hardwork on the C# side which may include custom marshaling. Things would get rather complicated and will by no means be the simplest solution to your problem.

    5.9 Hence I hope you can try the solution described in sections 2 and 3 and let us know if it helps.

     

    Best of luck, Jesper,

    - Bio.

     



    • Marked as answer by eryang Monday, April 11, 2011 3:08 AM
    Sunday, March 27, 2011 9:30 AM

All replies

  • Hello Jesper,

     

    1. >> my question is now how do i do the same thing from c#?

    1.1 From the sample C++ code, the SAFEARRAY is to hold an array of bytes (VT_UI1).

    1.2 Calling the GetVarAudioData() method from C# is very simple :

                // Declare a managed array of bytes.
                // Size of 10 elements is just an example.
                byte [] byte_array = new byte[10];
                // Fill in values into the byte array.
                // The following is just an example.
                for (int i = 0; i < byte_array.Length; i++)
                {
                    byte_array[i] = (byte)i;
                }
                // Call the method, passing the byte array directly.
                // "byte_array" will be boxed into an object automatically.
                int iRetTemp = m_Speech.GetVarAudioData(byte_array);

    - Bio.

     

    Wednesday, March 23, 2011 2:58 PM
  • Hello Bio,

    thanks for the quick reply.

    Problem still persists.

    I tried you suggestion but the result is the same, the byte[] comes back unmodified.

    The result should be that byte[] should be filled with PCM-audiodata by GetVarAudioData.

    Any new suggestions?

    - Jesper

    Wednesday, March 23, 2011 3:46 PM
  • Hello Jesper,

     

    1. >> The result should be that byte[] should be filled with PCM-audiodata by GetVarAudioData.

    1.1 Then in this case, the C++ sample client code should have been written as :

      VARIANT var;
      SAFEARRAYBOUND rgsabound[1] = { lDataSize, 0 };

      VariantInit(&var);
      V_VT(&var) = VT_BYREF|VT_ARRAY|VT_UI1;

      SAFEARRAY* pSafeArrayOfBytes = SafeArrayCreate(VT_UI1, 1, rgsabound);
      V_ARRAYREF(&var) = &pSafeArrayOfBytes;
      m_Speech.GetVarAudioData(var);

    1.2 The input VARIANT should have contained a pointer to a pointer to a SAFEARRAY in order that the client code successfully obtains a modified array from GetVarAudioData().

    1.3 You must thus make sure that the ActiveX really does take, for its parameter, a VARIANT that contains a reference to a SAFEARRAY of bytes.

     

    2. In this case, it would be necessary that you use Reflection to late bind to the IDispatch interface of the ActiveX component. ActiveXs in general do support the IDispatch interface so this should not be a problem.

    2.1 You would thus have to use Type.InvokeMember() to call the GetVarAudioData() method. The particular flavour of Type.InvokeMember()  to call is the one that takes an array of ParameterModifier types :

    Object InvokeMember(
     string name,
     BindingFlags invokeAttr,
     Binder binder,
     Object target,
     Object[] args,
     ParameterModifier[] modifiers,
     CultureInfo culture,
     string[] namedParameters
    );

    2.2 Note that there is no intrinsic C# supported way to early bind to the GetVarAudioData() method and pass an object that somehow ends up as a VARIANT with the VT_BYREF flag in the implementation code for GetVarAudioData() albeit it may be possible with custom marshaling. Let me do some research on this.

    2.3 Listed below is one way to perform the Type.InvokeMember() :

                object[] parameters = { new byte[10] };
                // Initialize a ParameterModifier with the number of parameters.
                ParameterModifier p = new ParameterModifier(1);
                // Set the VT_BYREF flag on the first parameter
                p[0] = true;
                ParameterModifier[] mods = { p };
                // Call the GetVarAudioData() method via late binding.
                // The byte array contained inside "parameters" will be passed by reference : (VT_BYREF|VT_ARRAY|VT_UI1).
                object objRet = m_Speech.GetType().InvokeMember("GetVarAudioData", BindingFlags.InvokeMethod, null, m_Speech, parameters, mods, null, null);
                // When the call is done, the byte array inside "parameters" will be the modified values set by GetVarAudioData().
                byte[] byte_array = (byte[])(parameters[0]);

    2.4 Pls give it a try and let us know how things turn out.

     

    - Bio.

     



    Wednesday, March 23, 2011 6:04 PM
  • Hi Bio.

    Let me first thank you a 1000 times for trying to help me out.

    Still I don't get a result (no PCM-data).

    Let me add that the C++ code works as is.

    Let me elaborate further on my program.

    I'm trying to create a web service (ASMX) that uses COM library for text to speak. When calling the Speak method I can choose to receive data in chunks via a callback event. In the handler as a parameter I get the number of bytes that can be retrieved from the GetVarAudioData method. I guess this means that the event is processed on another thread but does this affect the call/result of GetVarAudioData?

    Kind regards,

    Jesper 

    Wednesday, March 23, 2011 8:17 PM
  • Hello Jesper,

     

    1. >> When calling the Speak method I can choose to receive data in chunks via a callback event. In the handler as a parameter I get the number of bytes that can be retrieved from the GetVarAudioData method...

    1.1 From the above description, I understand that GetVarAudioData() is a method and is not an event. Pls confirm this.

    1.2 It would be useful to see the IDL declaration for the GetVarAudioData() method. Is it declared as :

    [id(x), helpstring("method GetVarAudioData")] LONG GetVarAudioData(VARIANT vData);

    or

    [id(x), helpstring("method GetVarAudioData")] LONG GetVarAudioData(VARIANT* vData);

    or something else.

    1.3 In the first case, "vData" is an "in" parameter and so the SAFEARRAY contained inside "vData" will not be modifiable for the client code.

    1.4 In the second case, "vData" is a reference parameter and so the SAFEARRAY contained within "vData" can be modified for the client code.

    1.5 As it is a 3rd party ActiveX that you are using, you can view the IDL of the ActiveX via the OLE-COM Object Viewer (OleView.Exe).

     

    - Bio.

     


    Thursday, March 24, 2011 12:54 AM
  • Hello Bio.

    GetVarAudioData is a method (from OLEView):

    [id(0x0000000b), helpstring("method GetAudioData")]long GetVarAudioData([in] VARIANT vData);

    I set up an event handler on the OnAudioData event that is received when the TTS server has generate a data chunk. From within the handler i call GetVarAudioData.

    Still let me point out that C++ code from my first post works.

    Best regards,

    Jesper



    Thursday, March 24, 2011 6:06 AM
  • Hi again Bio,

    Please also read my previous post.

    I have one addition. Now I also tried you C++ code in the working C++ solution and you code results in getting an unmodified savearray back.

    - Jesper

    Thursday, March 24, 2011 8:43 AM
  • Hello Jesper,

     

    1. >> [id(0x0000000b), helpstring("method GetAudioData")]long GetVarAudioData([in] VARIANT vData);

    1.1 With the above declaration, it is highly unusual that the SAFEARRAY contained inside "vData" can be permanently modified inside the GetVarAudioData() and then returned to client code.

    1.2 With following code listed in the OP :

    VARIANT var;

    SAFEARRAYBOUND rgsabound[1] = { lDataSize, 0 };

    VariantInit(&var);

    var.vt = VT_ARRAY|VT_UI1;

    var.parray = SafeArrayCreate(VT_UI1, 1, rgsabound);

    m_Speech.GetVarAudioData(var);

     

    What should happen is that implementors of the GetVarAudioData() method must regard that it has received a copy of "var". Furthermore, GetVarAudioData() shoud not do anything with "var". This is an expectation for an "in" parameter.  The GetVarAudioData() method must regard "vData" as a constant and not make changes to the SAFEARRAY contained in "vData".

    In your case, it appears that the client's "vData" is somehow passed into the GetVarAudioData() method and changes to the SAFEARRAY contained inside "vData" are made. This can only happen if the client code and m_Speech are both inside the same apartment. No marshaling of "var" is performed and so GetVarAudioData() received a direct copy of the client's "var". If GetVarAudioData() contained code to make changes to the SAFEARRAY inside "var", these changes are permanent.

    This situation will not occur if the client code and m_Speech are in different apartments. Marshaling code will kick in and GetVarAudioData() will obtain a copy of "var" and changes to the SAFEARRAY inside the copy of "var" will not affect the original SAFEARRAY inside the client's "var".

     

    2. >> I have one addition. Now I also tried you C++ code in the working C++ solution and you code results in getting an unmodified savearray back...

    2.1 It is likely that in the implementation code for GetVarAudioData(), the following check is done at the beginning of the code :

    if (V_VT(&vData) == (VT_ARRAY | VT_UI1)) ...

    Hence if the client code was :

      VARIANT var;
      SAFEARRAYBOUND rgsabound[1] = { lDataSize, 0 };

      VariantInit(&var);
      V_VT(&var) = VT_BYREF|VT_ARRAY|VT_UI1;

    nothing would be done for the SAFEARRAY inside "vData".

    2.2 All in all, it is very unusual code, in my personal opinion albeit I could be wrong.

    2.3 I also find it strange that the code in the OP is :

    m_Speech.GetVarAudioData(var);

    instead of :

    m_Speech -> GetVarAudioData(var);

    Is m_Speech an instance of an object or is it an interface pointer ?

     

    3. My advise to you would be to clarify with the vendors of the 3rd party ActiveX and clarify with them the points raised above : that it is not expected for an "in" parameter to be modified permanently and problems will occur if different apartments are involved.

     

    - Bio.

     



    Thursday, March 24, 2011 10:31 AM
  • Hi Bio.

    Thanks again for your answer.

    I found the following in the C++ program:

    long CAASpeechCtrl::GetVarAudioData(const VARIANT& vData)
    {
    	long result;
    	static BYTE parms[] =
    		VTS_VARIANT;
    	InvokeHelper(0xb, DISPATCH_METHOD, VT_I4, (void*)&result, parms,
    		&vData);
    	return result;
    }
    

    does this explain the wierd behavior? 

    - Jesper

    Thursday, March 24, 2011 11:13 AM
  • Hello Jesper,

     

    1. The code listing for CAASpeechCtrl::GetVarAudioData() in your last post is likely produced by the MFC wizard. It looks like standard code.

     

    2. Please read my comments in my previous post especially point 1.2. I have revised my comments after performing some more tests.

     

    3. I beleieve the problem is with the vendors of the ActiveX. I personally believe that the vendor should have declared the GetVarAudioData() method more like the following :

    [id(x), helpstring("method GetVarAudioData")] LONG GetVarAudioData(VARIANT* vData);

     

    4. I believe that the code worked for you, i.e. that the call to GetVarAudioData() successfully "returned" a modified SAFEARRAY of bytes, because of the fact that the client code and the ActiveX happen to be in the same apartment (likely STA). You can look at it this way : m_Speech is created in the same thread as the client code that calls its GetVarAudioData() method. If so, trouble would ensue if you call GetVarAudioData() in another thread : you will not be able to obtain any modified SAFEARRAY.

     

    - Bio.

     

    Thursday, March 24, 2011 11:38 AM
  • Hi Bio.

    You are highly likely right about the apartment model. The sample C++ app is an MFC app. Does this mean no marshaling is taking place?

    As I'm trying to figure out another solution I would like to ask for your help. I was thinking if it would be possible to wrap the ActiveX component in an unmanaged dll to "change" the API of the class/method that creates the problem? and how would I do that the easiest way? Preferably it would simply have a method called TextToPcmSpeech that would do all the work and return a byte[] containing the PCM data. Would that be possible in light of the thread/apartment problem?

    Thanks for all your help so far I really appreciate it.

    -Jesper

    Thursday, March 24, 2011 4:16 PM
  • Hello Jesper,

     

    1. >> The sample C++ app is an MFC app. Does this mean no marshaling is taking place?

    1.1 Marshaling can and do take place in an MFC app. There is nothing special about MFC that somehow removes the need for marshaling.

     

    2. >> I was thinking if it would be possible to wrap the ActiveX component in an unmanaged dll to "change" the API of the class/method that creates the problem? ... Would that be possible in light of the thread/apartment problem?

    2.1 Yes, I think that writing a wrapper would be helpful if we are indeed right about our suspicion that the original ActiveX was written with an incorrect assumption that its GetVarAudioData() method will always be invoked on the same thread that it was created in.

    2.2 Let me do some more research on this. I'll revert with some more information.

     

    - Bio.

     

     

    Friday, March 25, 2011 4:55 PM
  • Hello Jesper,

     

    1. >> I was thinking if it would be possible to wrap the ActiveX component in an unmanaged dll to "change" the API of the class/method that creates the problem? and how would I do that the easiest way?

    1.1 After doing some research work on this, I found that the best way forward for you would be to write a wrapper ActiveX object object that encapsulates the original ActiveX object.

    1.2 This new wrapper ActiveX would internally create an instance of the original ActiveX.

    1.3 It must provide all the methods with exact same signatures of the original ActiveX. The new wrapper methods can simply forward all method calls to the original ActiveX methods.

    1.4 An exception to this would be the GetVarAudioData() method :

    1.4.1 Here, the wrapper GetVarAudioData() method would expect an input VARIANT of type : VT_BYREF|VT_ARRAY|VT_UI1.

    1.4.2 Therefore, the input VARIANT would internally contain a pointer to a pointer to a SAFEARRAY.

    1.4.3 The wrapper GetVarAudioData() would then create a new VARIANT that will contain the original SAFEARRAY and then pass the new VARIANT to the original GetVarAudioData().

    1.4.4 The original GetVarAudioData() would then modify the SAFEARRAY as required.

    1.4.5 An example implementation of the wrapper method is provided in the next section.

     

    2. Implementation of Wrapper GetVarAudioData() Method.

    2.1 The code below is a sample implementation of a wrapper GetVarAudioData() method :

    LONG JesperMFCObjectWrapper::GetVarAudioData(VARIANT &vData)
    {
     AFX_MANAGE_STATE(AfxGetStaticModuleState());

     // TODO: Add your dispatch handler code here
     
     // Confirm that the incoming vData contains
     // an array of bytes passed by reference.
     if (V_VT(&vData) == (VT_BYREF|VT_ARRAY|VT_UI1))
     {
      // Get a pointer to the pointer to the safearray.
      SAFEARRAY** ppSafeArrayOfBytes = V_ARRAYREF(&vData);
      // Create a new VARIANT that will contain the
      // safearray.
      VARIANT  varData;
      
      VariantInit(&varData);
      V_VT(&varData) = (VT_ARRAY|VT_UI1);
      V_ARRAY(&varData) = *ppSafeArrayOfBytes;
      
      // Call the original method.
      return m_pOriginalObject -> GetVarAudioData(varData);
      // After the above call, the safearray inside
      // ppSafeArrayOfBytes will be modified by the
      // original method.
      // We can simply return.
     }

     return 0;
    }

     

    3. Call From C# Code.

    3.1 The C# client code will naturally need to create and use the wrapper ActiveX object instead of the original one.

    3.2 When it comes time to call the GetVarAudioData() method, you need to do so (via reflection) by late binding to the IDispatch interface of the wrapper ActiveX component as indicated by my post dated Thursday, March 24, 2011 10:31 AM.

    3.3 Use Type.InvokeMember() to call the GetVarAudioData() method. The particular flavour of Type.InvokeMember()  to call is the one that takes an array of ParameterModifier types :

    Object InvokeMember(
     string name,
     BindingFlags invokeAttr,
     Binder binder,
     Object target,
     Object[] args,
     ParameterModifier[] modifiers,
     CultureInfo culture,
     string[] namedParameters
    );

    3.4 Here is a sample listing demonstrating how to call GetVarAudioData() using reflection :

    object[] parameters = { new byte[10] };
    // Initialize a ParameterModifier with the number of parameters.
    ParameterModifier p = new ParameterModifier(1);
    // Set the VT_BYREF flag on the first parameter
    p[0] = true;
    ParameterModifier[] mods = { p };
    // Call the GetVarAudioData() method via late binding.
    // The byte array contained inside "parameters" will be passed by reference : (VT_BYREF|VT_ARRAY|VT_UI1).
    object objRet = m_SpeechWrapper.GetType().InvokeMember("GetVarAudioData", BindingFlags.InvokeMethod, null, m_SpeechWrapper, parameters, mods, null, null);
    // When the call is done, the byte array inside "parameters" will be the modified values set by GetVarAudioData().
    byte [] byte_array = (byte[])(parameters[0]);

     

    4. Additional Points About Apartment Marshaling.

    4.1 It is important that the wrapper ActiveX and the original ActiveX live in the same apartment. To ensure this, within the wrapper ActiveX, create the original ActiveX at the point when the wrapper object is created (i.e. in the wrapper class' constructor).

    4.2 With the wrapper and the original ActiveX objects living in the same apartment, when the wrapper calls the original object's GetVarAudioData() method, no COM marshaling will take place. This will ensure that the SAFEARRAY contained in the VARIANT passed from the wrapper to the original can be modified by the original.

    4.3 Note that point 4.2 describes a hack. It is not standard practice. It is best to contact the original ActiveX vendors and clarify things.

    4.4 A call to the wrapper's GetVarAudioData() method from the C# client will go through successfully. Note that interop marshaling will be involved and because we have modified the object parameter (passed to the GetVarAudioData() via InvokeMember()) to be passed by reference, the C# array will processed and passed to GetVarAudioData() and then returned with modified data.

     

    5. Alternative Technique.

    5.1 An alternative way to declare the wrapper GetVarAudioData() method would be to declare the input VARIANT as a pointer :

    LONG GetVarAudioData(VARIANT* vData);

    5.2 The equivalent C# declaration will be :

    int GetVarAudioData(ref object vData);

    5.3 This declaration works well for a C++ client (especially an MFC client). But it does not work well for a C# client. The problem has to do with the fact that an ActiveX object's methods are called via late binding (i.e. via IDispatch::Invoke()).

    5.4 To see the problem, let's look at the following C# call to GetVarAudioData() :

                byte[] byte_array = new byte[10];
                              ...
                              ...
                              ...
                // Call the method, passing the byte array boxed into an object.
                object obj = byte_array;

                int iRetTemp = m_SpeechWrapper.GetVarAudioData(ref obj);

    5.5 In such a situation, the CLR interop marshaler will internally create a VARIANT of type VT_BYREF|VT_ARRAY|VT_UI1 for "obj".

    5.6 This looks promising but over at the unmanaged side, the COM marshaler will expect a VARIANT of type VT_BYREF|VT_VARIANT. This matches a dispinterface method parameter of type VARIANT*.

    5.7 A type mismatch exception will occur even before the wrapper's GetVarAudioData() method gets invoked.

    5.8 I anticipate that any way around this problem, if one can be found, will require alot of hardwork on the C# side which may include custom marshaling. Things would get rather complicated and will by no means be the simplest solution to your problem.

    5.9 Hence I hope you can try the solution described in sections 2 and 3 and let us know if it helps.

     

    Best of luck, Jesper,

    - Bio.

     



    • Marked as answer by eryang Monday, April 11, 2011 3:08 AM
    Sunday, March 27, 2011 9:30 AM