BC36913: Cannot infer a common type - vb.net

Here's my code:
Private Const CFE_LINK As UInt32 = &H20
Public Sub SetSelectionLink(ByVal link As Boolean)
SetSelectionStyle(CFM_LINK, If(link, CFE_LINK, 0))
End Sub
Public Function GetSelectionLink() As Integer
Return GetSelectionStyle(CFM_LINK, CFE_LINK)
End Function
Private Sub SetSelectionStyle(ByVal mask As UInt32, ByVal effect As UInt32)
Dim cf As CHARFORMAT2_STRUCT = New CHARFORMAT2_STRUCT()
cf.cbSize = CUInt(Marshal.SizeOf(cf))
cf.dwMask = mask
cf.dwEffects = effect
Dim wpar As IntPtr = New IntPtr(SCF_SELECTION)
Dim lpar As IntPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(cf))
Marshal.StructureToPtr(cf, lpar, False)
Dim res As IntPtr = SendMessage(Handle, EM_SETCHARFORMAT, wpar, lpar)
Marshal.FreeCoTaskMem(lpar)
End Sub
I got an error (Cannot infer a common type for the first and second operands of the binary 'If' operator) on that line:
SetSelectionStyle(CFM_LINK, If(link, CFE_LINK, 0))

The type for the constant 0 is Integer. I think the error is because the compiler can't tell whether it should use Integer or UInt32 as the result type; they're both integer types with the same bit width, the only difference is the upper and lower bounds.
As you've noted, you can use an explicit conversion to make both operands to If have the same type.
You can also use the appropriate type suffix to make the constant 0 have the right type. In this case, the following should work:
SetSelectionStyle(CFM_LINK, If(link, CFE_LINK, 0UI))
The UI suffix tells the compiler to treat the 0 as a UInteger (which is the same type as UInt32) instead of an Integer.

Ok...Following the MSDN Documentation, I tried this and the compilator seems ok.
SetSelectionStyle(CFM_LINK, If(link, CFE_LINK, CUint(0)))

Related

GdipSaveImageToFile(), pointer to a Structure/Class in VB.net

