VB.NET DLL does not trigger methods in VB6 - vb.net

I have created a DLL in VB.NET and loaded in VB6.
All variables and methods are working very well.
In the DLL, I have a RaiseEvent.
The RaiseEvent from .NET DLL cannot trigger the methods in VB6.
Option Explicit On
Option Strict On
Imports System.Windows.Forms
Imports System.Net.Sockets
Imports System.IO
Imports System.Text
Imports System.Net
Imports System.ComponentModel
Imports System.Threading
Imports System.Runtime.InteropServices
<ComClass(TestDLL.ClassId, TestDLL.InterfaceId, TestDLL.EventsId)>
Public Class TestDLL
Public Const ClassId As String = "6E9AB173-14BD-4DE4-9AE0-A9638FCE40B3"
Public Const InterfaceId As String = "E659D166-F952-489F-899F-0104553B44E4"
Public Const EventsId As String = "1C38AB4A-84B9-4CC2-A090-0C272177ECED"
Public Event Disconnected()
Public Event FirstConnect()
Public Event Waagerecht()
Private Sub Received(ByVal msg As String) Handles Me.Receive
RaiseEvent Waagerecht()
End Sub
This DLL is working in C#, VB.NET and in Labview amazing. Only not in VB6
RaiseEvent Part in VB.NET DLL
And the code in VB6:
Option Explicit
Private WithEvents MyNetClass As TestDll
Private Sub Form_Load()
Set MyNetClass = New TestDll
End Sub
Private Sub Form_Terminate()
Set MyNetClass = Nothing
End Sub
And the methods for triggering:
Private Sub MyNetClass_Waagerecht()
MsgBox "Ich werde angezeigt, sobald dll mir was sagt"
End Sub

Disclaimer: The following is based on issues I have experienced with Office VBA and events being raised on a secondary thread. Due to their similar underlying architecture, I am assuming that VB6 will have similar issues. I do not have VB6 so I can not verify the this.
With that stated, it appears that your code is using NetworkStream.BeginRead and the callback events will arrive on a secondary thread, you may be experiencing problems due to the event being raised on the secondary thread.
To provide thread synchronization to the COM class, I utilize a System.Windows.Forms.Control. The control is created in the class constructor to capture the thread it is created on. Events are defined as Custom Event so that the RaiseEvent method can be defined to use the InvokeRequired property of the synchronizing control. I have only shown the pattern for your Waagerecht event; if this works, you will need to implement the same pattern for your other Public events.
Private synch As ISynchronizeInvoke
Public Sub New()
Dim marshalingControl As New System.Windows.Forms.Control
marshalingControl.CreateControl() ' the handle must be created
synch = marshalingControl
End Sub
Private _Waagerecht As Action
Public Custom Event Waagerecht As Action
AddHandler(value As Action)
_Waagerecht = CType([Delegate].Combine(_Waagerecht, value), Action)
End AddHandler
RemoveHandler(value As Action)
_Waagerecht = CType([Delegate].Remove(_Waagerecht, value), Action)
End RemoveHandler
RaiseEvent()
If _Waagerecht IsNot Nothing Then
If synch.InvokeRequired Then
synch.Invoke(_Waagerecht, {})
Else
_Waagerecht.Invoke()
End If
End If
End RaiseEvent
End Event
Edit: Recommendations For Debugging
A first step add the following to the VB.Net TestDLL class.
Public Sub TestWaagerecht()
RaiseEvent Waagerecht()
End Sub
Then in your VB6 code, add a button to your form and in its Click handler, call MyNetClass.TestWaagerecht. This will verify whether or not the VB6 code is receiving the event.
Hopefully this will work with VB6. Try setting up VB.Net project for debugging. If the project does not have an app.config, add one (Project Menu->Add New Item->"Application Configuration File"). I don't know why, but without the app.Config file, break-points are not hit.
Next go to Project Properties->Debug Tab and set the "Start External Program" to point to the VB6 program. Add a break-point in the Sub TestWaagerecht that I asked you to add above.
When you click on the "Start" button in VS to start debugging, it should launch VB6. Now load your VB6 project and start debugging it. Click on the button that calls TestWaagerecht. Hopefully, the VS break-point will be hit. If it is hit, then all is well and you can start debugging your code to hopefully find the issue.

