.Net Dynamically Load DLL - vb.net

I am trying to write some code that will allow me to dynamically load DLLs into my application, depending on an application setting. The idea is that the database to be accessed is set in the application settings and then this loads the appropriate DLL and assigns it to an instance of an interface for my application to access.
This is my code at the moment:
Dim SQLDataSource As ICRDataLayer
Dim ass As Assembly = Assembly. _
LoadFrom("M:\MyProgs\WebService\DynamicAssemblyLoading\SQLServer\bin\Debug\SQLServer.dll")
Dim obj As Object = ass.CreateInstance(GetType(ICRDataLayer).ToString, True)
SQLDataSource = DirectCast(obj, ICRDataLayer)
MsgBox(SQLDataSource.ModuleName & vbNewLine & SQLDataSource.ModuleDescription)
I have my interface (ICRDataLayer) and the SQLServer.dll contains an implementation of this interface. I just want to load the assembly and assign it to the SQLDataSource object.
The above code just doesn't work. There are no exceptions thrown, even the Msgbox doesn't appear.
I would've expected at least the messagebox appearing with nothing in it, but even this doesn't happen!
Is there a way to determine if the loaded assembly implements a specific interface. I tried the below but this also doesn't seem to do anything!
For Each loadedType As Type In ass.GetTypes
If GetType(ICRDataLayer).IsAssignableFrom(loadedType) Then
Dim obj1 As Object = ass.CreateInstance(GetType(ICRDataLayer).ToString, True)
SQLDataSource = DirectCast(obj1, ICRDataLayer)
End If
Next
EDIT: New code from Vlad's examples:
Module CRDataLayerFactory
Sub New()
End Sub
' class name is a contract,
' should be the same for all plugins
Private Function Create() As ICRDataLayer
Return New SQLServer()
End Function
End Module
Above is Module in each DLL, converted from Vlad's C# example.
Below is my code to bring in the DLL:
Dim SQLDataSource As ICRDataLayer
Dim ass As Assembly = Assembly. _
LoadFrom("M:\MyProgs\WebService\DynamicAssemblyLoading\SQLServer\bin\Debug\SQLServer.dll")
Dim factory As Object = ass.CreateInstance("CRDataLayerFactory", True)
Dim t As Type = factory.GetType
Dim method As MethodInfo = t.GetMethod("Create")
Dim obj As Object = method.Invoke(factory, Nothing)
SQLDataSource = DirectCast(obj, ICRDataLayer)
EDIT: Implementation based on Paul Kohler's code
Dim file As String
For Each file In Directory.GetFiles(baseDir, searchPattern, SearchOption.TopDirectoryOnly)
Dim assemblyType As System.Type
For Each assemblyType In Assembly.LoadFrom(file).GetTypes
Dim s As System.Type() = assemblyType.GetInterfaces
For Each ty As System.Type In s
If ty.Name.Contains("ICRDataLayer") Then
MsgBox(ty.Name)
plugin = DirectCast(Activator.CreateInstance(assemblyType), ICRDataLayer)
MessageBox.Show(plugin.ModuleName)
End If
Next
I get the following error with this code:
Unable to cast object of type 'SQLServer.CRDataSource.SQLServer' to type 'DynamicAssemblyLoading.ICRDataLayer'.
The actual DLL is in a different project called SQLServer in the same solution as my implementation code. CRDataSource is a namespace and SQLServer is the actual class name of the DLL.
The SQLServer class implements ICRDataLayer, so I don't understand why it wouldn't be able to cast it.
Is the naming significant here, I wouldn't have thought it would be.
Final Working code
Contents of PluginUtility:
enter code here Public Shared Function GetInstances1(Of Type)(ByVal baseDir As String, ByVal searchPattern As String) As System.Type()
Dim tmpInstances As New List(Of Type)
Try
Dim file As String
For Each file In Directory.GetFiles(baseDir, searchPattern, SearchOption.TopDirectoryOnly)
Dim assemblyType As System.Type
For Each assemblyType In Assembly.LoadFrom(file).GetTypes
Dim s As System.Type() = assemblyType.GetInterfaces
Return s.ToArray()
Next
Next
Catch exp As TargetInvocationException
If (Not exp.InnerException Is Nothing) Then
Throw exp.InnerException
End If
End Try
End Function
Code to load the DLL:
enter code here
Dim basedir As String = "M:\MyProgs\WebService\DynamicAssemblyLoading\SQLServer\bin\Debug\"
Dim searchPattern As String = "*SQL*.dll"
Dim plugin As CRDataLayer.ICRDataLayer
Try
Dim file As String
For Each file In Directory.GetFiles(baseDir, searchPattern, SearchOption.TopDirectoryOnly)
Dim assemblyType As System.Type
For Each assemblyType In Assembly.LoadFrom(file).GetExportedTypes
If assemblyType.GetInterface("CRDataLayer.ICRDataLayer") IsNot Nothing Then
plugin = DirectCast(Activator.CreateInstance(assemblyType), CRDataLayer.ICRDataLayer)
MessageBox.Show(plugin.ModuleDescription)
End If
Next
Next
Catch exp As TargetInvocationException
If (Not exp.InnerException Is Nothing) Then
Throw exp.InnerException
End If
Catch ex As Exception
MsgBox(ex.Message)
Clipboard.SetText(ex.Message)
End Try

