Managed method for SetParent() on form - vb.net

How can I show a form as a child of a window that isn't in my program?
I have a window handle to what should be the parent, but I don't see any managed method for SetParent() on a form. Is there one? It also seems that the form.Show() method only accepts managed objects implementing IWin32Window.
If there isn't a managed method, what is the preferred method for declaring the API for maximum compatibility with future systems? Like this?:
<DllImport("user32.dll")> _
Private Shared Function SetParent(hWndChild As IntPtr, hWndNewParent As IntPtr) As IntPtr
End Function
Is it possible to build a class that implements IWin32Window and somehow wraps up a window? It would be handy do something like this, but I am not familiar with IWin32Window:
frmMyForm.Show(New NativeWindowWrapper(12345)) 'Where 12345 is the hWnd of the window I want to wrap

Oh wow, I just found the documentation on IWin32Window, and see that it is only one property... Handle. Yes, then of course I can easily make this NativeWindowWrapper class...
I haven't tested it yet, but I am sure it will work just fine...
Public Class NativeWindowWrapper
Implements IWin32Window
Private _Handle As IntPtr
Public ReadOnly Property Handle As System.IntPtr Implements System.Windows.Forms.IWin32Window.Handle
Get
Return _Handle
End Get
End Property
Public Sub New(ByVal Handle As IntPtr)
Me._Handle = Handle
End Sub
End Class

Related

Public Shared Readonly Member optimized out in VB.Net?

I have this weird behavior in VB.Net I am stuck with. At the moment I am not able to determine whether I am missing some concept or if it is a bug.
I have a base class:
Public MustInherit Class BaseType(Of T As BaseType(Of T)) :
Implements IEquatable(Of BaseType(Of T))
Protected Sub New(key As Integer, value As List(Of String))
LogManager.GetLogger("INFO").Info("Strings: " & vbTab & value.Count())
If key <> -1 Then
enumValues.Add(CType(Me, T))
End If
End Sub
Protected Shared Function TypeFromStringBase(name As String) As T
For Each b As T In enumValues
LogManager.GetLogger("DEBUG").Info(b.Names(0))
If b.Names.Contains(name) Then
Return b
End If
Next
Return TypeFromStringBase("Keine")
End Function
End Class
And a Class that inherits it:
Public Class AnschlussType : Inherits BaseType(Of AnschlussType) :
Implements IEquatable(Of AnschlussType)
Public Shared ReadOnly Rund As New AnschlussType(1, {"Rund", "1"})
Public Shared ReadOnly Flach As New AnschlussType(2, {"Flach", "2"})
Public Shared ReadOnly Gewinde As New AnschlussType(3, {"Gewinde", "3"})
Public Shared ReadOnly Kein As New AnschlussType(4, {"Kein", "None", "4"})
Private Sub New(key As Integer, names As String())
MyBase.New(key, names.ToList())
End Sub
Public Shared Function TypeFromString(name As String) As AnschlussType
Return TypeFromStringBase(name)
End Function
End Class
Here is the weird part I don't get. The first time you call AnschlussType.TypeFromString("Some String"), VB should create all the Public Shared ReadOnly members. This results in four calls to BaseType.New. Each of those calls then adds its own type to the enumValues List.
After all those initializations, finally, the AnschlussType.TypeFromString call will be executed. There it calls TypeFromStringBase which iterates over the enumValues List we filled right before.
This all works fine in the DEBUG mode.
Here is the weird part I don't get. Now I tried RELEASE mode. The enumValues.Count would always stay 0. I assumed this because the logger does not print anything, which means it doesn't iterate, which means it is zero. So I investigated a little more and put a logging statement into BaseType.New. And this does NOT log at all. This leads me to the conclusion that New is not executed at all.
Let me emphasize that this all works great in DEBUG mode and with other subtypes that have Public Shared ReadOnly members in the same matter.
But this does not work in RELEASE mode.
Does anyone have a hint for me what I could be missing?
If a static constructor (Section 10.11) exists in the class, execution of the static field initializers occurs immediately prior to executing that static constructor. Otherwise, the static field initializers are executed at an implementation-dependent time prior to the first use of a static field of that class.
Assuming VB works like C#, your shared (i.e. static) fields aren't being initialized because you haven't used them.
Try creating a shared constructor.

