Can't call VB.NET COM DLL function with String from VBA - vb.net

I implemented a VB.NET DLL with a simple test function:
<ComVisible(True)>
Function TestString ( <MarshalAs(UnmanagedType.BStr)> xyz As String) As <MarshalAs(UnmanagedType.BStr)> String
Dim y As Integer
TestString = "Hello"
End Function
The function is dead simple. In VBA I declare the function appropriately:
Public Declare Function TestString Lib "myDLL.dll" (xyz As String) As String
From VBA I also obviously load the DLL. However the issue is that when I run the function like so:
Dim st as String
st = "Hello"
Debug.Print TestString(ByVal st)
I get a message saying bad DLL calling convention. On the other hand when I remove the <MarshalAs(UnmanagedType.BStr)> the function works BUT crashes shortly after printing "Hello".
What am I doing wrong?

Public Declare Function is the syntax used to call functions exported from a DLL, this is a different mechanism from COM.
To use COM add a reference to the DLL, create an instance of the class containing TestString then call across that instance: r = classInstance.TestString("Hello")

Related

Casting a string variable to a class object

Using VB.Net
I am using the CallByName() function to call certain subroutines based on a selection made by the user. The options for the subroutines come from a dropdown menu which gets it data from a table in a database. The CallByName function requires the starting class and the subroutine name as well as the method type ex: CallByName(class,subroutine,type.method). In my table I have a column for the class and a column for the subroutine. So these get read by my program as strings. I'm trying to use these variables in the function and while the subroutine string works fine, I can't seem to convert the class string into a proper class object that can be used by the CallByName function.
My code looks like:
Dim base_class as string = myReader(0)
Dim subroutine as string = myReader(1)
base_class = CType(base_class, class)
'* what I need, but doesn't work ^^^
CallByName(base_class, subroutine, CallType.Method)
How can I convert the class variable read in from the DB into something that I can use in the function?
Thanks.
Edit: The code that I ended up using that worked based on Jimi's response:
Dim t As Type = Type.GetType(base_class)
Dim obj = Activator.CreateInstance(t)
CallByName(obj, subroutine , CallType.Method)

Public member 'ToCSVValue' on type 'Integer' not found for VB Extension method