Related

How to use an ApplicationContext as the startup object in visual studio (without application framework)?

I have created a "Empty Project(.NET Framework) Visual Basic"
I then added an empty Class object
Next I added the reference to System.Windows.Forms
And put the following code in the Class to make it an ApplicationContext
Imports System.Windows.forms
Public Class Class1
Inherits ApplicationContext
End Class
Lastly I tried setting the Startup Object to my Class1
However that is not an option ?
I tried adding a Sub Main to my Class1
but this had no effect
Imports System.Windows.forms
Public Class Class1
Inherits ApplicationContext
Sub main()
End Sub
End Class
At this point, hitting start fails with this error
Error BC30737 No accessible 'Main' method with an appropriate signature was found in 'Project4'.
At this point I could add a module with the following code
and that would compile without errors and run
Module Module1
Sub main()
End Sub
End Module
But that runs for an instant and terminates
In another similar program I have made, I know I could put the following code in a module instead
Module Module1
Public myClass1 As Class1
Sub main()
myClass1 = New Class1
Application.Run(myClass1)
End Sub
End Module
And that would run until I called Application.Exit()
However in this specific case, the Application Framework is disabled so this solution does not work.
So another solution I have found is to use Sleep(50) in a while loop
Imports System.Threading
Module Module1
Public myClass1 As Class1
Sub main()
While True : Thread.Sleep(50) : End While
End Sub
End Module
While I cannot find anything explicitly wrong with this, it strikes me as very inelegant.
It doesn't consume noticeable cpu time or memory
I just wonder if there isn't an equivalent way to do that using just ApplicationContext and dispose of the module entirely ?
If you have any suggestion I would love to hear them at this point.
I wrote this hoping to find a solution as I write the question but I am stuck at this point.
Where does the code go after Application.Run(myClass1)
It's probably looping something inoffensive while waiting for something to happen but what ?
thanks
Here is how to make the barest bone (console or non-console) application using the ApplicationContext Class.
In Visual Studio create a "Empty Project(.NET Framework) Visual Basic"
then add an empty class
Add the following reference by right-clicking on Reference in the Solution Explorer
System.Windows.Forms
Now paste the following code in your class
Imports System.Windows.Forms
Public Class Class1
Inherits ApplicationContext
Shared Sub Main()
Dim myClass1 As Class1
myClass1 = New Class1
System.Windows.Forms.Application.Run(myClass1)
End Sub
End Class
Now hit start and you've got a perfectly working useless application that does nothing with least amount of stuff that I could manage.
If you are here, I suspect that you also would like to manually compile this project from anywhere without having visual studio installed.
Very easy ! In your project folder, create a text file and rename it compile.bat
Paste this code in compile.bat
path=%path%;c:\windows\microsoft.net\framework\v4.0.30319
VBC /OUT:bin\Debug\app.exe /imports:System.Windows.Forms /T:winexe *.vb
pause
BONUS ROUND
This app does nothing, how do you know the events even work ?
Let's add, a tray icon, a resource file to add the tray icon and some events
First add a new reference to system.drawing
Go to project properties
Create a new resource file
Add some random *.ico file
Now replace the following code in the class
Imports System.Windows.Forms
Public Class Class1
Inherits ApplicationContext
Shared Sub Main()
Dim myClass1 As Class1
myClass1 = New Class1
System.Windows.Forms.Application.Run(myClass1)
End Sub
Private WithEvents Tray As NotifyIcon
Public Sub New()
Tray = New NotifyIcon
Tray.Icon = My.Resources.appico
Tray.Text = "Formless tray application"
Tray.Visible = True
End Sub
Private Sub AppContext_ThreadExit(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles Me.ThreadExit
'Guarantees that the icon will not linger.
Tray.Visible = False
End Sub
Private Sub Tray_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles Tray.Click
Console.WriteLine("Tray_Click")
End Sub
Private Sub Tray_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles Tray.DoubleClick
Console.WriteLine("Tray_DoubleClick")
System.Windows.Forms.Application.Exit()
End Sub
End Class
And now, to compile this manually
First, find resgen.exe somewhere on your harddrive
and copy it in your "My Project" folder, you can probably download it from somewhere
Make sure you've got version 4 or above of resgen though
And overwrite the compile.bat with this new code
path=%path%;c:\windows\microsoft.net\framework\v4.0.30319
cd "My Project"
resgen /usesourcepath "Resources.resx" "..\bin\debug\Resources.resources"
cd ..
VBC /OUT:bin\Debug\app.exe /resource:"bin\debug\Resources.resources" /imports:System.Drawing,System.Windows.Forms /T:winexe *.vb "My Project\*.vb"
pause
Unfortunately, this last compile.bat doesn't work for me, it might work for you.
Mine compiles just fine, but the app crashes on start.
This worked in another project where I made the Resource files by hand but something is wrong with
I tried adding the root namespace to the build with /rootnamespace:Project5 but this had no effect
Still good enough for now, this last bit will be edited if a fix is ever found

VB.NET getting error: Class 'Application' must implement 'Sub InitializeComponent()'

I'm doing a VB.NET WPF application using VS2013, and I am just trying to find and use the right entry point.
I have read tons of answers on this, one saying something and the other saying the opposite. Mainly they say: the entry point of your project is the autogenerated main() you can find in your Application.g.vb. Yes, ok, very nice but...it is a generated file, not a good idea to modify it. So I searched the net on how to implement my own main() method, and the common answer I've found is:
Select Application.xaml and change its build action to "Page"
Create your own main method in Application.xaml.vb with this signature:
_
Public Shared Sub Main()
Dim app As Application = New Application()
app.InitializeComponent()
app.Run()
End Sub
Go to your project properties, untick "Enable applpication framework" and select Sub Main as startup for your application.
And so I have done but I continuously get this error:
Error 3 Class 'Application' must implement 'Sub InitializeComponent()' for interface 'System.Windows.Markup.IComponentConnector'.
this is the Application.g.i.vb file it generates:
#ExternalChecksum("..\..\Application.xaml","{406ea660-64cf-4c82-b6f0-42d48172a799}","DB788882721B2B27C90579D5FE2A0418")
'------------------------------------------------------------------------------
' <auto-generated>
' This code was generated by a tool.
' Runtime Version:4.0.30319.42000
'
' Changes to this file may cause incorrect behavior and will be lost if
' the code is regenerated.
' </auto-generated>
'------------------------------------------------------------------------------
Option Strict Off
Option Explicit On
Imports System
Imports System.Diagnostics
Imports System.Windows
Imports System.Windows.Automation
Imports System.Windows.Controls
Imports System.Windows.Controls.Primitives
Imports System.Windows.Data
Imports System.Windows.Documents
Imports System.Windows.Ink
Imports System.Windows.Input
Imports System.Windows.Markup
Imports System.Windows.Media
Imports System.Windows.Media.Animation
Imports System.Windows.Media.Effects
Imports System.Windows.Media.Imaging
Imports System.Windows.Media.Media3D
Imports System.Windows.Media.TextFormatting
Imports System.Windows.Navigation
Imports System.Windows.Shapes
Imports System.Windows.Shell
'''<summary>
'''Application
'''</summary>
<Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Partial Public Class Application
Inherits System.Windows.Application
Implements System.Windows.Markup.IComponentConnector
Private _contentLoaded As Boolean
'''<summary>
'''InitializeComponent
'''</summary>
<System.Diagnostics.DebuggerNonUserCodeAttribute(), _
System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")> _
Public Sub InitializeComponent()
#ExternalSource("..\..\Application.xaml",4)
Me.StartupUri = New System.Uri("MainWindow.xaml", System.UriKind.Relative)
#End ExternalSource
If _contentLoaded Then
Return
End If
_contentLoaded = True
Dim resourceLocater As System.Uri = New System.Uri("/FatLink;component/application.xaml", System.UriKind.Relative)
#ExternalSource("..\..\Application.xaml",1)
System.Windows.Application.LoadComponent(Me, resourceLocater)
#End ExternalSource
End Sub
<System.Diagnostics.DebuggerNonUserCodeAttribute(), _
System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0"), _
System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never), _
System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes"), _
System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity"), _
System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")> _
Sub System_Windows_Markup_IComponentConnector_Connect(ByVal connectionId As Integer, ByVal target As Object) Implements System.Windows.Markup.IComponentConnector.Connect
Me._contentLoaded = True
End Sub
End Class
so...as Sub InitializeComponent() is there, why the hell do I keep getting this error?
**EDIT:**My Application.xaml.vb is just that:
Partial Public Class Application
<System.STAThreadAttribute(), _
System.Diagnostics.DebuggerNonUserCodeAttribute(), _
System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")> _
Public Shared Sub Main()
Dim app As Application = New Application()
app.InitializeComponent()
app.Run()
End Sub
End Class
I have managed to reproduce you problem:
Create a new WPF VB.NET application
Create the custom Main in the Application.xaml.vb file as from your post
In the project properties uncheck the Enable Application Framework (automatically the startup object becomes our Main sub)
Set for the Application.xaml file the Build Action property to Page
At this point, the line that call app.InitializeComponent() is underlined with a red squiggle to signify that the project cannot find the InitializeComponent method. If you try to compile at this point you get the mentioned error.
Final step:
In Application.xaml file remove the
StartupUri="MainWindow.xaml"
Now the squiggle disappears and the compilation ends correctly.
If you want to start you own main window you could change the Run line with
app.Run(New MainWindow())
If you get that error, you probably haven't stated that your app is to start with your Main() method. So:
"Go to your project settings and untick the Enable application framework checkbox (if it's ticked) and select Sub Main as the Startup object , then add your main-method to Application.xaml.vb (plus the logger and other stuff)."
When you have done that, VS won't create "its own" Main-method and will rather call the one you defined in Application.xaml.
Taken from: https://social.msdn.microsoft.com/Forums/vstudio/en-US/d7ef493f-27be-4f32-8ff4-a014078f572c/custom-systemwindowsapplication-in-vb-net?forum=wpf
Check also this: http://ugts.azurewebsites.net/data/UGTS/document/2/3/32.aspx