Version 2 - This sample loads up a DLL from it current directory.
There are 2 projects, 1 console application project and a "module" project (the module 'coppies' its DLL to the working directory of the console app).
The sample below simply demonstrates dynamically loading a DLL that implements an interface. The IModule interface just reports its name. PlugInUtility.GetInstances(Of IModule)(Environment.CurrentDirectory, "*.Module.dll") will create an instance of any IModule instance found in a DLL within the current directory ending with ".Module.dll". It's a reflected VB.NET version straight out of Mini SQL Query.
With that in mind something like:
Dim modules As IModule() = PlugInUtility.GetInstances(Of ICRDataLayer)(Environment.CurrentDirectory, "*.Server.dll")
Should satisfy your requirement. Then you just need to chose which one to execute!
The code:
In "VB.LoaderDemo Colsole App"
' IModule.vb
Public Interface IModule
Property ModuleName() As String
End Interface
' PlugInUtility.vb
Imports System.IO
Imports System.Reflection
Public Class PlugInUtility
Public Shared Function GetInstances(Of T)(ByVal baseDir As String, ByVal searchPattern As String) As T()
Dim tmpInstances As New List(Of T)
Try
Dim file As String
For Each file In Directory.GetFiles(baseDir, searchPattern, SearchOption.TopDirectoryOnly)
Dim assemblyType As Type
For Each assemblyType In Assembly.LoadFrom(file).GetTypes()
If (Not assemblyType.GetInterface(GetType(T).FullName) Is Nothing) Then
tmpInstances.Add(DirectCast(Activator.CreateInstance(assemblyType), T))
End If
Next
Next
Catch exp As TargetInvocationException
If (Not exp.InnerException Is Nothing) Then
Throw exp.InnerException
End If
End Try
Return tmpInstances.ToArray()
End Function
End Class
' MainModule.vb
Module MainModule
Sub Main()
Dim plugins As IModule() = PlugInUtility.GetInstances(Of IModule)(Environment.CurrentDirectory, "*.Module.dll")
Dim m As IModule
For Each m In plugins
Console.WriteLine(m.ModuleName)
Next
End Sub
End Module
In "Sample1 DLL" (references 'VB.LoaderDemo' for IModule)
Imports VB.LoaderDemo
Public Class MyModule1
Implements IModule
Dim _name As String
Public Sub New()
_name = "Sample 1, Module 1"
End Sub
Public Property ModuleName() As String Implements IModule.ModuleName
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
End Set
End Property
End Class
The output is:
> Sample 1, Module 1