I am trying to write a ToCSV() extension in VB based on Scott Hanselman's blog. It could be that my C# to VB is not correct, but it all seems right.
I added a module with:
<System.Runtime.CompilerServices.Extension>
Public Function ToCSV(Of T)(items As IEnumerable(Of T)) As String
Try
Dim csvBuilder = New StringBuilder()
Dim properties = GetType(T).GetProperties()
For Each item As T In items
'' Test Code
Dim newline As String = ""
For Each l2 As Reflection.PropertyInfo In properties
' This works
newline &= l2.GetValue(item, Nothing)
' This works too
Dim int As Integer = 1234
Dim s As String = int.ToCSVValue()
'This works
Dim nl = l2.GetValue(item, Nothing)
' This blows up with "Public member 'ToCSVValue' on type 'Integer' not found."
' The Debugger type shows "Object {Integer}" which I assume to mean that the debugger interprets the object as an integer.
nl = nl.ToCSVValue()
Next
' Original code
Dim line As String = String.Join(",", properties.Select(Function(p) p.GetValue(item, Nothing).ToCSVValue()).ToArray())
csvBuilder.AppendLine(line)
Next
Return csvBuilder.ToString()
Catch ex As Exception
Throw
End Try
End Function
<System.Runtime.CompilerServices.Extension>
Private Function ToCSVValue(Of T)(item As T) As String
If item Is Nothing Then
Return """"""
End If
If TypeOf item Is String Then
Return String.Format("""{0}""", item.ToString().Replace("""", "\"""))
End If
Dim dummy As Double
If Double.TryParse(item.ToString(), dummy) Then
Return String.Format("{0}", item)
End If
Return String.Format("""{0}""", item)
End Function
When I call it with something like:
Dim s As String = ctx.Customers.Where(Function(x) x.CustomerID = 123456).Select(Function(x) New With {.CustomerID = x.CustomerID, .CustomerName = x.CustomerName}).ToCSV()
it gets to the function ToCSV just fine. It recognizes the items passed in. It pulls out the first item and sees that there are the 2 fields in it. All good!
The GetValue() works just fine.
If I create a static integer and call ToCSVValue on it, it works fine.
If I create a static string and call ToCSVValue on it, it works fine.
When I call ToCSVValue on the GetValue() I get:
Public member 'ToCSVValue' on type 'Integer' not found.
Likewise, if I have just strings in the dataset, I get:
Public member 'ToCSVValue' on type 'String' not found.
Ideally this would work as it is in the "Original code" section and I can kill all this other test code.
Can anyone tell me what is happening and why the "(Of T)" is not working the get GetValue() types, but it is for the directly cast types?
You need to have 'Option Infer On'.
When I use Option Infer On, it works fine.
If you don't use this, then VB is using 'Object' whenever you leave off the type.
Also, although this isn't causing your problem, the proper conversion of the ToCSV method is:
Public Function ToCSV(Of T As Class)(items As IEnumerable(Of T)) As String
The short answer is that calling it as a method ToCSVValue(p.GetValue(item, Nothing)) will work as in the C# version.
The longer answer is that you can't call extension methods on Object in VB. In VB Object is treated more like dynamic in C#. For example:
<Extension()> Function toStr(Of T)(item As T) As String
Return item.ToString
End Function
then this will result in compile-time Warning "Late bound resolution; runtime errors could occur." and a run-time Error "Public member 'toStr' on type 'Integer' not found.", but it will work in C#:
Dim i As Object = 123
Dim s = i.toStr

Working with strings in visual basic 2010

I have to create a class in Visual Basic called StringWork.
Public Class StringWork
Now i wrote a shared function in the class called Working that can take a string or a string and Boolean.
Public Shared Function Working(ByVal SingleString as string, optional BValu as Boolean = true)as string
if(working(SingleString))then
'The handling of the string
else if (working(SingleValue, BValue) then
'do something else with string
end if
end function
The function I wrote is returning a string.
Can I access the string passed and edit characters in the string or change the position of characters?
You use that optional parameter to determine how you handle that string:
Public Shared Function Working(ByVal singleString as string, _
Optional bValue as Boolean = True) As String
If bValue Then
'Handle the true part manipulating the result string
Else
'Handle the false part manipulating the result string
End If
End Function
If you call this function like this:
Dim test As String = StringWork.Working("I am Spartacus")
it will call that Working function with bValue = true.
What bValue is suppose to represent isn't very clear from the code nor the post.
In VB.NET, and other .NET languages, strings are immutable. Typically, if you need to modify a string that is passed to your method, you would return the modified string. If however, you need it to modify the parameter, you can specify that it is a "ByRef" argument, in which case you will be able to set it to point to a new string object which will affect the variable that was passed into the method as a parameter. If you need a truly mutable string, you will need a character array or a StringBuilder object.

Problem returning object from VB.NET COM Assembly into VBA (Access)

I have an assembly in VB .NET 2.0 that I am trying to use to call a webservice.
This will be COM visible, and return the results to Access in VBA.
The .NET Assembly passes all tests and executes perfectly.
I was experiencing "Object does not support this property or method" errors when calling the methods from VBA.
I broke it down to a certain object that was being returned and added some test methods to the .NET DLL.
There is a "Patient" object I want to return.
It looks like this (made it very very simple to test it):
Option Strict On
Option Explicit On
<ComClass(Patient.ClassId, Patient.InterfaceId, Patient.EventsId)> _
Public Class Patient
#Region "COM GUIDs"
' These GUIDs provide the COM identity for this class
' and its COM interfaces. If you change them, existing
' clients will no longer be able to access the class.
Public Const ClassId As String = "672dfbd9-8f3a-4ba2-a33d-89fef868f2b9"
Public Const InterfaceId As String = "74a9c54c-4427-4d31-8220-3258ecda345d"
Public Const EventsId As String = "dc25515e-1bb7-4a66-97d5-270c00d792a9"
#End Region
Public Sub New()
MyBase.New()
End Sub
Public Property StorePatientID() As Integer
Get
Return m_StorePatientID
End Get
Set(ByVal value As Integer)
m_StorePatientID = value
End Set
End Property
Private m_StorePatientID As Integer
End Class
So about as simple as an object can be really.
I have a method that just returns a dummy record, just to test it:
Public Function GetPatientTest() As Patient
Dim patient As New Patient
patient.StorePatientID = 99
Return patient
End Function
This fails with the afformentioned error.
HOWEVER,
This method succeeds!
Public Function GetPatientArrayTest() As Patient()
Dim strings As New List(Of Patient)
Dim patient As New Patient
patient.StorePatientID = 99
strings.Add(patient)
Return strings.ToArray
End Function
The DLL is made com visible through "Properties" page.
Builds to project/bin/debug, always do a rebuild.
Always seems to be updated with new methods etc when I look at it in VBA so don't think it's looking at an old version.
Obviously no funny dependencies with these methods.
Really really struggling with this.
EDIT:
Update 16/03/2011 - Added VBA script
Public Function FindPatientsTest(ByVal surname As String, ByVal surnameBeginsWith As Boolean, ByVal forename As String, ByVal forenameBeginsWith As Boolean, ByVal dateOfBirth As String)
Dim token As String
token = Login()
Dim patient As SCIStoreWS60.patient
Set patient = New SCIStoreWS60.patient
'// This doesn't work.
'// When adding a "Watch" to the function, I can see it returns an "Object/Patient" and is the correct results
'// When adding a "Watch" to the variable "patient" I can see it is a "Patient/Patient"
patient = sciStore.GetPatientTest()
'// This works fine
Dim something As Variant
something = sciStore.GetPatientArrayTest()
End Function
Update 16/03/2011 5 minutes later - Chastising myself
Sorry, I just worked it out.
I need to "Set" the patient variable.
Set patient = sciStore.GetPatientTest()
Why didn't I need to do this for the "something" variant?
So, yes, you need to Set object references, but not arrays.

VB.NET pointer interop question

I am trying to write a VB.NET program that will call a function in an unmanaged C DLL passing the structure like this:
typedef struct {
unsigned char *msg;
int msglen;
}
What I have not been able to figure out is how to handle the "unsigned char *msg" part. How would you define this in the VB.NET Structure?
<StructLayout(LayoutKind.Sequential)> _
public structure foo
<MarshalAs(UnmanagedType.LPStr)> dim msg as string
dim msgLen as integer
end structure
This depends a lot on how the memory for the msg field is handled. You need to be careful to free any allocated memory which is transfered to managed code.
That being said I think the most straight forward interop type is as follows
Public Structure S1
Public msg as IntPtr
Public msgLen as Integer
End Structure
To get the actual msg value as a String you'll need to use the following code.
Public Function GetString(ByVal s1 as S1) As String
return Marshal.PtrToStringAnsi(s1.msg, s1.msgLen)
End Function
To create an S1 instance based on a String do the following. Note: You will need to free the memory allocated here if the calling function does not take ownership.
Public Function CreateS1(ByVal str As String) As S1
Dim local As New S1
local.msg = Marshal.StringToHGlobalAnsi(str)
local.msgLen = str.Length
return local
End Function