chosing keyboard layout using vb.net code

I have installed Phoenetic Key board for Urdu language, that I can select from Control Panel > Languages > Keyboard
Can I add languages and select keyboard using my vb.net code?
Thanks
The InputLanguage.CurrentInputLanguage property lets you switch keyboard layouts. Not so sure it can deal with multiple layouts for a single language but I don't really know what "Phoenetic Key board" really means. The underlying Windows api functions are LoadKeyboardLayout() and ActivateKeyboardLayout(), you could pinvoke them. GetKeyboardLayoutList() to get a list of installed layouts, you probably need that, GetKeyboardLayoutName() to get a description of a layout. Also covered by the InputLanguage class.
You normally leave it up to the user to select keyboard layouts, forcing your preference is pretty hostile to usability. Easy to do with the language bar.
You can change your application Input language this way:
InputLanguage.CurrentInputLanguage = InputLanguage.FromCulture(New System.Globalization.CultureInfo("ZH-CN"))
but if you don't have the required InputLanguage Installed you can install your cultures input language from code temporary by using windows api:
<DllImport("user32.dll")> _
Private Shared Function UnloadKeyboardLayout(hkl As IntPtr) As Boolean
End Function
<DllImport("user32.dll")> _
Private Shared Function LoadKeyboardLayout(pwszKLID As String, Flags As UInteger) As IntPtr
End Function
Public Class KeyboardHolder
Implements IDisposable
Private ReadOnly pointer As IntPtr
Public Sub New(klid As Integer)
pointer = LoadKeyboardLayout(klid.ToString("X8"), 1)
End Sub
Public Sub New(culture As CultureInfo)
Me.New(culture.KeyboardLayoutId)
End Sub
Public Sub Dispose()
UnloadKeyboardLayout(pointer)
GC.SuppressFinalize(Me)
End Sub
Protected Overrides Sub Finalize()
Try
UnloadKeyboardLayout(pointer)
Finally
MyBase.Finalize()
End Try
End Sub
End Class
and use it this way:
' install keyboard layout temporary
Dim keyboard As New KeyboardHolder(New System.Globalization.CultureInfo("ZH-CN"))
' after finishing what you want remove temporary added keyboard layout:
keyboard.Dispose()

Raising events in a class library exposed to COM