I am using GdipSaveImageToFile() from GDI+ dll. It works all right if I send a null pointer in the last parameter (EncoderParameters)
<System.Runtime.InteropServices.DllImport("gdiplus.dll", ExactSpelling:=True, CharSet:=System.Runtime.InteropServices.CharSet.Unicode)>
Friend Shared Function GdipSaveImageToFile(image As IntPtr, filename As String, <System.Runtime.InteropServices.[In]> ByRef clsid As Guid, encparams As IntPtr) As Integer
End Function
Sub test(hbmp as IntPtr, filename as String, clsid as Guid)
Dim status as Integer = GdipSaveImageToFile(hbmp, filename, clsid, IntPtr.Zero)
If status <> 0 Then
MessageBox.Show("Error status = " & status)
End If
End Sub
The code saves the image to a file using the standard settings.
Now, I have been strugling sending a real pointer in the last parameter (EncoderParameters) in vb.net.
Here is my attempt:
<System.Runtime.InteropServices.DllImport("gdiplus.dll", ExactSpelling:=True, CharSet:=System.Runtime.InteropServices.CharSet.Unicode)>
Friend Shared Function GdipSaveImageToFile(image As IntPtr, filename As String, <System.Runtime.InteropServices.[In]> ByRef clsid As Guid, ByRef encparams As cEncoderParameters) As Integer
End Function
<StructLayout(LayoutKind.Sequential, Pack:=2, CharSet:=CharSet.Ansi)>
Friend Structure cEncoderParameter
Public GUID As Guid
Public NumberOfValues As UInt32
Public type As UInt32
Public Value As IntPtr
End Structure
<StructLayout(LayoutKind.Sequential, Pack:=2)>
Friend Class cEncoderParameters
Public Count As UInt32
Public Parameter As cEncoderParameter
End Class
Friend Enum cEncoderParameterType As UInt32
EncoderParameterValueTypeByte = 1 ' 8-bit unsigned int
EncoderParameterValueTypeASCII = 2 ' 8-bit byte containing one 7-bit ASCII code. NULL terminated.
EncoderParameterValueTypeShort = 3 ' 16-bit unsigned int
EncoderParameterValueTypeLong = 4 ' 32-bit unsigned int
EncoderParameterValueTypeRational = 5 ' Two Longs. The first Long Is the numerator, the second Long expresses the denomintor.
EncoderParameterValueTypeLongRange = 6 ' Two longs which specify a range of integer values. The first Long specifies the
' lower end And the second one specifies the higher end. All values are inclusive at both ends
EncoderParameterValueTypeUndefined = 7 ' 8-bit byte that can take any value depending on field definition
EncoderParameterValueTypeRationalRange = 8 ' Two Rationals. The first Rational specifies the lower end And the second specifies
' the higher end. All values are inclusive at both ends
EncoderParameterValueTypePointer = 9 ' A pointer to a parameter defined data.
End Enum
Sub b(hbmp As IntPtr, filename As String, clsid As Guid)
Dim eps As New cEncoderParameters
eps.Count = 1
eps.Parameter.GUID = Encoder.Quality.Guid
eps.Parameter.NumberOfValues = 1
eps.Parameter.type = cEncoderParameterType.EncoderParameterValueTypeLong
eps.Parameter.Value = New IntPtr(10)
If GdipSaveImageToFile(hbmp, filename, clsid, eps) <> 0 Then
MessageBox.Show("Error")
End If
End Sub
But the code breaks at the GdipSaveImageToFile(), with the following message
An unhandled exception of type 'System.AccessViolationException'
occurred in TWAIN.exe
Additional information: Attempted to read or write protected memory.
This is often an indication that other memory is corrupt.
I also tried to change EncoderParameter definition from Class to Structure, and the following code
Dim pEnc As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(eps))
Marshal.StructureToPtr(eps, pEnc, False)
status = GdipSaveImageToFile(hbmp, filename, clsid, pEnc)
Marshal.FreeHGlobal(pEnc)
But I get a similar error message
Any ideas? I am burn out :)
Additional information: Definitions from gdiplusimaging.h, gdiplusflat.h
class EncoderParameter
{
public:
GUID Guid; // GUID of the parameter
ULONG NumberOfValues; // Number of the parameter values
ULONG Type; // Value type, like ValueTypeLONG etc.
VOID* Value; // A pointer to the parameter values
};
class EncoderParameters
{
public:
UINT Count; // Number of parameters in this structure
EncoderParameter Parameter[1]; // Parameter values
};
GpStatus WINGDIPAPI
GdipSaveImageToFile(GpImage *image, GDIPCONST WCHAR* filename,
GDIPCONST CLSID* clsidEncoder,
GDIPCONST EncoderParameters* encoderParams);

How to make Parameters of VB.NET function as Generic type?

I have a VB.NET function as below, the parameter 'x' that is passed to the function is of Type 'Single'. However, I want to write the function so that it can accept any numeric type such as 'Single', 'Double' and 'Integer'. I know one way of doing that is to write 3 functions with the same names, but it would be so tedious. Can anyone suggest any idea? Thank you.
Public Function Square(x As Single) As Single
Return x * x
End Function
try following method
Public Function Square(Of T)(ByVal x As Object) As T
Dim b As Object = Val(x) * Val(x)
Return CType(b, T)
End Function
You can use above function like this
Dim p As Integer = Square(Of Integer)(10)
Dim d As Double = Square(Of Double)(1.5)
The code allows any value to posted via Arg but only numeric values will be processed. The returned value must be double because Val returns double only.
The Of T allows for generic object types to be presented.
Private Function sqr(Of T)(Arg As T) As Double
If IsNumeric(Arg) Then
Return Val(Arg) * Val(Arg)
Else
Return 0
End If
End Function
You can constrain the generic type by IConvertible and Structure. The following data types implements the IConvertible interface:
System.Boolean
System.Byte
System.Char
System.DateTime
System.DBNull
System.Decimal
System.Double
System.Enum
System.Int16
System.Int32
System.Int64
System.SByte
System.Single
System.String
System.UInt16
System.UInt32
System.UInt64
Here's a rewrite of the code found in the link provided by SLaks:
Public Function Square(Of T As {IConvertible, Structure})(x As T) As T
'TODO: If (GetType(T) Is GetType(Date)) Then Throw New InvalidOperationException()
Dim left As ParameterExpression = Expression.Parameter(GetType(T), "x")
Dim right As ParameterExpression = Expression.Parameter(GetType(T), "x")
Dim body As BinaryExpression = Expression.Multiply(left, right)
Dim method As Func(Of T, T, T) = Expression.Lambda(Of Func(Of T, T, T))(body, left, right).Compile()
Return method(x, x)
End Function
Reference: https://jonskeet.uk/csharp/miscutil/usage/genericoperators.html