Cross-thread communication and field-updating in VB.NET

I'm having some trouble getting cross-thread communication/field-updating working properly in my VB.NET 2010 program. I'm trying to update a field on my main form whenever a thread that I've started throws an event. Here's a simplified version of my code:
My main form:
Public Class Main
' stuff
' Eventually, startProcessing gets called:
Private Sub startProcessing()
Dim processingClass = New MyProcessingClass("whatever")
AddHandler processingClass.processStatusUpdate, AddressOf handleProcessStatusUpdate
Dim processingThread = New Thread(AddressOf processingClass.process)
processingThread.Start()
End Sub
Private Sub handleProcessStatusUpdate(statusUpdate As String)
txtMainFormTextBox.Text = statusUpdate ' InvalidOperationException
' "Cross-threaded operation not valid: Control 'txtMainFormTextBox' accessed from a thread other than the thread it was created on"
End Sub
End Class
The class which raises the event:
Public Class MyProcessingClass
Private whatever As String
Public Event processStatusUpdate(status As String)
Public Sub New(inWhatever As String)
whatever = inWhatever
End Sub
Public Sub process()
' do some stuff
RaiseEvent processStatusUpdate(whatever)
End Sub
End Class
As you can see, the handler in my main class doesn't have access to the TextBox I need since it was triggered by a different thread (I think). I've tried a number of other approaches to get this working, including:
Moving the event handler to MyProcessingClass, and passing txtMainFormTextBox by reference (ByRef) to the class.
Having the actual thread start inside of MyProcessingClass instead of Main.
None of these have worked. Clearly there's a concept that I'm missing here. What's the best way to get this done? Thanks!
You need to update the textbox on the UI thread by calling BeginInvoke.
You should use the BackgroundWorker component, which does all of this for you.
Simply handle the DoWork and ProgressChanged events.

