We have a very large application that is written in VB6. It has hundreds of forms/user controls/classes etc. We have started migrating to .Net (currently on framework v2, although just about to change that to v4) with a COM exposed DLL by converting individual forms on an 'as and when' basis. This is all working just fine so far.
Some of the more complex VB6 forms call multiple other forms, which call forms etc etc, so conversion is a bottom up process. There are several instances where it would make life easier in the short term if we could call a VB6 form from the .Net DLL, perhaps by passing some form of object reference to the form into .Net. Although I'm pretty sure this isn't possible, I want to check to be certain.
So, is this possible?
We've called VB6 forms from a .Net EXE by referencing a Vb6 DLL from the EXE, it works. I think the same approach should work from a .Net DLL. If you want the VB6 form to be non-modal, you have to use a VB6 ActiveX EXE instead.
A piecemeal approach to migration is a good idea. Divide the application into manageable chunks, and migrate each chunk separately.
I don't know how clean your code is, so take this with a few grains of salt. But here is a a rough outline in pseudo-code (and vastly simplified) of the approach I would take:
In a shared library, exposed to both .NET and COM define an interface for each of your forms:
public interface ILoginForm
property UserName as String
property Password as String
function DisplayModal as Boolean 'True for login, false for cancel...or expose an enum
end interface
public interface IContactEditor
property FirstName as String
property LastName as String
property EmailAddress as String
function DisplayModal as Boolean 'True for save, false for cancel...or expose an enum
end interface
etc, etc for each form in your application.
Next, define a FormFactory interface:
public interface IFormFactory
function CreateLoginForm as ILoginForm
function CreateContactEditorForm as IContactEditor
end interface
If you like to cahce your forms, then you could chnage the interface a bit to match that use-case.
Next, in your VB6 EXE, you should implement the IFormFactory interface:
Class FormFactory Implements IFormFactory
public function IFormFactory_CreateLoginForm as ILoginForm
'let's say this form is still in VB6
Dim frm As frmLoginPage
Set frm = new frmLoginPage
Set IFormFactory_CreateLoginForm = frm
end function
public function IFormFactory_CreateContactEditorForm as IContactEditor
'let's say this form is in .NET
Dim frm As DotNetLib.ContactEditorDialog
Set frm = new DotNetLib.ContactEditorDialog
Set IFormFactory_CreateContactEditorForm = frm
end function
Throughout your VB6 app, have all form creation pass through this singleton:
Dim contactEditor as IContactEditor
Set contactEditor = modSingletons.FormFactory.CreateContactEditorForm()
contactEditor.FirstName = "Joe" 'seed with initial values
contactEditor.LastName = "Blow"
contactEditor.EmailAddress = "bubblegum#something.net"
Dim saved As Boolean
saved = contactEditor.DisplayModal()
if saved then
'read the new values back out and write to DB or whatever
end if
If you do this correctly, your main EXE should not even be aware if the forms are in .NET or VB6, you just switch them out as you go in the Factory.
Finally, you setup the same thing in the .NET lib. Create a COM exposed singleton that the VB6 exe can pass the IFormFactory instance into the .NET library. Then your .NET code can use the factory instance to invoke any form in your app.
Alternatively, you could pass the factory instance on every call into a form (to allow that form to access any other forms), but I would not do it that way. The reason for this is because there very likely are even more services aside from Form creation that you will want to start migrating over. You'd be better served with setting up a bunch of interfaces for your various application services and injecting all of them into the .NET library in a similar manner. Eventually everything will be in .NET, but your code will not need to change since it is using interfaces.
Related
I've got a bunch of Silverlight 5 applications hosted in a website that set some application specific data. One of these datum is a CurrentUser object.
On startup of the app (each one), it talks to a web service to pull in the current user information. However, this needs to be accessed from all sorts of places in the SL application.
I thought it made sense to put it on the Application object itself, but I wasn't sure if this was a good practice.
I put a simple property containing the user in an interface and implemented it on the main application class.
Then, the rest of the application code accesses it via a static class:
Public Module ApplicationUtils
Public Property CurrentUser() As ConnectEntities.WebUser
Get
Dim app As ICommonAppData = TryCast(Application.Current, ICommonAppData)
If (app IsNot Nothing) Then
Return app.CurrentUser
End If
Return Nothing
End Get
Set(value As ConnectEntities.WebUser)
Dim app As ICommonAppData = TryCast(Application.Current, ICommonAppData)
If (app IsNot Nothing) Then
app.CurrentUser = value
End If
Throw New NotImplementedException("Current application does not implement required interface")
End Set
End Property
End Module
I'm not terribly familiar with Silverlight, so I'm trying to find out if this is an accepted practice to access global data, or if there is a complication I'm not realizing here (thread safety issues, etc.).
I was unable to find anywhere that mentioned any details on needing elevated privileges to access the Application.Current property. As well, in all of my testing, it appears that I am able to access this property as desired without having to have elevated privileges (both local testing as well as with the app deployed on other machines).
I have moved ahead with my original design and everything seems to work fine.
Whether it is a best practice or not, I don't know. However, it seems a reasonably clean solution to accessing this type of data.
I've got two standalone applications:
First one:
Namespace FirstApplication
Class MainWindow
Public Sub New()
InitializeComponent()
End Sub
Public Function RunBatch(Parameter as String) as Double
'Do some work
Return SomeValue
End Function
End Class
End Namespace
Second application:
Namespace SecondApplication
Class MainWindow
Public Sub New()
InitializeComponent()
End Sub
Public Sub RunBatch()
'Call RunBatch() from first Application, get show the result
Msgbox(RunBatch)
End Function
End Class
End Namespace
Both are WPF, .Net 4.0 based. The goal is to have second application call on the first one and execute a function in it.
The key part is that both applications are used primarily independently and only occasionally second calls on the first. Because both applications need to exist as executable, I don't want to solve the problem by creating a dll of the first application - I would need to maintain both the executable and dll update to date with potentially disastrous consequences if they fall out of sync.
So the question is whether it's possible, to create an instance of first application within AppDomain of the second one and, crucially, execute functions of that instance.
I don't believe you can create one AppDomain inside of another.
Any other options you have (using WCF, or old style .NET Remoting, or crossing AppDomains) are all going to be more complicated than just making a single DLL that both applications can reference. So long as you don't change the assembly number of the shared DLL you won't have to recompile each exe if you make changes to the DLL (assuming you don't make breaking changes like changing method signatures).
Does FirstApplication have to do something to SecondApplication? Are you trying to control a feature of one application from another? If so you will need something like WCF (using Named Pipes or just a self-hosted web service). Or just trying to not have to write the same code twice? Then the simplest approach is probably to create a single DLL both applications reference.
Apparently, this can be done via reflection. The process is straightforward, though not nearly as convenient as using dll.
Public Class CalltoExternallApp
'this is the declaration of the external application you want to run within your application
Dim newAssembly As System.Reflection.Assembly = System.Reflection.Assembly.LoadFrom("Mydirectory\Myfile.exe")
Public Sub Main()
'Loads the main entry point of the application i.e. calls default constructor and assigns handle for it to MyApplication
Dim MyApplication = newAssembly.CreateInstance("MyApplication.RootClass")
'Get the type which will allow for calls to methods within application
Dim MyApplicationType as Type = newAssembly.GetType("MyApplication.RootClass")
'If calling a function, the call will return value as normal.
Dim Result As Object = LunaMain.InvokeMember("MyFunction", Reflection.BindingFlags.InvokeMethod, Nothing, MyApplication, MyParameters)
End Sub
End Class
Check also here for adding the event handlers to instances created via Reflection:
http://msdn.microsoft.com/en-us/library/ms228976.aspx
I am creating an XML web service that passes an array of custom types. In my consuming code I am referencing the code as a web reference which I have given the namespace MYWS. Now in code I am trying to assign the results of my web service call to an array of my type like so :
'instance to make a call to my web service
Dim srv As New MYDWS.ServiceNameWSSoapClient
'array to hold the results
Dim arr() As MyClass
'assign the web service call results
arr = srv.myWebMethod()
When I do this the complier complains, saying:
Value of 1 dimensional array of my.namespace.MyClass cannot be
converted to 1 dimensional array of my.namespace.MYWS.MyClass because
my.namespace.MYSW.MyClass is not derived from my.namespace.MyClass
Now I understand the message, the thing is they are the same class. The class is declared in my calling code by the web service references a dll from that project. How do I tell the compiler that these are the same type? Any help would be very much appreciated. Thanks!
The upshot is that you have a namespace mismatch. If you right-click on MyClass in your example and select Go To Definition, where does it take you? I suspect that you may end up in a locally defined class.
The solution is to change
Dim arr() As MyClass
to
Dim arr() As MYWS.MyClass
Update based on information in comments
The problem with using the web service is that you cannot cast it to a local class.
You have a couple of options depending on exactly what you need out of the local class.
If you only need methods to act on the data in the class or you need additional properties, you can create a partial class in your environment that extends the class created by the web service. For example:
Namespace MYWS
Public Partial Class MyClass
Public Property SomeAdditionalData As String
Public Sub SomeMethod
' Perform some operations on the class members
End Sub
End Class
End Namespace
However, if you have calculations or other work embedded in the class, then you will need to get the data using the web service class, then copy the data from that class into your local class. If the properties have the same names, you could ease this task using reflection.
As another option, if you have control over the web service, you could change it to a WCF service. This will allow you to reuse the exact same class code on both ends of the communication pipe.
Found a solution to the problem. In the web.config I found this:
<add key="net.mydom.mydom" value="http://localhost:7452/dir/mysvc.asmx"/>
which was what the system automatically entered when I registered the web service. I got the error messages on screen, but everything compiled and ran w/o problem.
When I manually changed to this:
<add key="net.mydom" value="http://localhost:7452/dir/mysvc.asmx"/>
The error messages went away and everything continued to function as expected.
(That only took my 7 years to figure out...)
UPDATE:
Well, not quite the fix, but it must be close. After awhile, the problem came back, when I switched back to to:
<add key="net.mydom.mydom" value="http://localhost:7452/dir/mysvc.asmx"/>
it went away again...sure to come back at any time...
UPDATE
If I explicitly add:
imports net.mydom
to the top of my code, the message goes away again (even though I was explicitly using the full net.mydom. when typing the variables.
I made a simple PasteBin demo example of what my code looks like: http://pastebin.com/GpDhPRVm
My actual Process object is extremely complex that includes adding collections of Tasks, Documents, Workflows, etc into a Process object and setting properties through methods, etc.
My PasteBin example is as simple as I can make it to show where things break down. Including the Process object in the CreateNewProcess method in my service (shown in code below), allows the service user to automatically "see" the Process object, properties and enumerators in their instance on the client side.
<ServiceContract()>
Public Interface ICreateProcess
<OperationContract()>
Sub CreateNewProcess(ByVal newprocess As Process)
End Interface
However, it does not allow them to use any of the methods like the 'AddTask' method (shown in PasteBin example) and it also doesn't expose the Task or TaskCollection objects. Even if I decorate the methods with DataContract, OperationContract, etc they still are not visible to the client service.
This is my major issue: I need the client service to be able to fully "build" the Process object and then pass it in to the CreateNewProcess method.
Another small note: (to avoid comments asking me about it) is that I made sure that all of my properties are simple types that are interoperable since this needs to be able to work for Java - not just .NET clients.
Ok, I figured it out with a little help from a buddy of mine.
Can't use methods at all outside the .svc service class; only properties, enums and sub objects will translate down to the client. (I figured this)
Then, instead of using a Collection, you have to use a generic List type.
So, instead of this:
Public Tasks As New TaskCollection() 'where TaskCollection inherits from Collection
I needed to do this:
Public Tasks As List(Of Task)
And the client will just have to build their own array of Tasks and assign it to the t.Tasks property.
This works like a charm now: http://pastebin.com/rt8HwsXY
I'm making some archeology, dealing with COM+
I managed to enlist a simple COM dll as a COM+ component, so far so good.
So I've got this 'foobar' com+ component, with it's interface, and the method I'd like to call.
My question is then rally simple: how am I supposed to make a call to this component?
Any .NET or VB6 answer is accepted (I have to check the component is OK, don't care about the client)
Thanks
Edit (06/03/09): Well, I'm confused. so as to work properly, my COM+ component needs it's dll to be registered. Why not? But then, how to be sure I'm making a COM+ call, and not a COM one?
Simplest VB.NET code snippet possible:
Dim myCom As Object
myCom = CreateObject("MyCom.ProgId")
myCom.Method(parms)
You need to replace "MyCom.ProgId" with the actual ProgId of your component - you can get this from the General tab of the properties of the component in the Component Services admin tool (sounds like you've already got a grasp of that)
myCom.Method(parms)
is simply a place holder for whatever method you want to invoke, with the parameters that method takes.
Here's a link to some examples of the VB.NET syntax:
http://msdn.microsoft.com/library/de...eateObject.asp
http://www.samspublishing.com/articl...le.asp?p=25857
http://msdn.microsoft.com/library/en...asp?frame=true
If all you are wanting is to check the component responds when called then use a quick VBScript rather than building something in VB6/VB.NET.
Dim o : Set o = CreateObject("Lib.Class")
o.YourMethod "someParam"
Watch your COM+ app in Component Services to see if the class requested spins up.
Adam's code in VB6 is similar:
Dim myCom As Object
Set myCom = CreateObject("MyCom.ProgId")
myCom.Method(parms
This example is late bound and carries with it some performance penalty.
You could call your method in an early bound manner, which avoids the penalty. In either VB6 or VB.NET, just add the COM+ dll to your references and you can call the object in this manner:
VB6
dim myCom as MyCom.ProgId
set myCom = new MyCom.ProgId
myCom.Method
VB.NET
dim myCom as new MyCom.ProgId
myCom.Method(...)
When you want to use COM+ for RMI then use this
Dim o As Object
Set o = CreateObject("Lib.Class", "MYSERVER")
where MYSERVER is the machine name where COM+ application is created and your DLL registered. Subsequently
o.YourMethod "someParam"
will be invoked remotely. If you are using only automation compatible interfaces, COM+ will successfully create a proxy for the RMI. Otherwise you'll need to provide the typelib on the client machine. This can be a separate TLB or the DLL itself.