ByRef in VB.NET

I have written the following code in VB.NET:
Dim obj As Object
obj = "00"
Test(obj)
MsgBox(obj)
Private Sub Test(ByRef num As Integer)
End Sub
Private Sub Test(ByVal num As Integer)
End Sub
When the value "00" is passed "ByRef" in the method "Test" it converts to 0. But if the value "00" is passed "ByVal" it keeps the same value as "00". How the passed value is being converted only depending of the signature?
In VB6 although the default passing type is "ByRef", still the same code keeps the same value("00")
Could anybody explain the reason behind this contradictory behaviour in VB6 and VB.NET?
The way you are doing it, the ByRef changes the type of the object from string to integer. By default, integer do not have trailling "0" when covnerted to strings.
This example below might help you understand what is hapenning.
Sub Main()
Dim o1 As Object = "00"
Dim o2 As Object = "00"
Console.WriteLine(o1.GetType().ToString())
Test1(o1)
Console.WriteLine(o1.GetType().ToString())
Console.WriteLine(o2.GetType().ToString())
Test2(o2)
Console.WriteLine(o2.GetType().ToString())
Console.ReadLine()
End Sub
Sub Test1(ByVal num As Integer)
End Sub
Sub Test2(ByRef num As Integer)
End Sub
Output
System.String
System.String
System.String
System.Int32
I suggest you always turn Option Strict On, this will remove a lot of confusion.
The object is of type System.String. It cannot be passed ByRef to a method, it is of the wrong type. So the compiler has to work around it and rewrites the code:
Dim obj As Object
obj = "00"
Dim $temp As Integer
$temp = CInt(obj)
Test($temp)
obj = $temp '' <=== Here
MsgBox(obj)
The indicated statement is the one that changes the object from a string to an integer. Which, converted again to a string by the MsgBox() call, produces "0" instead of "00".
Notable is that C# does not permit this and generate a compile error. This rewriting trick is rather nasty, if the method itself changes the original object then you'll have a very hard time guessing what is going on since that doesn't change the passed argument value.
ByRef means that value passes by reference and in function will be used the same value what has been sent.
ByVal means that value passes by value (function creates a copy of passed value) and you use only copy of value.

How do I import and call unmanaged C dll with ANSI C string "char *" pointer string from VB.NET?

