Thursday, July 26, 2007

I Have Adam's Book!

Just wanted to say that I got Adam Nathan's COM Interp book, and I'm already taking notes as to what I need to blog about. :) It's already answering questions that I've had, and while I thought what I was recently reading was going to give insight into the problems I had previously, it just skirted the issue and said "More in Chapter 7, 20, and 24".

This book is huge. We're talking bigger-than-the-Bible huge. So big, they wouldn't bind it with just one binding. No, they bound it as two seperate tomes. Tomes of arcane COM knowledge. ;)

Although I'm only into chapter 4, I can tell this book has what I need. Heh, well, I've also heard that if this book doesn't have the answers to your COM-interop questions, then no other book will, soooo. :) It hasn't been updated for .NET 2.0/3.0/3.5. I'm curious to know if any of the version-specific remarks Adam makes regarding the implementation of the SDK at the time of his writing have changed. I would guess the answer is "not much" because who would miss the chance to write a second edition!? :D

Anyway, before I go, does anyone else think it's kinda weird the way I just happened to have "N" post-it notes for Adam *N*athan's book?  
Posted by Picasa

Wednesday, July 18, 2007

VMWare : Loosing eth0 after you've copied your VM

Background

Here's a bit of weird-behavior I've noticed when working with some of our production virtual machines (running Gentoo Linux) here at work.

In order to update the OS's on our virtual machines, I will copy them to my local machine, power them on, update them, and then push the updated OS's back out into production at the earliest convenience.

When you copy the VM from one location to another, VMWare notices this and asks you "Hey, it looks like this machine has been physically moved or copied, do you want me to create a new VM-UUID?" If you answer in the affirmative, VMWare internally regenerates any unique-identifiers tied to this virtual machine. The one thing that's really noticeable is that any virtual ethernet adapters get their MAC addresses changed.

The problem I've experienced is that when you power on the new-UUID'd VM, you no longer have an ethernet adapter. Gentoo tries to bring-up eth0 and it says "network interface eth0 does not exist" and "Please verify hardware or kernel module (driver)"


Explanation

"So, what's going on?"

Try a couple things:
  • If you run lspci you should still see the ethernet adapter.

  • If you run 'dmesg' and should see the kernel find the network card and it even calls it eth0


"So, where does eth0 go?"

Try running ifconfig -a. I bet you now have an eth1 and it's MAC address matches the newly-generated virtual MAC address specified in the virtual machine's .vmx file.

"Oh great, so every time I copy the VM I need to update the system configs to use the new eth1, or eth2, etc!?!?!"

No, hush, I'm getting to the answer.

The problem stems from the linux distro 'remembering' the MAC address of the network adapter and expecting it to be the same between boots. In the case of our Gentoo VM's, it's udev that mucks this up.

"Ok, fine, it's udev's fault. We know it's broken because it's expecting the ethernet adapter to have a MAC address that it no longer has. What to do?

The Answer