Is the type ICRDataLayer defined in the DLL you are going to load? If so, you seem to already reference the DLL in your project settings.
You need to work with just reflection:
Dim obj As Object = ass.CreateInstance("ICRDataLayer", True)
Dim t as Type = obj.GetType()
Dim method as MethodInfo = t.GetMethod("DoSomething")
method.Invoke(obj, ...)
Edit: If ICRDataLayer is implemented in the application, and the plugin just implements the interface, you need the plugin to provide a factory for you: (sorry for C# code, I am not familiar with VB.NET's syntax)
// in each of plugins:
static class CRDataLayerFactory // class name is a contract,
{ // should be the same for all plugins
static ICRDataLayer Create()
{
return new CRDataLayerImplementation();
}
}
The application's code should look like this:
Dim factory As Object = ass.CreateInstance("CRDataLayerFactory", True)
Dim t as Type = factory.GetType()
Dim method as MethodInfo = t.GetMethod("Create")
Dim obj as Object = method.Invoke(factory, null)
SQLDataSource = DirectCast(obj, ICRDataLayer)

A few things to look for in your code
Debug through and check that the
assembly is loaded correctly, in case
it fails due to dependency checking
Instead of using GetType, use GetExportedType so you have smaller subset to iterate through
The CreateInstance should use your loadedType rather than the interface (you cant create object from an interface)
Personally, I dont like naming my variable ass, I would shorten it to assem instead :)

Related

How do I save objects in Configuration of Winforms application?

I'm trying to implement functionality to allow the user to select his favorite forms. Favorite forms are forms he/she needs quick access to. To avoid browsing for too long through the ToolStripMenu's.
I try to save a reference to a form in the application configuration. But I'm getting the error
Value of type 'System.Windows.Forms.Form' cannot be converted to
'String'.
Public Sub SetSetting(ByVal pstrKey As String, ByVal frmFavorite As Form)
Dim keyExists As Boolean = False
For Each strKey As String In configuration.AppSettings.Settings.AllKeys
If strKey.Equals(pstrKey) Then
configuration.AppSettings.Settings.Item(pstrKey).Value = frmFavorite
keyExists = True
End If
Next
If Not keyExists Then
configuration.AppSettings.Settings.Add(pstrKey, frmFavorite)
End If
configuration.Save(ConfigurationSaveMode.Modified)
ConfigurationManager.RefreshSection("appSettings")
End Sub
You can only store string values in the application config file, no objects.
But just store the name of the Form in the config file.
When starting your application create the form via reflection like shown in this Object Factory example.
Public Class ObjectFactory
Public Shared Function CreateAnObject(ByVal ObjectName As String) As Object
Dim Assem = [Assembly].GetExecutingAssembly()
Dim myType As Type = Assem.GetType(ObjectName.Trim)
Dim o As Object = Nothing
Try
o = Activator.CreateInstance(myType)
Catch oEx As TargetInvocationException
MessageBox.Show(oEx.ToString)
End Try
Return o
End Function
End Class
...
Dim formName as String = configuration.AppSettings.Settings.Item(<YourSettingKey>)
Dim oForm As Form = _
ObjectFactory.CreateAnObject(formName)

Serialization of a class cause error

