I have the COM idl definition file, and i have a VB project that demonstrate how to communicate thru an application thru COM.
unfortunately, i cannot straight up port the VB code to C#.
The COM is written in C++ and is reference by its program id
How can I instantiate the COM from C# and call functions from there?
here is the vb code
Dim DrCephPatient As Object
SampleCom = CreateObject("Sample.SampleCOM")
SampleCom.Attribute1= "Hello"
SampleCom.Method1(1)
Also, since i have the IDL definition, i have the UUID i can use to access.
Any resources I need to read to make this work?
You just need to add a COM reference to your C# project.
The COM tab of the Add Reference dialog window lists all COM components that are available for referencing. If you want to add a reference to a registered COM DLL that contains an internal manifest, unregister the DLL first. Otherwise, Visual Studio adds the assembly reference as an ActiveX control instead of as a native DLL.
If a project type doesn't support COM, the tab doesn't appear in the Reference Manager dialog box. Read more about that in the How to: Add or remove references by using the Reference Manager article.
After adding a COM reference you can use your component as a regular .net classes (using the interop library generated automatically behind the scene).
Using C#, you can quasi get it to perform like VB script code by using Type.GetTypeFromProgID(), the Activator class, and using the dynamic type. I wouldn't necessarily prefer this, though. It's better to add the COM reference to your project. In my example I'm using, Word which runs out of process (is an .exe) so it will work with 32-bit or 64-bit versions of the runtime. However, for an in process server (.dll), the bitness of your code and the server need to match (either both 32- or 64-bit). However, for a quick test, this sometimes will do the job.
using System;
class Test {
public static void Main(string[] args) {
Type t = Type.GetTypeFromProgID("Word.Application");
dynamic word = Activator.CreateInstance(t);
Console.WriteLine("Word title:" + word.Name);
word.Quit(); // otherwise hanging reference ... is Word API specific
}
}
You can use the SerialPort Class
https://learn.microsoft.com/en-us/dotnet/api/system.io.ports.serialport?view=dotnet-plat-ext-5.0
Basically works with Open, Write/Read and Close functions but it's in C#, I guess it's not exactly what you look for
Also there is this list of articles about VB that could help you
https://learn.microsoft.com/en-us/dotnet/visual-basic/developing-apps/programming/computer-resources/accessing-the-computer-s-ports
Related
I have a C# DLL that is being used as a class to contain a list of properties about a file.
The C# DLL is COM visible, and is able to be declared and instantiated in VB6.
Inside my vb6 function where I create the object..
Dim fileObj As New MyCSharpClass.FileProperties
I am immediately able to see all the different properties accessible to my C# FileProperties object.
fileObj.(intellisense) shows me the dropdown list of anything available inside the object
But when I pass my object to a function..
GetProperties(fileObj)
When I am inside GetProperties
Public Function GetProperties(ByRef pfileObj As MyCSharpClass.FileProperties)
When I try to have intellisense show me what options are available to me..
it does now recognize pfileObj as a variable I can use, it will not show up in intellisense
If I try to manually type it out, once again intellisense will not show me any options.
pfileObj.
Is there a special way to pass COM interropt objects around to functions they were not delcared in inside of VB6?
Is this simply not possible?
I was trying to avoid creating functions that return strings, and then assign to the object properties one at a time.
The reason is because you need to have .NET create a TypeLib file which you can then reference from VB6. VB6 will use this to display intellisense as well as assist VB6 in how to invoke your C# object. You need to do the following items:
Declare an interface in C# which will publicly expose the properties/methods that you want VB6 to access.
Implement the interface in your C# object using the colon character after your class name.
Create a strong name for your project using the Strong name command line utility. (You can do this in the project properties in Visual Studio .NET).
Use the command line utility regasm.exe to create the necessary TypeLib file.
Use gacutil to add your newly created tlb file to the GAC.
Create a reference to the newly create .tlb file from VB6 (Project > References).
You can find the detailed instructions here: http://www.codeproject.com/Articles/3511/Exposing-NET-Components-to-COM. The article may be old but for the most part it is still applicable to the modern versions of Visual Studio.
I need a list of the methods and properties exposed by a COM object. I am able to late bind the object and use some basic methods that I found in an example code snippet but I need to find out the full list of methods and properties on the object.
More info:
I am trying to pull data from a fairly old system and am unable to early bind any of the dlls.
I do have some snippets of example code from the company that creates these dlls (it was packaged with the software), however it does not have code for all of the advanced functionality I am seeing from their example application (which I do not have source code to).
I have already tried using reflection without much success.
Assuming that you don't need to programatically access the information, the easiest way is to the OLE-COM object viewer. The tool is part of the Windows SDK.
Once running, look in the Type Library node and locate the type library of the component that you are using. The Type Library viewer will show you all of the dispatch interfaces defined by the component.
If you can't add the DLL to VB6 in the References dialogue, then there is no Type Library resource embedded in the DLL. If you can load the DLL, you should be able to use F2 to bring up Object Browser, and see all of the properties and methods of the component.
It seems unlikely that you hadn't tried this. So there are three possibilities:
There is an external type library for the component.
You got an error when adding the DLL as a reference, which essentially said the VB IDE couldn't find a type library in the component.
You got an error when adding the DLL as a reference, saying something like "Could not load DLL".
Just in case of 1. - check if there is a TLB or OLB file for this component.
If you got the error in 2. - then you are out of luck. You will require access to documentation and/source code.
If you got the error in 3. - then there is probably a dependent library which has not been registered. Try to find the dependent TLB or DLL, and register it.
At the request of my boss I created a small set of scripts that are used to periodically monitor the status of certain devices and processes. This info is subsequently processed using a relatively elaborate VBA module that gathers all info, applies formulas, sets up ranges and generates graphs, etc.
There are two problems however:
I'm an amateur programmer, so my VBA routine is quite inefficient. That's not a huge problem for me (I just get up and get a coffee while it's running), but for other users this can be a hassle, as then don't know why it's taking so long. I want a graphical representation of progress.
The configuration of the application is done through a text file. I want to provide a decent GUI for setting up the configuration.
In order to achieve this, I'm looking for a way to let my C# WinForms application communicate with my VBA application. I know how to run a VBA routine from my C# app, but I don't know how I can let them comminicate in real-time.
Here are the two things I specifically want to achieve:
I already have a log file that's saved at the end of the VBA routine.
Instead of that however I want to send the log/debugging-messages to
my C# application in real-time (not just at the end of the
application) so the messages can be displayed in my GUI app as they
are generated by the VBA app.
I also want the VBA app to send info
about real-time progress to my GUI app so I can create a graphical
progress-bar in my GUI app.
I've already thought about communicating through the Standard Output. I know how to read from the Standard Output using C#/.Net, but I'm not sure how I would write to the StdOut stream using VBA.
I'm sure many would point out that what I'm trying to achieve is stupid and old-fashioned (or totally unnecessary), but as an amateur programmer it seemed like a really challenging and fun project for me that could teach me a thing or two.
Creating a C# COM-visible class is pretty easy, and (as you said in your comments) it is fun. Here's a small sample.
In a new C# Library project, add:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace CSharpCom
{
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
//The 3 GUIDs in this file need to be unique for your COM object.
//Generate new ones using Tools->Create GUID in VS2010
[Guid("18C66A75-5CA4-4555-991D-7115DB857F7A")]
public interface ICSharpCom
{
string Format(string FormatString, [Optional]object arg0, [Optional]object arg1, [Optional]object arg2, [Optional]object arg3);
void ShowMessageBox(string SomeText);
}
//TODO: Change me!
[Guid("5D338F6F-A028-41CA-9054-18752D14B1BB")] //Change this
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
interface ICSharpComEvents
{
//Add event definitions here. Add [DispId(1..n)] attributes
//before each event declaration.
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(ICSharpComEvents))]
//TODO: Change me!
[Guid("C17C5EAD-AA14-464E-AD32-E9521AC17134")]
public sealed class CSharpCom : ICSharpCom
{
public string Format(string FormatString, [Optional]object arg0, [Optional]object arg1, [Optional]object arg2, [Optional]object arg3)
{
return string.Format(FormatString, arg0, arg1, arg2, arg3);
}
public void ShowMessageBox(string SomeText)
{
MessageBox.Show(SomeText);
}
}
}
You will want to go into your project properties, to the "Signing" tab, check the box to sign your assembly, and create a new "strong name key file". This will help to prevent versioning issues with your registered DLL.
Compile it, and register the DLL using regasm in a Visual Studio command prompt. You will use either 32 or 64-bit regasm depending on what version of Office you are using... you will find RegAsm in C:\windows\Microsoft.NET\Framework or C:\windows\Microsoft.NET\Framework64 (32 and 64-bit, respectively):
C:\windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe /codebase /tlb CSharpCom.dll
This will register your DLL with Windows, so now in the VBA editor, you should be able to go to Tools->References and find your COM's namespace "CSharpCom". Check that box, and now you should be able to create your COM objects in VBA:
Sub TestCom()
Dim c As CSharpCom.CSharpCom
Set c = New CSharpCom.CSharpCom
c.ShowMessageBox c.Format("{0} {1}!", "Hello", "World")
End Sub
You can add forms to your COM object, and open them when the VBA calls a particular method; you should be able to use this to create a form with a progress bar. One thing to consider, though, is that VBA is single-threaded. This means that everything else gets frozen while your code is running, so things might get a little tricky.
Off the top of my head, you could create 3 methods in your COM-visible class: one to open the form, one to update progress (call VBA's DoEvents() method right after your VBA calls the update() method on your COM object, to allow Office to process screen updates), and one to close it. You should call Dispose() on the form; which could be done in the "close" method, but I think it could potentially cause memory leaks/problems if your VBA crashes, and your close method is never called -- just something else to consider.
You can use a .NET DLL with your VB6 project and create an instance of any object you have defined in your assembly. This is done by registering the .NET assembly (.dll) for COM interop (use in VB6) by creating a type library file (.tlb). The .tlb file contain extra information so that your VB6 project can use your .dll. Note, your VB6 project will not reference the .dll but the corresponding .tlb file. You can use the Regasm.exe utility to generate and register a type library and register the location of the managed assembly. Then its just a matter of creating an instance of your .NET object, whether it be a WinForm or some other cool class. =).
Dim MyDotNetObject As MyDotNetClass
Set MyDotNetObject = New MyDotNetClass
MyDotNetObject.SomeMethod(value1, value2)
''end of execution
Set MyDotNetObject = Nothing
See: http://support.microsoft.com/default.aspx?scid=kb;en-us;817248
I believe the code in this link does roughly what you want:
C# code creates a COM object (in the example it is IE, but from your description, you have already managed to create an instance of your VBA routine)
It attaches a .Net event handler (using +=) to an event raised by the COM object (when the title changes)
It defines sample event handling code
http://msdn.microsoft.com/en-us/library/66ahbe6y.aspx
I don't profess to understand this code, my objective is to show convoluted this solution would be! You should really build this in just one solution. A total VBA will solution will be quicker to develop (you only need to learn VBA forms which is easy), but old technology. A total C# solution will be slower to develop but then you will get to learn C#.
Performance actually is an issue. If it performs inefficiently now, what happens when you have 5x as many records to process? You should solve the performance issue before you get that many records.
The other answers to this question involve the VBA macro calling an external method. But there is an easier way. Your GUI app can attach to the workbook SheetSelectionChange event or sheet Change event, which will be triggered when any value changes.
Because you probably don't want to slow down everything, I would recommend adding an additional sheet, add named ranges to describe what each value means, then hook the sheet change event to intercept when these values are changed and update your GUI.
In an earlier post Passing pointer from managed C++/CLI to ActiveX C++ component I've asked about the correct means to pass an array (whether managed or unmanaged array) to an activeX component created in native C++.
The activeX method has the following signature:
short Component::CopyToBuffer(short FAR* ptr) {}
when the activeX is imported to be used in C++/CLI
the method signature is displayed as
short Component::CopyToBuffer(short% ptr) {}
when imported in C# it is displayed as
short Component::CopyToBuffer(ref short ptr) {}
However, I was not able to pass the array correctly.
whether native array: short* shortsArray = new short[500];
neither a managed array: array<short>^ shortsArray = gcnew array<short>(500);
users ildjarn and Hans Passant suggested that I need to edit the interop assembly file to change the exported method signature to something like Component::(int16[] ptr) which I did and successfully compiled the project but ran into other kind of problems (type mismatch or something).
So now I've made a sample project that reproduces the problemnSolution
The solution contains:
A project for the ActiveX component with one method CopyToBuffer found in SomeCompCtl.h
A test project in C++/CLI. with a single form that has the activeX added to it and a button calls the method with an array of given values.
Another test project in C# that does the same thing
To run the project:
- Simply compile SomeComp to generate Somecomp.ocx which contains the ActiveX.
- regsrv32 the ActiveX control
Please note that I don't access to the ActiveX code (I've had access to one version of code but I cannot presume that the developers will continue to provide me with updated versions of code) so any solutions shouldn't depend on changing the ActiveX interfaces or code. I normally only have the ocx file with its tlb file.
With the signature as CopyToBuffer(short% ptr), how did you call it? If you did CopyToBuffer(myArray[0]) or CopyToBuffer(&myArray[0]), that could fail because the garbage collector could move the array on you. Try this:
pin_ptr<short> pinned = &myArray[0];
component->CopyToBuffer(pinned);
If that doesn't work, try editing the interop assembly file again, change the signature to CopyToBuffer(IntPtr ptr). Since it's more explicit about the fact that the parameter is a simple pointer, perhaps that will work better.
Say we have an existing process (or application) that calls a COM object from an ocx file such as "MyCOMLibrary.ocx".
Is there a way to write a C# library to exactly replicate the ocx file? So that the original application can call your C# code rather than the original COM object?
You would, of course, have to use identical CLSID and ProgIDs as the original ocx. And assuming there is no signing involved, such as a SNK in the .Net world.
Also, are there any tools that exist to automate this? Something that takes in an ocx and spits out a C# file with methods to implement.
EDIT: I want to add that the original application is VB6, and does not use .Net at all. They are most likely loading the ocx as a VB6 app would (ProgId or Guid). Does this cause any issues?
We also have no problem with completely rewriting the ocx--we will most likely just return success error codes for all methods and only use methods/events required by our situation.
EDIT: You would think this would not be too difficult to accomplish. Can we make a VB6 ocx file that could replace the old ocx, and just pass all calls to a .Net assembly?
EDIT: I tried using the following open source library: EasyHook
But it seems like this question should still be viable. VB6 seems to load COM objects in a way that prevents hooking. I don't see a way to hook instance methods on a class/interface or a class's constructor with EasyHook.
You can use ActiveX Import AxImp to import the OCX, create a wrapper class and then call that. The program is described here: http://msdn.microsoft.com/en-us/library/8ccdh774(VS.80).aspx
Basically, what you need to do is execute the following on the commandprompt:
c:/>AxImp MyControl.ocx
result is a MyControl.dll and an AxMyControl.dll. The first you can use as a normal .NET DLL in your projects (i.e., without a graphical user interface), the second can be used to be drawn on a form as you normally would with any other control like a TextBox or a Label.
To use it, go to Visual Studio, rightclick your project and select Add Reference. Browse to the newly created DLL and add it. That's all.
Our Deviare Hook Library can be used for hooking COM objects. You can see an article from our blog related to this topic: Hooking Outlook COM objects with Deviare
Apparently VBMigration Partner can automatically upgrade a VB6 COM component to a VB.Net component that has binary compatibility with the original VB6 component. I don't know whether it supports OCXs. If it does, I'd suggest use that first and then try to go to C# later (if necessary).
Not really a complete answer to the question, but something I think might be useful. If you add a key called 'TreatAs' under the CLSID of the object you want to replace and set the default value to the CLSID of the object you want to create instead, this instructs the COM runtime to create your object instead of the original one. No need then to force your new, replacement object to have the same CLSID and ProgID of the old one.
For example, if your original object had ProgID "MyComLibrary.Object" and CLSID "{ABC}", and your new object has ProgID "MyDotNet.Object" and CLSID "{123}", then under HKLM/CLSID/{ABC} add a key called TreatAs with a default value of {123}. Then any request for "MyComLibrary.Object" or "MyDotNet.Object" will get a copy of the new object (assuming they implement the same interfaces).