Page 1 of 1

IGuestProcess.Read raises exception

Posted: 10. Apr 2013, 09:39
by LeeEll
Hi!

I am working on an app that need to execute a program in the guest, send input to it and get output from it. I use the COM API, but there are several problems and I now need some help on this.

I use C# NET 4.0, host is Win7 64-bit, the guest OS is WinXP 32-bit, VirtualBox is ver 4.2.10.

I need to be able to interact with any program on the guest i have executed. But because I also need to get the current IP addresses in the guest, I have used ipconfig as a first attempt.

The problem now is that i get an exception (see code below) that i can't get around. The return type for IGuestProcess.Read() is System.Array (that is at least what mouse over in VisualStudio is showing and that is the only return type that compiles). But when the code is executed the exception suggests that the return type should be something else. HELP!!!!

The code i use to execute ipconfig and get output from it looks like this (simplified for brevity):

Code: Select all

                // Get GuestSession
                session = new Session(); // (Virtualbox running on x64 OS requires that this prog is also running as x64!)
                machine.LockMachine(session, LockType.LockType_Shared);
                guestSession = session.Console.Guest.CreateSession(userName, password, "", "");

                // Setup for Command Execution
                var cmdArgs = new string[] { }; // Empty arguments array
                var envVars = new string[] { }; // Empty environment vars
                var flags = new ProcessCreateFlag[] {
                    ProcessCreateFlag.ProcessCreateFlag_WaitForStdOut, 
                };

                // Execute ipconfig on guest
                var guestProcess = guestSession.ProcessCreate(@"c:\windows\system32\ipconfig.exe", cmdArgs, envVars, flags, 5000);
                if (guestProcess == null) {
                    // Return error. Code omitted for brevity
                    return mrd;
                }

                // Wait up to 5 seconds for process to start
                var waitCount = 0;
                if (guestProcess.Status != ProcessStatus.ProcessStatus_Error) {
                    while (waitCount <= 200 && guestProcess.Status != ProcessStatus.ProcessStatus_Started && guestProcess.Status != ProcessStatus.ProcessStatus_Error) {
                        Debug.WriteLine("Waiting... ({0})", waitCount);
                        waitCount++;
                        Thread.Sleep(25);
                    }
                }

                // Timeout or process error?
                if (waitCount > 200 || guestProcess.Status != ProcessStatus.ProcessStatus_Started) {
                    // Return error. Code omitted for brevity
                }

                // Read output from ipconfig
                System.Array output = guestProcess.Read(0, 2000, 5000); // Exception!!!

                // Exception on line above:
                // Specified array was not of the expected type.
                //   at System.StubHelpers.MngdSafeArrayMarshaler.ConvertSpaceToManaged(IntPtr pMarshalState, Object& pManagedHome, IntPtr pNativeHome)
                //   at VirtualBox.IGuestProcess.Read(UInt32 aHandle, UInt32 aToRead, UInt32 aTimeoutMS)


Re: IGuestProcess.Read raises exception

Posted: 10. Apr 2013, 14:39
by noteirak
Checking the SDK, it looks like an array is to be expected indeed.
I do not use C# but can't you get the class name to check if it is an array returned? Also, maybe you do do get an array but just not the kind you expect - the SDK sends back an array of bytes, but it could be different here. Try to see if you can actually get the class name of the returned object aswell as the class name of the objects containted inside

Re: IGuestProcess.Read raises exception

Posted: 10. Apr 2013, 19:26
by LeeEll
Just to make sure, I have now tried all integral C# array datatypes:
byte[]
sbyte[]
char[]
short[]
ushort[]
int[]
uint[]
long[]
ulong[]
And I have even tried string[].
All give the same exception.

>can't you get the class name to check if it is an array returned?
Hmm? I am not sure what you want me to do here, Visual Studio indicates it is a System.Array, but that doesn't work.

>the SDK sends back an array of bytes
Yes, that would be either byte[] or sbyte[], but that doesn't work.

>Try to see if you can actually get the class name of the returned object aswell as the class name of the objects containted inside
I don't know how to do that. The exception is raised deep withing the bowels of NET marshaling. As far a I know, thats a place unreachable to me.

I am beginning to think this is a bug in the COM API.