Windows 10 universal app. VB
I have the class below which I want to save to file and read from file, Saving works but when I attempt to load I get the error.
Message=Error in line 1 position 249. Element 'http://schemas.datacontract.org/2004/07/KoW_Universal_v2:MagicItem' contains data of the 'http://schemas.microsoft.com/2003/10/Serialization/Arrays:ArrayOfKeyValueOfstringMagicItemyoeMIiQz' data contract. The deserializer has no knowledge of any type that maps to this contract. Add the type corresponding to 'ArrayOfKeyValueOfstringMagicItemyoeMIiQz' to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding it to the list of known types passed to DataContractSerializer.
Source=System.Private.DataContractSerialization
Imports System.Runtime.Serialization
Imports Windows.Storage
Public Class MagicItem
Property MagicItemName As String
Property MagicItemCost As Integer
Property MagicItemDescripyion As String
Property LimitToHero As Boolean
Property LimitToSpecialRule As String 'key to the special rule
End Class
Public Class clsMagicItems
Private MagicItems As New Dictionary(Of String, MagicItem)
Async Sub SaveMagicItems()
' StorageFile File = Await openPicker.PickSingleFileAsync();
Dim myfile As StorageFile
myfile = Await ApplicationData.Current.LocalFolder.CreateFileAsync("MagicItems.dat", CreationCollisionOption.ReplaceExisting)
Dim KnownTypeList As New List(Of Type)
KnownTypeList.Add(GetType(clsMagicItems))
KnownTypeList.Add(GetType(MagicItem))
Dim r As Streams.IRandomAccessStream
r = Await myfile.OpenAsync(FileAccessMode.ReadWrite)
Using outStream As Streams.IOutputStream = r.GetOutputStreamAt(0)
Dim serializer As New DataContractSerializer(GetType(clsMagicItems), KnownTypeList)
serializer.WriteObject(outStream.AsStreamForWrite(), MagicItems)
Await outStream.FlushAsync()
outStream.Dispose()
r.Dispose()
End Using
End Sub
Async Sub LoadMagicItems()
Try
Dim myfile As Stream
Dim KnownTypeList As New List(Of Type)
KnownTypeList.Add(GetType(clsMagicItems))
KnownTypeList.Add(GetType(MagicItem))
Dim serializer As New DataContractSerializer(GetType(clsMagicItems), KnownTypeList)
myfile = Await ApplicationData.Current.LocalFolder.OpenStreamForReadAsync("MagicItems.dat")
'LINE BELOW RAISE ERROR
MagicItems = serializer.ReadObject(myfile)
Catch
'failed to load the file
End Try
End Sub
End Class
Fixed it, should have had the following code
KnownTypeList.Add(GetType(Dictionary(Of String, MagicItem)))
...
Dim serializer As New DataContractSerializer(GetType(Dictionary(Of String, MagicItem)), KnownTypeList)

Entity Framework : Why this code doesn't work

