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
Related
I have three projects, let's call them Client, Service and Execute.
Both Client and Service references Execute.
Client is a windows forms application. While first loading, it calls a shared method in a class called ParametersManager which fills four parameters in a class called Parameters.
Public Class Parameters
Public Shared Property FirstProperty As Integer
Public Shared Property SecondProperty As Integer
End Class
Public Class ParametersManager
Public Shared Sub FillParameters()
'In real scenario, I am just sending the parameters as arguments to the method.
Parameters.FirstProperty = 1
Parameters.SecondProperty = 2
End Sub
End Class
So when the application starts, it will call FillParameters from Execute.
I did debug the application, and when this method finish executing, the properties has the exact values that I gave them.
In one of the forms of the Client application, I am calling the Service which is an asmx Web Service. Service now calls a method from Execute project, let's call it UsePropertiesDoingSomething.
Public Class UseProperties
Public Shared Function UsePropertiesDoingSomething() as Integer
Return Parameters.FirstProperty + Parameters.SecondProperty
End Function
End Class
My problem is that when I call this function from the service, the properties are both equal to 0, not the values I have previously filled when the Client application first run.
How did I lose the the values? Shouldn't the values be saved in the Execute scope? How can I make sure not to lose them?
For testing purposes, I filled the parameters at the start of the Service, now when the Service calls the UseProperties the values are kept, but when called from the Client, the values are lost again.
For this purpose you have explore AppDomain and Execution context.
Shared does not means that it is shared between application or project. It is shared for specific application domain.
If same application domain load same library then it will be shared otherwise not as other application domain has it's own copy. (At least in .net it will not cross AppDomain boundry).
In your case Window Form has different Application Domain and ASMX in Web Service so it has different Application Domain so it will not be shared.
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.
I have three web services, which were developed by three different vendors and have different URLs; input parameters and output parameters. They are all ASMX web services. They are used to delete records from third party relational databases e.g. I supply a personID and a person is deleted from one system and everything linked to the person. In another system I supply an order ID and everything linked to the order is deleted.
I have several options:
Create a single wrapper class, which is responsible for accessing the web services; supplying common input parameters and accepting common output parameters. This class would have lots of responsibilities.
Create three wrapper classes i.e. one for each web service
Modify the proxies generated by Visual Studio
Which way is best?
I would recommend allowing Visual Studio to automatically generate the appropriate proxy classes. I would then implement a wrapper class for each web service so that all of the wrapper classes could implement the same interface. For instance, you may make a common interface that looks like this:
Public Interface IPersonBusiness
Sub DeletePerson(personId As String)
End Interface
Then, lets say you had two web services. The first, we'll call it WebService1, has a Delete method which takes a person ID followed by the deletion time. The second web service, we'll call it WebService2, has a DeletePeople method which takes an array of person ID's. We could wrap both of these web services using the above common interface, like this:
Public Class WebService1Wrapper
Implements IPersonBusiness
Public Sub New(proxy As WebService1)
_proxy = proxy
End Sub
Private _proxy As WebService1
Public Sub DeletePerson(personId As String) Implements IPersonBusiness.DeletePerson
_proxy.Delete(personId, Date.Now)
End Sub
End Class
Public Class WebService2Wrapper
Implements IPersonBusiness
Public Sub New(proxy As WebService2)
_proxy = proxy
End Sub
Private _proxy As WebService2
Public Sub DeletePerson(personId As String) Implements IPersonBusiness.DeletePerson
_proxy.DeletePeople({personId})
End Sub
End Classs
I would avoid writing your own proxy code unless you really need to. For instance, if you needed to dynamically call any web service based on some external settings which tell you the method name and parameters to pass, or something like that, then it would be worth looking into.
I would also avoid putting all of the logic to call any of the web services into a single wrapper class. Doing so will make the code unnecessarily ugly and confusing, especially if you need to add additional web services in the future.
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 must be losing my mind...
After getting a test WCF hosted in a windows service, I'm trying for another one (practice, practice, practice).
I created a WCF service library, added one function. Then created a Windows Service, and added my WCF to the project. Did the rest of the stuff located here (http://joefreeman.co.uk/blog/2010/03/creating-a-setup-project-for-a-windows-wcf-service-with-visual-studio/)
Now I'm getting this "Sub Mian was not found in [WCF app]" error when I try to build the solution.
I didn't think WCF projects required a Sub Main as they are services and not applications. What am I doing wrong? I didn't have a sub main in my last project. Any ideas?
For others who are looking for the same answer I was...
Created a service in VB.NET (VS2010), and was getting the same error listed above...
'Sub Main' was not found in 'SERVICENAME.Service1'.
In VB.NET, VS2010 created a "Service1.Designer.vb" file for me automatically. Inside of that file was:
Shared Sub Main()
Dim ServicesToRun() As System.ServiceProcess.ServiceBase
' More than one NT Service may run within the same process. To add
' another service to this process, change the following line to
' create a second service object. For example,
'
' ServicesToRun = New System.ServiceProcess.ServiceBase () {New Service1, New MySecondUserService}
'
ServicesToRun = New System.ServiceProcess.ServiceBase() {New SERVICENAME}
System.ServiceProcess.ServiceBase.Run(ServicesToRun)
End Sub
To resolve this, right-click your solution name in the solution explorer, go to the "Application" tab, and pick "Sub Main" as your Startup object in the "Startup object" drop-down box. Recompile.
Granted, the file names and structure will be different in C#, but the idea is that Visual Studio put the Main procedure SOMEWHERE, and it hopefully should already know where it is.
The WCF service itself doesn't need a Sub Main, but your Windows Service does.
Compare against your previously built, working Windows Service.
edit: using C#, when I create a new project using the Windows Service template, I get the following initial code in a Program.cs file (static void Main is your Sub Main). I'd be very surprised if you don't also have this in your Windows Service.
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main()
Check you haven't accidentally changed the Application type to something stupid like "windows forms app", hit the properties of the project and make sure it's on "class library".