I have written my own function, which in C would be declared like this, using standard Win32 calling conventions:
int Thing( char * command, char * buffer, int * BufSize);
I have the following amount of Visual Basic code figured out, which should import the DLL file and call this function, wrapping it up to make it easy to call Thing("CommandHere",GetDataBackHere).
UPDATE: This code is now a working solution, as shown here:
Imports Microsoft.VisualBasic
Imports System.Runtime.InteropServices
Imports System
Imports System.Text
Namespace dllInvocationSpace
Public Class dllInvoker
' I tried attributes, but I could not make it build:
' <DllImport("thing1.dll", False, CallingConvention.Cdecl, CharSet.Ansi, "Thing", True, True, False, True)>
Declare Ansi Function Thing Lib "thing1.dll" (ByVal Command As String, ByRef Buffer As StringBuilder, ByRef BufferLength As Integer) As Integer
' This part contributed by helpful user:
Shared Function dllCall(ByVal Command As String, ByRef Results As String) As Integer
Dim Buffer As StringBuilder = New StringBuilder(65536)
Dim Length As Integer = Buffer.Capacity
Dim retCode As Integer = Thing(Command, Buffer, Length)
Results = Buffer.ToString()
'Debug.Assert(Results.Length = Length) ' This assertion is not true for me
Return retCode
End Function
End Class
End Namespace
I got the code to build by following the help received here, and then I had forgot the As Return Type (which got me a MarshalDirectiveException PInvokeRestriction). Then I had an assertion failure inside my DLL, which lead to an SEHException. Once fixed, this works BEAUTIFULLY. Thank you folks. There are newsgroups where people are saying this can not be done, that Visual Basic only loads managed DLL assemblies (which I guess is the normal thing most Visual Basic users are used to).
It depends on how you use the buffer argument in your C code. If you only pass a string from your VB.NET code to your C code then declaring it ByVal String is good enough. If however you let the C code return a string in the buffer then you have to declare it ByVal StringBuilder and initialize it properly before the call. For example:
Public Class dllInvoker
Declare Ansi Function Thing Lib "Thing1.dll" (ByVal Command As String, ByVal Buffer As StringBuilder, ByRef BufferLength As Integer) As Integer
Shared Function dllCall(ByVal Command As String, ByRef Results As String) As Integer
Dim Buffer As StringBuilder = New StringBuilder(65536)
Dim Length As Integer = Buffer.Capacity
Dim retCode As Integer = Thing(Command, Buffer, Length)
Results = Buffer.ToString()
Debug.Assert(Results.Length = Length)
Return retCode
End Function
End Class
Note the ambiguity in the returned Length value.
You cannot convert a StringBuilder instance to a string instance, instead, use the 'ToString' method to convert it back to the string type...here's the portion of the code in the dllCall function...
retCode = Thing(Command, Buffer, bufsz)
Results = Buffer.ToString();

How can I use CodeDom to create a decimal constant?

I have this function in my generator.
Private Sub AddBoundedValue(ByVal boundedValue As Object, ByVal type As CodeTypeDeclaration, ByVal numericType As Type, name As String)
If boundedValue IsNot Nothing Then
Dim constant As New CodeMemberField(numericType, name)
constant.Attributes = MemberAttributes.Const Or MemberAttributes.Public
constant.InitExpression = New CodePrimitiveExpression(Convert.ChangeType(boundedValue, numericType))
type.Members.Add(constant)
End If
End Sub
If a developer passes a decimal in for the "boundedValue" parameter and the decimal type for the "numericType" parameter the following code gets generated.
Public Const DollarAmountMaximumValue As Decimal = 100000
Despite the data type being passed into the constructor of the CodePrimitiveExpression object being a decimal, the code generated is an integer that gets implicitly converted and stored in a decimal variable. Is there any way to get it to generate with the "D" after the number as in:
Public Const DollarAmountMaximumValue As Decimal = 100000D
Thanks.
Well, I'm not happy about this solution but unless someone has a better one I'll have to go with it.
Private Sub AddBoundedValue(ByVal boundedValue As Object, ByVal type As CodeTypeDeclaration, ByVal numericType As Type, name As String)
If boundedValue IsNot Nothing Then
Dim constant As New CodeMemberField(numericType, name)
constant.Attributes = MemberAttributes.Const Or MemberAttributes.Public
If numericType Is GetType(Decimal) AndAlso [I detect if the language is VB.NET here] Then
constant.InitExpression = New CodeSnippetExpression(boundedValue.ToString & "D")
Else
constant.InitExpression = New CodePrimitiveExpression(Convert.ChangeType(boundedValue, numericType))
End If
type.Members.Add(constant)
End If
End Sub