Handling VB.NET events in VB6 code

I have some VB6 code that instantiates a class which handles events that are being raised from a VB.NET component. The VB6 is pretty straightforward:
private m_eventHandler as new Collection
...
public sub InitSomething()
dim handler as EventHandler
set handler = new EventHandler
m_eventHandler.Add handler
...
m_engine.Start
end sub
Note that the event handler object has to live beyond the scope of the init method (which is why it is being stored in a Collection). Note also that m_engine.Start indicates the point in the program where the VB.NET component would start raising events.
The actual event handler (as requested):
Private WithEvents m_SomeClass As SomeClass
Private m_object as Object
...
Private Sub m_SomeClass_SomeEvent(obj As Variant)
Set obj = m_object
End Sub
Note that m_object is initialized when an instance of EventHandler is created.
The VB.NET code which raises the event is even simpler:
Public ReadOnly Property SomeProp() As Object
Get
Dim obj As Object
obj = Nothing
RaiseEvent SomeEvent(obj)
SomeProp = obj
End Get
End Property
My problem is that when I debug the VB6 program, the first time InitSomething gets called, the event will not be handled (the VB6 event handler is never entered). Subsequent calls to InitSomething does work.
Everything works as I would have expected when I run the program outside the debugger. At this point, I'm not even sure if this is something I should be worried about.
It may or may not be relevant but the VB.NET was converted from a VB6 using the Visual Studio code conversion tool (and subsequently manually cleaned up).
I've found that if you are writing .Net Components for Consumption in VB6 (or any other COM environment) the utilisation of Interfaces is absolutely criticial.
The COM templates that comes out of the box with VStudio leave a lot to be desired especially when you are trying to get Events to work.
Here's what I've used.
Imports System.Runtime.InteropServices
Imports System.ComponentModel
<InterfaceType(ComInterfaceType.InterfaceIsDual), Guid(ClientAction.InterfaceId)> Public Interface IClientAction
<DispId(1), Description("Make the system raise the event")> sub SendMessage(ByVal theMessage As String)
End Interface
<InterfaceType(ComInterfaceType.InterfaceIsIDispatch), Guid(ClientAction.EventsId)> Public Interface IClientActionEvents
<DispId(1)> Sub TestEvent(ByVal sender As Object, ByVal e As PacketArrivedEventArgs)
End Interface
<ComSourceInterfaces(GetType(IClientActionEvents)), Guid(ClientAction.ClassId), ClassInterface(ClassInterfaceType.None)> _
Public Class ClientAction
Implements IClientAction
Public Delegate Sub TestEventDelegate(ByVal sender As Object, ByVal e As PacketArrivedEventArgs)
Public Event TestEvent As TestEventDelegate
public sub New()
//Init etc
end sub
public sub SendMessage(theMessage as string) implements IClientAction.SendMessage
onSendMessage(theMessage)
end sub
Protected Sub onSendMessage(message as string)
If mRaiseEvents Then
RaiseEvent TestEvent(Me, New PacketArrivedEventArgs(theMessage))
End If
End Sub
end Class
I've been able to get COM and .Net consumers of the Assembly/Component to work properly with events and be able to debug in and out of the component.
Hope this helps.
Just something to try - I have an inherent distrust of "As New .."
Can you try
private m_eventHandler as Collection
public sub InitSomething()
dim handler as EventHandler
set handler = new EventHandler
If m_eventHandler Is Nothing Then
Set m_eventHandler = New Collection
End if
m_eventHandler.Add handler
...
m_engine.Start
end sub
Alas, I've got no idea why this works in normal execution and not in debug except some vague suspicions that it's to do with .NET being unable to instantiate the VBA.Collection object (MS recommends that you write a quick VB6 component to do so), but since you're not creating collections in .NET code, it is still just a vague suspicion.

