none
can you tell me why SAFEARRAY so important in interface method definition? RRS feed

  • Question

  • Hi All:

    here is the Interface definition from idl file:

    interface ITestExe1 : IDispatch{
    	[id(1)] HRESULT SetImage([in, size_is(nLength)] byte* pBuffer, LONG nLength);
    };

    and some test code from C#:

    byte lByteArray[] = new byte[]{ 1,2,3,4,5 };
    
    TestExeLib.ITestExe1 lTest2 = new TestExeLib.TestExe1Class();
    lTest2.SetImage( ref lByteArray[0], lFileLength );

    above code works if the interface is defined within in-process dll, however it failed if it's moved into out-process exe.

    After above interface definition get changed as below:

    interface ITestExe1 : IDispatch{
    	[id(1)] HRESULT SetImage([in, size_is(nLength)] SAFEARRAY(BYTE) pBuffer, LONG nLength);
    };

    and C# code call changed as follow:

    TestExeLib.ITestExe1 lTest2 = new TestExeLib.TestExe1Class();
    lTest2.SetImage( lByteArray, lFileLength );
    it worked in out-process exe as well.

    So my question is why the SAFEARRAY definition becomes so important here, can't IDL compiler know pBuffer is an array by size_is() keyword? since it's smart to do memory marshal from C# to in-process dll, why it can not be smarter for C# to out-process exe? or it's for backward compatibility?


    fight for future!

    Thursday, May 10, 2012 8:04 AM