I'm trying to write a wrapper to a service, which will be used by an existing VB6 project. I've got most of the basic framework working, except for one important aspect: I can reference the wrapper in a VB6 project and subs/function calls etc. work as expected, but events do not. The events are visible in the VB6 app, but they never fire.
VB.NET Code:
Public Event Action_Response(ByVal Status as String)
Public Function TestEvent()
RaiseEvent Action_Response("Test Done")
Return "Done"
End Function
VB6 Code:
Dim WithEvents my_Wrapper as Wrapper_Class
Private Sub cmdTest_Click()
Set my_Wrapper = New Wrapper_Class
Debug.Print my_Wrapper.TestEvent()
End Sub
Private Sub my_Wrapper_Action_Response(ByVal Status As String)
Debug.Print Status
Set my_Wrapper = Nothing
End Sub
So, the cmdTest button code prints 'Done' as expected, but the Action_Response event doesn't fire. Is there something else do I need to do to get the event to fire?
Its too much to write in a comment, so I'll make it an answer....
First, identify the .net class you want to expose to COM. I'll pick a class called CORE.
Create an interface that describes the EVENTS that the CORE object will source (ie generate).
<ComVisible(True)> _
<Guid("some guid here...use guidgen, I'll call it GUID1")> _
<InterfaceType(ComInterfaceType.InterfaceIsIDispatch)> _
Public Interface ICoreEvents
<System.Runtime.InteropServices.DispId(1)> _
Sub FileLoaded(ByVal Message As String)
End Interface
Next, Create an interface for the COM exposed properties and methods of your class.
<ComVisible(True)> _
<Guid("another GUID, I'll call it guid2")> _
<InterfaceType(ComInterfaceType.InterfaceIsDual)> _
Public Interface ICore
ReadOnly Property Property1() As Boolean
ReadOnly Property AnotherProperty() As ISettings
ReadOnly Property Name() As String
ReadOnly Property Phone() As String
End Interface
Now, create your actual .net class
<ComVisible(True)> _
<ClassInterface(ClassInterfaceType.None)> _
<ComDefaultInterface(GetType(ICore))> _
<ComSourceInterfaces(GetType(ICoreEvents))> _
<Guid("a third GUID, I'll call it GUID3")> _
Public Class Core
Implements ICore
<System.Runtime.InteropServices.ComVisible(False)> _
Public Delegate Sub OnFileLoaded(ByVal Message As String)
Public Event FileLoaded As OnFileLoaded
End Class
Now, when you need to raise the FileLoaded event, just RAISEEVENT FILELOADED(Message) from within your class. .NET will forward the event out to COM because you've hooked up the COMSourceInterfaces attribute.
The attribute is shorthand for much of of this, but unfortunately doesn't give you quite the control that you need to do certain things (for instance retain version compatibility on your com interfaces).

Handling VB.NET events in VB6 code

I have some VB6 code that instantiates a class which handles events that are being raised from a VB.NET component. The VB6 is pretty straightforward:
private m_eventHandler as new Collection
...
public sub InitSomething()
dim handler as EventHandler
set handler = new EventHandler
m_eventHandler.Add handler
...
m_engine.Start
end sub
Note that the event handler object has to live beyond the scope of the init method (which is why it is being stored in a Collection). Note also that m_engine.Start indicates the point in the program where the VB.NET component would start raising events.
The actual event handler (as requested):
Private WithEvents m_SomeClass As SomeClass
Private m_object as Object
...
Private Sub m_SomeClass_SomeEvent(obj As Variant)
Set obj = m_object
End Sub
Note that m_object is initialized when an instance of EventHandler is created.
The VB.NET code which raises the event is even simpler:
Public ReadOnly Property SomeProp() As Object
Get
Dim obj As Object
obj = Nothing
RaiseEvent SomeEvent(obj)
SomeProp = obj
End Get
End Property
My problem is that when I debug the VB6 program, the first time InitSomething gets called, the event will not be handled (the VB6 event handler is never entered). Subsequent calls to InitSomething does work.
Everything works as I would have expected when I run the program outside the debugger. At this point, I'm not even sure if this is something I should be worried about.
It may or may not be relevant but the VB.NET was converted from a VB6 using the Visual Studio code conversion tool (and subsequently manually cleaned up).
I've found that if you are writing .Net Components for Consumption in VB6 (or any other COM environment) the utilisation of Interfaces is absolutely criticial.
The COM templates that comes out of the box with VStudio leave a lot to be desired especially when you are trying to get Events to work.
Here's what I've used.
Imports System.Runtime.InteropServices
Imports System.ComponentModel
<InterfaceType(ComInterfaceType.InterfaceIsDual), Guid(ClientAction.InterfaceId)> Public Interface IClientAction
<DispId(1), Description("Make the system raise the event")> sub SendMessage(ByVal theMessage As String)
End Interface
<InterfaceType(ComInterfaceType.InterfaceIsIDispatch), Guid(ClientAction.EventsId)> Public Interface IClientActionEvents
<DispId(1)> Sub TestEvent(ByVal sender As Object, ByVal e As PacketArrivedEventArgs)
End Interface
<ComSourceInterfaces(GetType(IClientActionEvents)), Guid(ClientAction.ClassId), ClassInterface(ClassInterfaceType.None)> _
Public Class ClientAction
Implements IClientAction
Public Delegate Sub TestEventDelegate(ByVal sender As Object, ByVal e As PacketArrivedEventArgs)
Public Event TestEvent As TestEventDelegate
public sub New()
//Init etc
end sub
public sub SendMessage(theMessage as string) implements IClientAction.SendMessage
onSendMessage(theMessage)
end sub
Protected Sub onSendMessage(message as string)
If mRaiseEvents Then
RaiseEvent TestEvent(Me, New PacketArrivedEventArgs(theMessage))
End If
End Sub
end Class
I've been able to get COM and .Net consumers of the Assembly/Component to work properly with events and be able to debug in and out of the component.
Hope this helps.
Just something to try - I have an inherent distrust of "As New .."
Can you try
private m_eventHandler as Collection
public sub InitSomething()
dim handler as EventHandler
set handler = new EventHandler
If m_eventHandler Is Nothing Then
Set m_eventHandler = New Collection
End if
m_eventHandler.Add handler
...
m_engine.Start
end sub
Alas, I've got no idea why this works in normal execution and not in debug except some vague suspicions that it's to do with .NET being unable to instantiate the VBA.Collection object (MS recommends that you write a quick VB6 component to do so), but since you're not creating collections in .NET code, it is still just a vague suspicion.