Unit Testing of .NET Add-In for Microsoft Office

Has anyone got any suggestions for unit testing a Managed Application Add-In for Office? I'm using NUnit but I had the same issues with MSTest.
The problem is that there is a .NET assembly loaded inside the Office application (in my case, Word) and I need a reference to that instance of the .NET assembly. I can't just instantiate the object because it wouldn't then have an instance of Word to do things to.
Now, I can use the Application.COMAddIns("Name of addin").Object interface to get a reference, but that gets me a COM object that is returned through the RequestComAddInAutomationService. My solution so far is that for that object to have proxy methods for every method in the real .NET object that I want to test (all set under conditional-compilation so they disappear in the released version).
The COM object (a VB.NET class) actually has a reference to the instance of the real add-in, but I tried just returning that to NUnit and I got a nice p/Invoke error:
System.Runtime.Remoting.RemotingException : This remoting proxy has no channel sink which means either the server has no registered server channels that are listening, or this application has no suitable client channel to talk to the server.
at System.Runtime.Remoting.Proxies.RemotingProxy.InternalInvoke(IMethodCallMessage reqMcmMsg, Boolean useDispatchMessage, Int32 callType)
at System.Runtime.Remoting.Proxies.RemotingProxy.Invoke(IMessage reqMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
I tried making the main add-in COM visible and the error changes:
System.InvalidOperationException : Operation is not valid due to the current state of the object.
at System.RuntimeType.ForwardCallToInvokeMember(String memberName, BindingFlags flags, Object target, Int32[] aWrapperTypes, MessageData& msgData)
While I have a work-around, it's messy and puts lots of test code in the real project instead of the test project - which isn't really the way NUnit is meant to work.
This is how I resolved it.
Just about everything in my add-in runs from the Click method of a button in the UI. I have changed all those Click methods to consist only of a simple, parameterless call.
I then created a new file (Partial Class) called EntryPoint that had lots of very short Friend Subs, each of which was usually one or two calls to parameterised worker functions, so that all the Click methods just called into this file. So, for example, there's a function that opens a standard document and calls a "save as" into our DMS. The function takes a parameter of which document to open, and there are a couple of dozen standard documents that we use.
So I have
Private Sub btnMemo_Click(ByVal Ctrl As Microsoft.Office.Core.CommandBarButton, ByRef CancelDefault As Boolean) Handles btnMemo.Click
DocMemo()
End Sub
in the ThisAddin and then
Friend Sub DocMemo()
OpenDocByNumber("Prec", 8862, 1)
End Sub
in my new EntryPoints file.
I add a new AddInUtilities file which has
Public Interface IAddInUtilities
#If DEBUG Then
Sub DocMemo()
#End If
End Interface
Public Class AddInUtilities
Implements IAddInUtilities
Private Addin as ThisAddIn
#If DEBUG Then
Public Sub DocMemo() Implements IAddInUtilities.DocMemo
Addin.DocMemo()
End Sub
#End If
Friend Sub New(ByRef theAddin as ThisAddIn)
Addin=theAddin
End Sub
End Class
I go to the ThisAddIn file and add in
Private utilities As AddInUtilities
Protected Overrides Function RequestComAddInAutomationService() As Object
If utilities Is Nothing Then
utilities = New AddInUtilities(Me)
End If
Return utilities
End Function
And now it's possible to test the DocMemo() function in EntryPoints using NUnit, something like this:
<TestFixture()> Public Class Numbering
Private appWord As Word.Application
Private objMacros As Object
<TestFixtureSetUp()> Public Sub LaunchWord()
appWord = New Word.Application
appWord.Visible = True
Dim AddIn As COMAddIn = Nothing
Dim AddInUtilities As IAddInUtilities
For Each tempAddin As COMAddIn In appWord.COMAddIns
If tempAddin.Description = "CobbettsMacrosVsto" Then
AddIn = tempAddin
End If
Next
AddInUtilities = AddIn.Object
objMacros = AddInUtilities.TestObject
End Sub
<Test()> Public Sub DocMemo()
objMacros.DocMemo()
End Sub
<TestFixtureTearDown()> Public Sub TearDown()
appWord.Quit(False)
End Sub
End Class
The only thing you can't then unit test are the actual Click events, because you're calling into EntryPoints in a different way, ie through the RequestComAddInAutomationService interface rather than through the event handlers.
But it works!
Consider the various mocking frameworks NMock, RhinoMocks, etc. to fake the behavior of Office in your tests.