Wrapping VB Application project with COM project - c#

I'm trying to establish the feasibility of using our company's product with COM. To do this, I've written a (MyAppCom) dll within the solution and referenced the original (MyApp)exe project. MyAppCom creates and instance of the MyApp (mainform).
The main problem I am coming up against is that the application entry point is an application type (with settings, startup forms, etc.). Generally, this isn't so bad, but there are a couple issues that I'm banging my head against.
The app config uses the calling exes name to search for the application configuration file (i.e. if I'm calling a com instance of MyApp via Python, its looking for python.exe.config instead of MyApp.exe.config. I've sidestepped this issue for the moment by just copying the settings file, but if anybody knows how to reference this specific config settings, any help would be appreciated.
The second point is a bit more sticky. It seems that MyApp has set mainform as its startup form, which seems to create a global instance of it. I've looked everywhere and there's no explicit initializations of mainform except through MyAppCom. This becomes a problem when I initialize mainform from MyAppCom and child forms are trying to reference the global MyApp.mainform. In this case, it is obviously not initialized (since I didn't create it from MyApp).
Is this something other people have done? I've searched for a couple days now with no luck but I don't imagine I'm one of few who've tried. A little background on the product - it was originally written in VB6 and now is updated to VB.NET with new projects in C#. The wrapper is also written in C#.
Here is the com wrapper (MyAppCom) (names have been changed to protect the not-so-innocent):
public interface IMainCom
{
void Init();
}
[ClassInterface(ClassInterfaceType.None)]
public class MainCom : IMainCom
{
private MDIMain mMDIMain = null;
public void Init()
{
OpenMain();
}
private void OpenMain()
{
mMDIMain = new MDIMain();
mMDIMain.Show();
}
}
Here also is the App.Designer for MyApp:
Namespace My
Partial Friend Class MyApplication
<Global.System.Diagnostics.DebuggerStepThroughAttribute()> _
Public Sub New()
MyBase.New(Global.Microsoft.VisualBasic.ApplicationServices.AuthenticationMode.Windows)
Me.IsSingleInstance = false
Me.EnableVisualStyles = false
Me.SaveMySettingsOnExit = true
Me.ShutDownStyle = Global.Microsoft.VisualBasic.ApplicationServices.ShutdownMode.AfterMainFormCloses
End Sub
<Global.System.Diagnostics.DebuggerStepThroughAttribute()> _
Protected Overrides Sub OnCreateMainForm()
Me.MainForm = Global.MyApp.MDIMain
End Sub
End Class
End Namespace

Related

How can I fix Run-time error 430 from importing custom DLL in VBA project

I've struggle several hours on that and I can't find what I'm doing wrong.
I created a new C# dll project, here is the content of the only class it contain:
using System;
using System.Runtime.InteropServices;
namespace PolygonSl {
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class Config {
[ComVisible(true)]
public string GetCompany() {
return "POL";
}
}
}
I basically remove everything from it trying to make it work, the only reference is System.
I checked the Make assembly COM-Visible flag on the Assembly Information and my project is signed (seams required for codebase).
It compiling fine, after that, I called RegAsm.exe, giving it my dll, I added /codebase and /tlb, the command is successful.
When I go to my VBA project, I can add my new tlb file to the references, working fine. After, I can use it in my code, the autocomplete is working and I can compile with no errors.
Then, when I execute, I got this:
Run-time error '430':
Class does not support Automation or does not support expected interface
Here is my code sample in the VBA:
Private Sub Button1_Click()
'With CreateObject("PolygonSl.Config")
With New PolygonSl.Config
MessBox .GetCompany, MB_OK, "Test"
End With
End Sub
I tried late binding and my code is running fine with it but I'd like to be able to use the autocomplete.
Anyone have a suggestion on what I could try to make it work?
Edit (Adding some details on my environment)
I work on VS2008 for projects related to Dynamics SL (one of the Microsoft ERPs)
I'm on Windows Server 2008 R8 Standard, running from VMWare
Compiling on Framework 3.5, Release, x86, Dynamics SL client is 32 bits
I tried my dll on Dynamics but also on Excel to be sure that the problem was not Dynamics ;)
I think you need to define an interface to be able to see getcompany.
using System;
using System.Runtime.InteropServices;
namespace PolygonSl
{
[Guid("6DC1808F-81BA-4DE0-9F7C-42EA11621B7E")]
[System.Runtime.InteropServices.ComVisible(true)]
[System.Runtime.InteropServices.InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IConfig
{
string GetCompany();
}
[Guid("434C844C-9FA2-4EC6-AB75-45D3013D75BE")]
[System.Runtime.InteropServices.ComVisible(true)]
[System.Runtime.InteropServices.ClassInterface(ClassInterfaceType.None)]
public class Config : IConfig
{
public string GetCompany()
{
return "POL";
}
}
}
You can generate the interface automatically by placing the cursor in the class definition and using Edit.Refactor.ExtractInterface.
I'd have to admit that I'm at the absolute edge of my abilities here and the above is put together based on examples I've seen elsewhere.
Edit
The following test code works fine on my PC
Option Explicit
Sub polygontest()
Dim my_polygon As SOPolygon.Config
Set my_polygon = New SOPolygon.Config
Debug.Print my_polygon.GetCompany
End Sub
Where SOPolygon is the project name.

sharing a static class with a DLL in C# without passing a reference

VS2012 for desktop .net framework 4.5 normal windows forms applications, not WPF
Hello, I tried to search for an answer, but I'm not sure of the correct terminology. I've managed to break my code, and can't understand what I've done wrong. (i didn't think i had changed anything, but ...)
I have a solution which contains 2 projects. The first project is an executable program, and the second is a DLL, which is loaded at run time and used by the first project.
the first project contains a form, and a static class with public static strings in the same namespace. (and some other unconnected classes). specifically:
namespace project1_namespace
{
static class settings
{
public static string some_words = "some words in a string";
}
class dll_callback{
//.. some public methods here
}
dll_callback dllcallback; // instance is initialised in the code (not shown)
Form form;
public partial class frm_splash : Form
{
private void frm_splash_FormClosing(object sender, FormClosingEventArgs e)
{
// this function actually loads the DLL, ensuring its the last step
//... some error checking code removed for brevity
Assembly assembly = Assembly.LoadFrom("c:\dllpath\project2.dll");
Type type_init = assembly.GetType("project2_class");
object init = Activator.CreateInstance(type_init, form, dllcallback);
//... some error checking code removed for brevity
}// end method
}// end form class
}// end namespace
when the form is closing, the method shown above is called which calls the second projects class project2_class constructor.
in project 2, the DLL, there is:
namespace project2_namespace
{
// how did i get this working to reference "settings" class from project 1??
public class project2_class
{
public project2_class(project2_namespace.Form1 form_ref, object callback)
{
settings.some_words = "the words have changed";
//... some more stuff
}
}
}
Now, i was experimenting with some code in an entirely different part of project2, and VS2012 suddenly started refusing to compile stating:
error CS0103: The name 'settings' does not exist in the current context
the standard solution to this appears to be to add a reference to project2, but that would create circular dependencies because project 1 calls 2 as a DLL.
I really honestly don't think i had changed anything relevant to this, but also clearly I have.
looking at it, i cant see how project 2 would have access to a class in project 1 without a reference, but the list of arguments to the project2_class constructor doesn't include one, and I am absolutely positive that it hasn't changed (and I cant change it for backwards compatibility reasons).
would really appreciate help with this, as its been a lot of work to get this working.
as a side note, I've definitely learned my lesson about not using source control. and not making "how this works" comments instead of "what this does" comments.
may dynamic help you? You can not get the setting string at complie time.

Application.Current <- how does it work?

I am going through some WPF example I found.
I have a class here which is inherited from Application:
public partial class DataBindingLabApp : Application
{
private ObservableCollection<AuctionItem> auctionItems = new ObservableCollection<AuctionItem>();
public ObservableCollection<AuctionItem> AuctionItems
{
get { return this.auctionItems; }
set { this.auctionItems = value; }
}
}
As you can see this class have a property called AuctionItems.
Because it inherits from Application it also contains property called 'Current' which provides access to the Application instance (according to MSDN).
Then in the code I have:
((DataBindingLabApp)Application.Current).AuctionItems.Add(item);
I do not understand it.
Since we can have many classes which may inherit from Application then how we know that Application.Current actually contains object of class 'DataBindingLabApp'?
Thank you!
Because Visual Studio generates entry point in the partial generated class of custom application type(DataBindingLabApp in your case) by default (You can find it by searching in the root directory of solution).
[System.STAThreadAttribute()]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
public static void Main() {
DataBindingLabApp app = new DataBindingLabApp();
app.InitializeComponent();
app.Run();
}
And after application has been ran Application.Current contains instanse of DataBindingLabApp.
Since we can have many classes which may inherit from Application
That isn't relevant. What matters is that there is only ever one instance of the Application class. The one-and-only application that's running. Be sure to distinguish types from objects.

Working With 2 Independant projects c# and VB

I have 2 completely independent projects one in vb and one in c#
Project A - VB
Project B - C#
We have scenario where by code in project A is currently using code in project B
e.g
Project A
Public Class SMS
Public Sub New()
End Sub
Public Function SendSMS() As String
Return "SMS Sent "
End Function
End Class
Project B
using ProjectA;
public class Email
{
public string SendEmail()
{
return "Email Sent ";
}
public string SendEmail(SMS sms)
{
sms.SendSMS();
return "Email Sent ";
}
}
This works fine However we now have a requirement to Send an Email from Project A which would create a circular reference After reading loads of help on here I created an interface and used the interface in project a as such:
Project C
Public Class Interface
Public Interface IEmail
Sub SendEmail()
End Interface
End Class
I have implemented the interface on Email and changed Project A to :
Public Function SendSMS(ByVal tEmail As IEmail) As String
tEmail.SendEmail()
Return "SMS Sent "
End Function
Which works fine from my ui as I can instantiate the object and all works fine.
However I have run into an issue where by I don’t want to instantiate the bject in the UI but instead in the Project A
Public Sub SendSMSAndEMail()
Dim temail As New email
Me.SendSMS(temail)
End Sub
I have hit a real road block? i think i should be looking at a factory class but i am getting confused as to how best to achieve this?
N.B Further info:
If we were to Design the system again project A and Project B would be in the same project (and Language) and this would not cause an issue. As it is we have 2 UI's A Console server app and a Win forms UI. Project B was originally for the console app and uses various functions that the main Win form app uses. It’s now arisen that there is code in the console app that we require from within the main app such as the ability to Send An E-mail however the code would be difficult to extract from the console app as it’s in a different language to the main application.
Evil solution: Static Factory Function
Declare this static class in your root project (example in interface project).
public static class EmailFactory{
public static Func<IEmail> Create{ get;set; }
}
Then in your UI (I assume that your Project will end up starting in UI), set it like:
EmailFactory.Create = new Func<IEmail>( () => { return new ProjectB.Email() } );
This way you can use it statically from Project A (even you still need to execute it from UI).
But forget it, you won't need it. Or at least, don't use it.
If you said that
The Current Sending process of SMS messages is Handled within Project A.
Does it means that Project A can be executed directly? I hope not. A library being executed is kind of evil for me. If you can, make the execution in separate project, and that project can reference to Project A and B as well. If so, then your interface approach is already fulfill the requirement.
EDIT:
Looks like the Project A or B itself is at UI level. It is a bad design to has library code inside UI project. However it will take months to refactor the whole application.
What I can suggest is to create a layer of library, which need to be used interactively between the project. So the architecture shall be:
infrastructure
|
|
library
/ \
/ \
[Project A] [Project B]
IMHO this is the only way to fix your problem.

Using a VB class from the same ASP.NET web site project in a C# class

I have an ASP.NET web site project where I am using both VB.Net and C# class files. I have included separate sub folders in the App_Code directory for classes of each language.
However, while I can successfully make use of a C# class in a VB class, I cannot do the opposite: use a a VB class in a C# class.
So, to illustrate, I might have two classes such as this:
Public Class VBTestClass
Public Sub New()
End Sub
Public Function HelloWorld(ByVal Name As String) As String
Return Name
End Function
End Class
public class CSTestClass
{
public CSTestClass()
{
}
public string HelloWorld(string Name)
{
return Name;
}
}
I can make use of the CS class in my VB class, with the "Imports" statement. So this works well:
Imports CSTestClass
Public Class VBTestClass
Public Sub New()
End Sub
Public Function HelloWorld(ByVal Name As String) As String
Return Name
End Function
Private Sub test()
Dim CS As New CSTestClass
CS.HelloWorld("MyName")
End Sub
End Class
But making use of the VB class in my C#, with the "using" statement, does not work:
using VBTestClass;
public class CSTestClass
{
public CSTestClass()
{
}
public string HelloWorld(string Name)
{
return Name;
}
}
I get an error that "the type or namespace "VBTestClass" could not be found". What am I missing here?
The best way to look at using/Imports as a shortcut to skip fully qualifying namespaces. The behaviour is the same across vb and c#.
Consider the examples:
fully qualyfying:
void DoSomething()
{
var p = new Interfaces.CPDATA.DataHolders.Placement();
}
skip the namespaces:
using Interfaces.CPDATA.DataHolders;
void DoSomething()
{
var p = new Placement();
var t = new Trade();
}
and a little shortcut trick
using data = Interfaces.CPDATA.DataHolders;
void DoSomething()
{
var p = new data.Placement();
var t = new data.Trade();
}
and a replacement trick:
using t = Interfaces.CPDATA.DataHolders.Placement;
void DoSomething()
{
var p = new t(); // happy debagging
}
As for code files in different languages in ASP.NET App_Code folder: DO NOT USE IT. For:
they won't work when using Web Application project
they will not compile when using csc or vbc compiler in continuous integration project outside of Visual Studio
and they will generally give you a lot of pain on infrastructure side of things.
Best way is to create separate Class Library projects for respective language and use them.
On top of it there are a lot of interesting things going on when running such project from under visual studio and iis. If you're curious you can take a look at various files sitting in
\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\{project name}\{tempname}
it should give you a good idea how asp.net engine combines the code files for aspx pages.
Aimed with that useless information we can now tell that having a CSTestClass class in the same namespace statement "Imports CSTestClass" is not really useful. Good coding style would be to have all of them wrapped in a namespaces statements MyWebProject.VbCode and MyWebProject.CsCode for example. Then statements "using MyWebProject.VbCode" and "Imports MyWebProject.CsCode" would make more sense to the compiler.
I think I found the problem and seems without a reflection you can't do it as a cross reference.
The reason is pretty simple, depends how the orders you define your codeSubDirectories, I think you made it this way:
<codeSubDirectories>
<add directoryName="CSCode"/>
<add directoryName="VBCode"/>
</codeSubDirectories>
As we know each directory will be build to different assembly, and they will be build one by one from top to bottom based on your settings.
So as you have CSCode folder defined first, it will be built first, and then compiler start to build VBCode, so using the CS class is OK as it can find the assembly to reference.
But if you do it reversely, as you mentioned to reference VB code in CS, it firstly build CSCode folder and at that time the assembly of VBCode does not exist so it throw exceptions.
So for make it work with CS using VB, just simply change the folder setting order:
<codeSubDirectories>
<add directoryName="VBCode"/>
<add directoryName="CSCode"/>
</codeSubDirectories>
But then you will lose the ability to use any CS class in VB as this time VBCode compile first.
So my suggestion is go with reflection to load it at run time so that compiler can let you go.
Hope my explanation is clear enough.
Thanks
the using statement is for namespaces not class names, put the VBClass inside a namespace and then, use the "using" statement:
Namespace MyFoo
Public Class VBTestClass
Public Sub New()
End Sub
Public Function HelloWorld(ByVal Name As String) As String
Return Name
End Function
End Class
End Namespace
now in c#:
using MyFoo;
...
THe difference is in how the Imports keyword works compared to the using keyword.
The using keyword can only be used to specify namespaces, while the Imports keyword can also be used to specify classes.
So, Imports CSTestClass specifies that classes, interfaces and enums inside that class should be available, but the class doesn't contain any of those, so the Imports statement is not needed.
When you try to use using VBTestClass it won't work, as VBTestClass is not a namespace.
So, just remove the Imports and using statements, and it should work fine. As the classes are in the same assembly, they already know about each other.

Categories

Resources