To fix this problem you need to tell your linux distro the VM's new MAC address. How you do this can vary by distro. In my spelunking, I found a few ways:

  • In Gentoo do one of the following: (Do #1, it's the easiest.)
    1. Delete /etc/udev/rules.d/70-persistent-net.rules and reboot. Your eth0 should be back.
      • 2007/09/13 Update: This almost-always works for me. But, for some reason, sometimes it seems to confuse udev even more; after rebooting, I'll have an eth2 or eth3. When this happens, I end up following #2, making sure the udev config file has 'eth0' listed, and not eth1, eth2, or eth3.
    2. Edit /etc/udev/rules.d/70-persistent-net.rules (or whatever it's named) to match your new MAC address and reboot. Your eth0 should be back.
  • Other distros:
    • Look for, (and edit if you find,) /etc/iftab
    • Look for, and delete, then reboot /etc/udev/rules.d/25-iftab.rules
    • Look for, (and edit if you find,) /etc/sysconfig/network-scripts/ifcfg-eth0



Give Credit Where Credit Is Due

I got hints from a number of pages, but in the end, it was the folks over at the VMWare discussion forums for the win:
VMWare Discussion Forums

Friday, July 13, 2007

Marshalling Arrays To VB6's COM Funland pt2

Ok, yesterday I figured out how to get an array of interfaces (actually, objects that implement an interface, but go along with my sloppy grammer, ok?) out of C#, through the COM Callable Wrapper (CCW) and into COM-land.

Wouldn't you know it, but I also need to be able to pass an array of objects back into C# from COM-land.

Well, I'm here to say that figuring this out was a lot easier than yesterday's problem. At first I tried something like:


[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[ComVisible(true)]
public interface ITest {
   void TakeBusinessObjects(IBusinessObject[] bos);
}



Well, that doesn't work. In VB6, when you try to compile something that calls TakeBusinessObjects(...) you get the a compile error like the following:


Function or interface marked as restricted, or the function uses an Automation type not supported in Visual Basic



Well, my Google digging on that particular error actually proved fruitful and the answer is simple:


[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[ComVisible(true)]
public interface ITest {
   void TakeBusinessObjects(ref IBusinessObject[] bos);
}


(credit goes to Jon Wojtowicz for his Using COM Callable Wrappers to Extend Existing Visual Basic 6.0 Applications post at EggHeadCafe.)

Hooray! That works! The array gets passed back into .NET-land, and things seem happy. Except, I wouldn't be writing this post if I didn't have a problem, right? Well, TakeBusinessObjects(...) doesn't throw an exception, and it reaches it's return statement successfully. Unfortunately, something gets lost in translation while returning control to VB6-land because I intermediately upon returning, VB6 raises this error:


Class does not support Automation or does not support expected interface
Number: 430



This error makes it sound like I've developed on v2 of some COM component, but I've deployed the compiled EXE on a machine that only has v1 of the COM component. What I don't understand is that the array gets passed through the CCW into .NET-land! It works! Something just goes wrong on the way back to VB-land.

Update: The Answer! (Kind of)


I gotta hand it to my buddy Jimmy - that guy has given me the "Try XYZ" that has fixed whatever problem I was tackling so many times -- and he's come through once again! He suggested that, I take a look at the [In] and [Out] attributes.

By default, when I compile my COM component, it's being assumed that I not only want to be able to take in an array reference, but that I also want to push any changes made to that array reference back out to the caller. Well, lucky for me, I don't make any changes to the array once it's in .NET-land, and I can flag the parameter as only needing to come into the method, and not out. Like this:


[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[ComVisible(true)]
public interface ITest {
   void TakeBusinessObjects([In] ref IBusinessObject[] bos);
}



So, while this fixes most of the situations where I would need to pass an array from COM-land into .NET-land, it still irks me that I don't know why VB6 throws that error if the CCW marshaller tries to move the array back out of .NET-land when the method returns.

Wednesday, July 11, 2007

Marshalling Arrays To VB6's COM Funland

Ok, so, please, don't ask why I've found myself writing a COM component in C#, and am using it in VB6. Just accept that fact that I need to.

This COM component needs to return an array of data to VB6. At first I thought "Why not just have my C# project reference the VB6 runtime via interop, and I'll be able to instantiate a VB6 Collection class, and return that to VB6, and it will be happy.

But no, when I try to instantiate a VBA.CollectionClass I get:


Retrieving the COM class factory for component with CLSID {A4C4671C-499F-101B-BB78-00AA00383CBB} failed due to the following error: 80040154.




After much googling, the closest answer I could get was that I was trying to instantiate a VBA.Collection on an x64 platform. Um, the last time I checked my Pentium-D was a 32 bit processor. I even went so far as to force my C# COM component to compile to x86 specifically instead of the 'Any CPU' configuration. Still, the error persisted.

So, I had to bail on that idea, and come up with "Hey, what about just returning an array? What a great idea! I performed a test.

I created a test COM visible interface and class, something like:



[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[ComVisible(true)]
public interface ITest {
   String[] GetStrings();
}

[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
public class Test : ITest {
   public String[] GetStrings() {
      List foo = new List();
      foo.Add("hello");
      foo.Add("bye-bye");

      return foo.ToArray();
   }
}


Then, the corresponding test-code in VB6:


Dim testObject as Test
Dim strings() as String

set testObject = new Test
strings = testObject.GetStrings()



It worked.

I was happy, and I continued working on my C# code as planned.


Days later, I have hundreds of lines of code down in C#, and I'm at a good point to test what I've written. Now, my objects weren't going to be returning string-arrays like the test case above, rather, they're going to be returning arrays of an interface defined in my C# COM component, something like:


[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[ComVisible(true)]
public interface IBusinessObject {
   Int32 GetSomething();
}

[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[ComVisible(true)]
public interface IFoo {
   IBusinessObject[] GetBusinessObjects();
}



[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
public class BusinessObject : IBusinessObject {
   /* you get the idea */
}

[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
public class Foo : IFoo {
   public IBusinessObject[] GetBusinessObjects() {
      List bos = new List();
      /* Add some BusinessObjects to 'bos' */

      return bos.ToArray();
   }
}



Then, the corresponding VB6 code:


Dim foo as Foo
Dim myObjects() as BusinessObject

set foo = new Foo
myObjects = foo.GetBusinessObjects()




Unfortunately, it didn't work. While no exception was thrown in C#, somewhere between return bos.ToArray() and VB6's assignment to the myObjects array a "Type Mismatch" error was thrown in VB6. I couldn't figure out why, though.

I tried catching the array returned from .GetBusinessObjects() into a Variant like:


Dim myObjects as Variant
myObjects = foo.GetBusinessObjects()



I still received the "Type Mismatch" error. I was really lost here, because in VB6-land a Variant is the closest thing you're going to get to a generic object pointer as you're going to get.

I again didn't garner much assistance from another thorough Google spelunking. In my searches, I did stumble across the MarshalAs attribute, but since I'm not a COM-master I wasn't entirely sure what I should be marshaling an array of interfaces as in order to safely reach COM-world. I blindly tried a number of things, and always ended up with "Type Mismatch". I was loosing hope. (As a side note I desperately need to get a copy of Adam Nathan's book .NET and COM: The Complete Interoperability Guide.)

Finally, I stumbled upon it, and I'm not entirely sure why I didn't try it first:

The Answer!

[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[ComVisible(true)]
public interface IFoo {
   [MarshalAs(UnmanagedType.AsAny)]
   IBusinessObject[] GetBusinessObjects();
}



What's weird is that this produces a compiler warning making it sound like the attribute is not of any use:


Type library exporter warning processing 'MyNamespace.IFoo.GetBusinessObjects(#0), MyProject'. Warning: Type contains [MarshalAs(AsAny)], which is only valid for PInvoke. The MarshalAs directive was ignored.



If you place the attribute on the actual implementation you do not get the compiler warning...but you also get the Type Mismatch again. So, it would seem the warning is ignorable as it is applying to something.

Something tells me that once I get my hands on Adam Nathan's book, this will become a lot more obvious to me, and/or I'll find a better answer. Either way, I've got a solution for now.


So, why this post? Mostly as a note-to-self as to how to solve the problem in the future, but there's the hope that some poor sap such as myself has had the exact same problem and that this will turn up for them while they spelunk Google.

If this proves useful to anyone, please, drop me a line.