callback function from unmanaged dll in VB .NET

I'm trying to use an unmanaged dll in VB.NET. The example source code provided with the dll is in VB6 and below is my attempt to convert it to .NET. When the dll tries to do a callback I get a "Attempted to read or write protected memory" exception. I really don't care about the callback function getting actually called.
My code:
<DllImport("AlertMan.dll")> _
Public Shared Function AlertManC( _
ByVal CallbackAddr As AlertManCallbackDel) As Long
End Function
Public Delegate Sub AlertManCallbackDel(ByVal data As Long)
Public Sub AlertManCallback(ByVal data As Long)
End Sub
Public mydel As New AlertManCallbackDel(AddressOf AlertManCallback)
'protected memeory exception here
Dim IStat as Long = AlertManC(mydel)
Original VB6 example code:
Declare Function AlertManC _
Lib "AlertMan.dll" _
Alias "AlertManC" (ByVal CallbackAddr As Long) As Long
Private Sub AlertManCallback(ByVal data As Long)
End Sub
' calling code
Dim IStat As Long
IStat = AlertManC(AddressOf AlertManCallBack)
Original dll header
typedef void TACBFUNC(char *);
int AlertManC(TACBFUNC *WriteCaller cHANDLEPARM);
Can you post the original native definiton for AlertManC?
My guess though is that the data parameter of the callback function is actually an Integer vs. a Long. In VB6 I believe Long's were actually only 32 bits vs. VB.Net where they are 64 bit. Try this
<DllImport("AlertMan.dll")> _
Public Shared Function AlertManC(ByVal CallbackAddr As AlertManCallbackDel) As Long
End Function
Public Delegate Sub AlertManCallbackDel(ByVal data As IntPtr)
Public Sub AlertManCallback(ByVal data As IntPtr)
End Sub
Edit
I updated the code based on the native signature you posted. Can you try this out?
Your callback should look like this:
Public Delegate Sub AlertManCallbackDel(ByRef data As Byte)
The reason for this being that you are passing a single-byte value by reference.
As for the declaration of the unmanaged function, it should look like this:
<DllImport("AlertMan.dll")> _
Public Shared Function AlertManC( _
ByVal CallbackAddr As AlertManCallbackDel) As Integer
End Function
Note that the return type is an Integer, which in VB.NET is a 32-bit value. In VB6, a Long was a 32-bit value, hence the need for a change in VB.NET.
The callback definition is important to get right as well, btw.
If the callback's calling convention is cdecl, you cant do that directly in C# or VB.NET.
You will have to modify the IL of the delegate to behave correctly.
You can search on CodeProject for an in-depth article.
Update:
I guess not the correct answer :) But will leave my response.