There is another bug (or is it a feature?) that might be related when you try to do:
Session = new Session();
The above line will throw an 80040154 Class not registered exception if Platform target is not correctly set in project settings:
When running this code under Win7 64-bit, platform target should be set to x64.
When running this code under XP 32-bit, platform target should be set to x86.

Could someone with C# knowhow please have a look at this?

Re: IGuestProcess.Read raises exception

Posted: 11. Jun 2013, 09:00
by Magnus Madsen
Hi LeeEll,
I replied to your message on the mailing list as well; but in case someone are reading the forums but not the list I'll just mirror the message here:

To use the VirtualBox API correctly with C#, you'll need to customize the marshaller.

For anyone else who wishes to use it, this is the process I found the
easiest:

* Use TlbImp to generate an interop DLL from VirtualBox.tlb
* Use IlDAsm to generate an MSIL source code file from the dll.
* Edit the MSIL and replace "marshal (safearray unsigned int8)" with
just "(safearray int8)".
* Use IlAsm to generate a new DLL from the edited MSIL.

I haven't tested it thoroughly, but it does seem to work for reading from
processes and getting screenshots at least - two things that didn't work
before without using a wrapper.

Re: IGuestProcess.Read raises exception

Posted: 11. Jun 2013, 09:44
by LeeEll
Hi Magnus,
thanks for your reply. It's great to hear there is a fix.
* Use TlbImp to generate an interop DLL from VirtualBox.tlb
* Use IlDAsm to generate an MSIL source code file from the dll.
* Edit the MSIL and replace "marshal (safearray unsigned int8)" with just "(safearray int8)".
* Use IlAsm to generate a new DLL from the edited MSIL
Great, how very user frendly! (not)

I guess this is something that has to be repeated every time VBox is updated? If so, I think that suggests some kind of automated script for this is needed.

Using TlbImp , IlDAsm and IlAsm is something I have never done. Sounds complicated, and like it could take a long time to figure out how to do it if you never done it before.

Could you please elaborate a little? Maybe give me a nice detailed step by step method description? Please!

It would be a great help if I could use the API as it is supposed to work. The apparent lack of interest from Oracle in solving this is disappointing.

(I am monitoring this forum, not the mailing list. So please reply here if you have anything more for me)

Re: IGuestProcess.Read raises exception

Posted: 11. Jun 2013, 11:01
by Magnus Madsen
Sorry, I realized that it wasn't very throughout. :oops:

Firstly; here is the modified Interop DLL I've created in case you just wish to use it. (For VirtualBox 4.2.12). : https://dl.dropboxusercontent.com/u/108 ... om.dll.zip

To get your code to work, all you should need to do is remove the reference to VirtualBox in Visual Studio and add a reference to this DLL instead.

You're right that it will need to be updated, although it should only be necessary when there are changes to the COM API, not just when new versions of VirtualBox are released. How exactly to mitigate the problem I'm not sure; it might be possible to modify the TLB that VirtualBox generates so that it is correctly marshalled by Visual Studio, but it may also just be that Visual Studio can't correctly marshal arrays of signed integers automatically. (This is what causes the problem; VirtualBox uses arrays of signed integers for the byte arrays it returns/receives, but VS tries to marshal them as unsigned integers, which doesn't work.)

Now as for the process to create it:
* You'll need VirtualBox.tlb - this is found in the VirtualBox SDK under bindings\mscom\lib.
* TlbImp is found in the Windows SDK under NETFX 4.0 Tools, as is IlDAsm.
* IlAsm should be present by standard in the dotNET framework folder under C:\Windows.

Run the following commands:

Code: Select all

TlbImp.exe "[path to VirtualBox.tlb"] /out:VirtualBox.Interop.dll

Code: Select all

IlDasm.exe VirtualBox.Interop.dll /out=VirtualBox.Interop.il
You should delete VirtualBox.Interop.dll.

Now you have the VirtualBox.Interop.il that contains the MSIL source code. Edit this file and replace any instance of "marshal (safearray unsigned int8)" with "marshal (safearray int8)"

Now run:

Code: Select all

IlAsm.exe /DLL VirtualBox.Interop.il
If everything went as it should, you'll now have a VirtualBox.Interop.dll that is identical to the one I uploaded.

Hope it helps :)