Answers

  • Hello Mulder2008,

    1. >> here is the Interface definition from idl file:...

    ... above code works if the interface is defined within in-process dll, however it failed if it's moved into out-process exe...

    1.1 To generalize the above statement : the above code works only if the COM object is used in the same apartment as the client code.

    1.2 When the same apartment is shared between the COM object and the client code, the COM object will be able to access the memory used by the client code and this includes the byte array (which is automatically pinned during the SetImage() method call because the byte data type is blittable). This is why the entire byte array can be seen by the COM object through the address of the very first element of the array.

    1.3 Wherever cross-apartment usage is relevant (e.g. when the COM server is housed in a separate EXE), interface marshaling will take place and the breakdown happens because by default, Type Library marshaling will be used (as in your case above). When Type Library marshaling is used, MIDL attributes like size_is() is ignored. It is not supported by the Type Library (see "INFO: Compile an IDL File for Proxy/Stub and not Type Library" http://support.microsoft.com/kb/236970 for more details).

    1.4 This being the case, the "pBuffer" parameter, being a byte pointer, will be marshaled as what it actually is : a pointer to one single byte . Hence your COM object will see only the first byte of the array (the number 1).

    2. >> So my question is why the SAFEARRAY definition becomes so important here, can't IDL compiler know pBuffer is an array by size_is() keyword? since it's smart to do memory marshal from C# to in-process dll, why it can not be smarter for C# to out-process exe? or it's for backward compatibility?

    2.1 Actually, there is a way to make the SetImage() method work with the size_is() attribute and the byte array even across apartments. The solution involves the use of the proxy/stub DLL and a modification of the interop assembly for the COM server. I will post again to provide information on how this cane be done.

    2.2 To answer your question about the SAFEARRAY : the SAFEARRAY is marshaled by the Type Library marshaler OLEAUT32.DLL. It is best type to use when passing managed arrays to unmanaged functions (including COM methods). This is because the SAFEARRAY, like the managed array, intrinsically contains information on the data type of the elements that it contains as well as the number of dimensions and number of elements per dimension of the contained array.

    2.3 The SAFEARRAY solution will work aright away because its marshaler : OLEAUT32.DLL is a system DLL and is always available. To use the size_is() solution, you need to compile the proxy/stub DLL and register it. Chances are you have missed this step and so the solution did not work.

    - Bio.


    Please visit my blog : http://limbioliong.wordpress.com/

    • Marked as answer by Mulder2008 Monday, May 14, 2012 1:34 AM
    Sunday, May 13, 2012 7:03 AM

All replies

  • Please use the CLR forum: http://social.msdn.microsoft.com/Forums/en-US/clr/threads

    [Discuss issues regarding the very core of .NET: Security, performance, hosting, base classes, interop, reliability, debugging, GC, and profiling are example topics.]

     

    Mike Zhang[MSFT]
    MSDN Community Support | Feedback to us

    Friday, May 11, 2012 6:54 AM
    Moderator
  • Hi Mulder2008,

     Many times, this information consists of an array of values. To return an array to UI Automation, the provider must pack the array into a SAFEARRAY structure. The array elements must be of the expected data type, and must appear in the expected order.


    Regards, http://shwetamannjain.blogspot.com

    Sunday, May 13, 2012 5:43 AM
  • Hello Mulder2008,

    1. >> here is the Interface definition from idl file:...

    ... above code works if the interface is defined within in-process dll, however it failed if it's moved into out-process exe...

    1.1 To generalize the above statement : the above code works only if the COM object is used in the same apartment as the client code.

    1.2 When the same apartment is shared between the COM object and the client code, the COM object will be able to access the memory used by the client code and this includes the byte array (which is automatically pinned during the SetImage() method call because the byte data type is blittable). This is why the entire byte array can be seen by the COM object through the address of the very first element of the array.

    1.3 Wherever cross-apartment usage is relevant (e.g. when the COM server is housed in a separate EXE), interface marshaling will take place and the breakdown happens because by default, Type Library marshaling will be used (as in your case above). When Type Library marshaling is used, MIDL attributes like size_is() is ignored. It is not supported by the Type Library (see "INFO: Compile an IDL File for Proxy/Stub and not Type Library" http://support.microsoft.com/kb/236970 for more details).

    1.4 This being the case, the "pBuffer" parameter, being a byte pointer, will be marshaled as what it actually is : a pointer to one single byte . Hence your COM object will see only the first byte of the array (the number 1).

    2. >> So my question is why the SAFEARRAY definition becomes so important here, can't IDL compiler know pBuffer is an array by size_is() keyword? since it's smart to do memory marshal from C# to in-process dll, why it can not be smarter for C# to out-process exe? or it's for backward compatibility?

    2.1 Actually, there is a way to make the SetImage() method work with the size_is() attribute and the byte array even across apartments. The solution involves the use of the proxy/stub DLL and a modification of the interop assembly for the COM server. I will post again to provide information on how this cane be done.

    2.2 To answer your question about the SAFEARRAY : the SAFEARRAY is marshaled by the Type Library marshaler OLEAUT32.DLL. It is best type to use when passing managed arrays to unmanaged functions (including COM methods). This is because the SAFEARRAY, like the managed array, intrinsically contains information on the data type of the elements that it contains as well as the number of dimensions and number of elements per dimension of the contained array.

    2.3 The SAFEARRAY solution will work aright away because its marshaler : OLEAUT32.DLL is a system DLL and is always available. To use the size_is() solution, you need to compile the proxy/stub DLL and register it. Chances are you have missed this step and so the solution did not work.

    - Bio.


    Please visit my blog : http://limbioliong.wordpress.com/

    • Marked as answer by Mulder2008 Monday, May 14, 2012 1:34 AM
    Sunday, May 13, 2012 7:03 AM
  • Hello Mulder2008,

    1. As promised, I shall expound on how to make the SetImage() method (which takes the "pBuffer" byte array parameter that is decorated by the size_is attribute) work for a client across apartments.

    2. Generate The Proxy/Stub DLL.

    2.1 As mentioned, the solution involves the use of a proxy/stub DLL and a modification of the interop assembly for the COM server.

    2.2. To enable the generation of the proxy/stub DLL, Visual Studio will generate a special proxy/stub sub-project for your COM server main project. This name of this project is post-fixed with "PS". Hence if your project is "MyCOMServer", then your proxy/stub DLL project will be "MyCOMServerPS".

    2.3. Make sure that you compile this project together with the main project. The output directory for the proxy/stub is also post-fixed with "PS" and so for a debug project, the output is in "DebugPS". Visual Studio will automatically register it for you once the compilation is done.

    3. Modify The Interop Assembly.

    3.1. Next, you need to do some modifications to the interop assembly DLL for the COM server.

    3.2 If you had directly referenced your COM server in your C# project, then an interop assembly DLL will be automatically generated for your project by Visual Studio.

    3.3 This interop assembly DLL will use default settings and import rules and so your SetImage() method is probably imported as :

    void SetImage(ref byte pBuffer, int nLength);

    3.4 To modify the interop assembly, you will overall perform the following :

    • Manually generate the interop assembly DLL using TLBIMP.EXE.
    • Disassemble the interop assembly (using ILDASM.EXE) to generate an intermediate language file (.il file - a text file).
    • Modify the .il file using NOTEPAD.EXE.
    • Re-assemble the .il file using ILASM.EXE to produce a new interop assembly DLL.
    • In your C# project, drop the reference to the original interop assembly and then reference the newly created interop assembly.

    3.5 To manually create the interop assembly, open up a new command prompt and make the following command :

    tlbimp MyCOMServer.dll /out:interop.MyCOMServer.dll

    3.6 To disassemble the newly generated interop assembly DLL, make the following command :

    ildasm interop.MyCOMServer.dll /out=interop.MyCOMServer.il

    3.7 Open interop.MyCOMServer.il file (a text file) and search for all occurences of the SetImage() method. These should be in the following form :

    instance void  SetImage([in] uint8& pBuffer,
                   		[in] int32 nLength) runtime managed internalcall

    Change this to :

    instance void  SetImage(/*[in] uint8& pBuffer,*/ [in] uint8[] marshal([+1]) pBuffer,
    			[in] int32 nLength) runtime managed internalcall

    I have commented out the signature of the first parameter (which is interpreted as a reference to a single byte (uint8) type) to an array of bytes the number of elements of which is determined by the value in the second parameter (this is what the "[+1]" means). Note that there will be more than one such SetImage() declarations. You need to change all of them as instructed above.

    3.8 Now, re-assemble the .IL file using ILASM.EXE in a command prompt :

    ilasm /DLL interop.MyCOMServer.il /OUTPUT=interop.MyCOMServer.dll

    3.9 In your C# project, drop the reference to the original interop assembly and then reference the newly created interop assembly. Now, when you look at the definition of the SetImage() method in the ObjectBrowser, it will appear as :

    void SetImage(byte[] pBuffer, int nLength);

    3.10 You may now make a call to SetImage() as follows :

    byte lByteArray[] = new byte[]{ 1,2,3,4,5 };
    TestExeLib.ITestExe1 lTest2 = new TestExeLib.TestExe1Class();
    lTest2.SetImage(lByteArray, lByteArray.Length);

    4. In Summary.

    4.1 You must make sure that the proxy/stub DLL is compiled and registered. This will make the DLL available at runtime to marshal the byte array to the SetImage() method.

    4.2 The newly created interop assembly must of course be available at runtime.

    - Bio.


    Please visit my blog : http://limbioliong.wordpress.com/

    Sunday, May 13, 2012 8:07 AM