I'm using Entity Framework 6.0, DbContext. I'm using this method to copy an object and some related children:
Imports System.Data.Objects
Imports System.Data.Objects.DataClasses
Imports System.Runtime.CompilerServices
Public Module Entities
<Extension()>
Public Function CloneEntity(Of T As Class)(entity As T, context As ObjectContext, Optional include As List(Of IncludeEntity) = Nothing, Optional copyKeys As Boolean = False) As T
Return CloneEntityHelper(entity, context, include, copyKeys)
End Function
Private Function CloneEntityHelper(Of T As Class)(entity As T, context As ObjectContext, Optional include As List(Of IncludeEntity) = Nothing, Optional copyKeys As Boolean = False) As T
If include Is Nothing Then include = New List(Of IncludeEntity)()
Dim myType = entity.GetType()
Dim methodInfo = context.GetType().GetMethod("CreateObject").MakeGenericMethod(myType)
Dim result = methodInfo.Invoke(context, Nothing)
Dim propertyInfo = entity.GetType().GetProperties()
For Each info In propertyInfo
Dim attributes = info.GetCustomAttributes(GetType(EdmScalarPropertyAttribute), False).ToList()
For Each attr As EdmScalarPropertyAttribute In attributes
If (Not copyKeys) AndAlso attr.EntityKeyProperty
Continue For
End If
info.SetValue(result, info.GetValue(entity, Nothing), Nothing)
Next
If info.PropertyType.Name.Equals("EntityCollection`1", StringComparison.OrdinalIgnoreCase) Then
Dim shouldInclude = include.SingleOrDefault(Function(i) i.Name.Equals(info.Name, StringComparison.OrdinalIgnoreCase))
If shouldInclude Is Nothing Then Continue For
Dim relatedChildren = info.GetValue(entity, Nothing)
Dim propertyType As Type = relatedChildren.GetType().GetGenericArguments().First()
Dim genericType As Type = GetType(EntityCollection(Of ))
Dim boundType = genericType.MakeGenericType(propertyType)
Dim children = Activator.CreateInstance(boundType)
For Each child In relatedChildren
Dim cloneChild = CloneEntityHelper(child, context, shouldInclude.Children, shouldInclude.CopyKeys)
children.Add(cloneChild)
Next
info.SetValue(result, children, Nothing)
End If
Next
Return result
End Function
Public Class IncludeEntity
Public Property Name As String
Public Property Children As New List(Of IncludeEntity)
Public Property CopyKeys As Boolean
Public Sub New(propertyName As String, ParamArray childNodes() As String)
Name = propertyName
Children = childNodes.Select(Function(n) new IncludeEntity(n)).ToList()
End Sub
End Class
End Module
Now I'm using the code like below :
Dim litm, newitm As New MyObject
Dim inc = New List(Of IncludeEntity)()
inc.Add(New IncludeEntity("Child_list"))
litm=context.MyObjects.FirstOrDefault
newitm = litm.CloneEntity(CType(context, Entity.Infrastructure.IObjectContextAdapter).ObjectContext,include:=inc)
The code is executed without errors, but nothing is copied, so newitm is empty.
I have inspected the code and found that this line on the CloneEntity function :
Dim myType = entity.GetType()
Is producing a strange type.
I'm expecting that the type will be of MyObject type, but instead this return :
MyObject_F2FFE64DA472EB2B2BDF7E143DE887D3845AD9D1731FD3107937062AC0C2E4BB
This line too :
Dim result = methodInfo.Invoke(context, Nothing)
produces the same strange type.
I don't know if this is the problem, but this is the only strange thing I have noticed.
Can you help me to find out why this code doesn't work?
Thank you!
Entity framework, like many other ORMs will build a proxy type for your entities so that it can intercept calls to:
Lazy load the contents of any collection contained as properties within your entity, when you access those collection properties.
Detect that you have made changes to the properties of an instance as part of dirty checking, so that it will know which objects are dirty and need to be saved to the database when you invoke SaveChanges.
Refer for example to EF returning proxy class instead of actual entity or Working with Proxies.
If you want to find out the underlying type of your entity that is wrapped by the proxy, i.e. the one that would match with the type you are looking for (e.g. MyObject), you can do that using a method in the object context:
var underlyingType = ObjectContext.GetObjectType(entity.GetType());

Get Result of Private Property TcpClient.BeginConnect, IAsyncResult in VB.NET

I have an application in VB.NET When I run the application in Visual Studio 2010 and mouseover an IAsyncResult, I see the protected property Result. I would like to read the value of the property in the application. How can I do that?
Imports System.Net
Imports System.Net.Sockets
...
Friend Function StartSendGo() As String
'Declarations
Dim strSendMachineName As String = "DEV001"
Dim intSendPort As Integer = 50035
Dim socketclient As New System.Net.Sockets.TcpClient()
Dim rslt As IAsyncResult = tcpClient.BeginConnect(strSendMachineName, intSendPort, New AsyncCallback(AddressOf ConnectCallback), socketclient)
Dim blnSuccess = rslt.AsyncWaitHandle.WaitOne(intTimeOutConnect, True)
'HERE is where I need rslt.Result.Message
End Function
Public Function ConnectCallback()
'Placeholder
End Function
When I mouseover rslt, VS shows that it is of type
System.Net.Sockets.Socket+MultipleAddressConnectAsyncResult I have never seen a plus (+) in a type before, and I am not able to declare a variable of that type. If I expand the properties, there is a protected property Result, which has a property Message with a value of "No connection could be made because the target machine actively refused it 192.0.0.10:50035". I need access to that message. I would also like to access addresses, but that is less important.
I found a solution - to use Reflection to read the value of the private property.
'Imports
Imports System.Reflection
'Call functions that write to rslt
rslt = tcpClient.BeginConnect(strSendMachineName, intSendPort, New AsyncCallback(AddressOf ConnectCallback), socketclient)
blnSuccess = rslt.AsyncWaitHandle.WaitOne(intTimeOutConnect, True)
'Use Reflection
'Get Type
Dim myType As Type = rslt.GetType()
'Get properties
Dim myPropertyInfo As PropertyInfo() = myType.GetProperties((BindingFlags.NonPublic Or BindingFlags.Instance))
'The order of the properties is not guaranteed. Find by name.
For Each pi As PropertyInfo In myPropertyInfo
If pi.Name = "Result" Then
'TODO Add check for nothing.
'Assign to Exception-type variable.
exException = pi.GetValue(rslt, Nothing)
End If
Next

Visual basic "Unhandled exception"

Hello I'm getting this error, while trying to save serial number to XML File.
If the file doesn't exist, it saves the file fine, but if i change Registered tag to False in Xml file, and try again, it says "The Process Cannot acces the file ... because it is being used by another process".
In my main form i read the information from XML, and in my regform (which i open if registered tag in xml is false) i write to the file. is it because of that?! I don't think so.
Here is my Registration class:
Imports System.IO
Imports System.Xml
Public Class RegistrationClass
Public Property SerialNumber As String
Public Property Registered As Boolean = False
Public Sub Write_Reg(ByVal FileString As String, ByVal RegisterName As String, ByVal RegisterCompany As String, ByVal RegisterSerialNumber As String)
Dim Registered As Boolean = False
Dim Comment As String = "StroySoft 2012 Register Database"
Dim SerialNumber As String = "dev-xxx-123"
Dim ClientOS As String = Trim(My.Computer.Info.OSFullName)
If RegisterSerialNumber = SerialNumber Then
Dim settings As New XmlWriterSettings()
settings.Indent = True
' Initialize the XmlWriter.
Dim XmlWrt As XmlWriter = XmlWriter.Create(FileString, settings)
With XmlWrt
' Write the Xml declaration.
.WriteStartDocument()
' Write a comment.
.WriteComment(Comment)
' Write the root element.
.WriteStartElement("Data")
' Start our first person.
.WriteStartElement("Register")
' The person nodes.
.WriteStartElement("Name")
.WriteString(RegisterName.ToString())
.WriteEndElement()
.WriteStartElement("Company")
.WriteString(RegisterCompany.ToString())
.WriteEndElement()
.WriteStartElement("SerialNumber")
.WriteString(RegisterSerialNumber.ToString())
.WriteEndElement()
Registered = True
.WriteStartElement("Registered")
.WriteString(Registered)
.WriteEndElement()
.WriteStartElement("ClientOS")
.WriteString(ClientOS)
.WriteEndElement()
' The end of this person.
.WriteEndElement()
' Close the XmlTextWriter.
.WriteEndDocument()
.Close()
End With
MsgBox("Успешна регистрация! Благодарим Ви!")
MainForm.РегистрацияToolStripMenuItem.Visible = False
Else
MsgBox("Невалиден сериен номер!")
End If
End Sub
Public Sub Check_Reg(ByVal FileString As String)
If (System.IO.File.Exists(FileString)) Then
Dim document As XmlReader = New XmlTextReader(RegForm.RegFile)
While (document.Read())
Dim type = document.NodeType
If (type = XmlNodeType.Element) Then
If (document.Name = "Registered") Then
If document.ReadInnerXml.ToString() = "True" Then
Registered = True
Else
Registered = False
End If
End If
If (document.Name = "SerialNumber") Then
SerialNumber = document.ReadInnerXml.ToString()
End If
End If
End While
Else
MessageBox.Show("The filename you selected was not found.")
End If
End Sub
End Class
is it because of that?! I don't think so.
It's exactly because of that.
You should always make sure to properly dispose IDisposable resources such as Streams and Writers/Readers by wrapping them in a Using block. In your case I don't see you closing your reader. But if you wrap it in a Using block you shouldn't worry about it. Even if an exception is thrown the resource will be properly released.
Example:
Using XmlWrt As XmlWriter = XmlWriter.Create(FileString, settings)
...
End Using
You should do the same with your XmlReader:
Using document As XmlReader = XmlReader.Create(RegForm.RegFile)
...
End Using