GdipSaveImageToFile(), pointer to a Structure/Class in VB.net - 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);

Related

BC36913: Cannot infer a common type

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)))

VBA - generating unique numbers in code

Is there any way in which I can generate a unique number in code ?
I had an idea of using system time for that, but eventually could not implement it.
You can use the Now() then format the output to a number:
Sub unique()
Dim t As Date
t = Now()
Range("A1").NumberFormat = "#"
Range("A1") = CStr(Format(t, "yyyymmddhhMMss"))
End Sub
This would be unique.
As #Vasily pointed out, without formatting the cell as string and placing the number as a sting the value gets truncated to scientific notation.
especially for such cases the GUID (Global Unique IDentifyer) was invented. It may be a little bit oversized ... but just that you have seen it:
Option Explicit
Public Type TYP_GUID
bytes(15) As Byte
End Type
Public Declare Function CoCreateGuid Lib "OLE32.dll" _
(guid As TYP_GUID) As Long
Public Function newGUID() As TYP_GUID
Dim uGUID As TYP_GUID
CoCreateGuid uGUID
newGUID = uGUID
End Function
whenever you call newGUID() you will become a value that should be really unique in world. You can try and call it as often as you want ... you will never get the same value a second time.
it's also possible to convert such GUID's to string:
Option Explicit
Public Type TYP_GUID
bytes(15) As Byte
End Type
Public Declare Function CoCreateGuid Lib "OLE32.dll" _
(guid As TYP_GUID) As Long
Public Declare Function StringFromGUID2 Lib "OLE32.dll" _
(guid As TYP_GUID, _
ByVal lpszString As String, _
ByVal iMax As Long) As Long
Public Function newGUID() As TYP_GUID
Dim uGUID As TYP_GUID
CoCreateGuid uGUID
newGUID = uGUID
End Function
Public Function newGUID_String() As String
Dim sBuffer As String
Dim lResult As Long
sBuffer = VBA.Space(78)
lResult = StringFromGUID2(newGUID, sBuffer, Len(sBuffer))
newGUID_String = Left$(StrConv(sBuffer, vbFromUnicode), lResult - 1)
End Function

Saving multiple file types into one potobuf-net'ized file in VB.net

I'm writing a program that saves 'map' files to the HD so that I can open them later and display the same data. My maps originally saved only one data type, a set of my own custom objects with the properties: id, layer, x, and y. You can see the code I did for that, here:
<ProtoContract()> _
Public Class StrippedElement
<ProtoMember(1)> _
Public Property X() As Integer
Get
Return m_X
End Get
Set(ByVal value As Integer)
m_X = value
End Set
End Property
Private m_X As Integer
<ProtoMember(2)> _
Public Property Y() As Integer
Get
Return m_Y
End Get
Set(ByVal value As Integer)
m_Y = value
End Set
End Property
Private m_Y As Integer
<ProtoMember(3)> _
Public Property Id() As Long
Get
Return m_Id
End Get
Set(ByVal value As Long)
m_Id = value
End Set
End Property
Private m_Id As Long
<ProtoMember(4)> _
Public Property Layer() As String
Get
Return m_Layer
End Get
Set(ByVal value As String)
m_Layer = value
End Set
End Property
Private m_Layer As String
End Class
Basically, I'm just serializing tons and tons of these classes into one file. Now I've come to find out that I have to save new parts of the map that aren't necessarily the same class type.
Is it possible to save multiple types to the same file and still read from it just as simply? Here is my code to write and read to/from the file:
Public Shared Sub Save(ByVal File As String, ByVal Map As RedSimEngine.Map)
Dim nPBL As New List(Of StrippedElement)
For z As Integer = 0 To Grid.LAYERLIMIT
For x As Integer = 0 To Grid.GRIDLIMIT
For y As Integer = 0 To Grid.GRIDLIMIT
Dim currentCell As GridElement = Map.Level.getCell(z, x, y)
If currentCell IsNot Nothing Then
If currentCell.Archivable Then
Dim nStEl As New StrippedElement
nStEl.Id = currentCell.getId()
nStEl.Layer = currentCell.getLayer()
nStEl.X = currentCell.X
nStEl.Y = currentCell.Y
nPBL.Add(nStEl)
End If
End If
Next
Next
Next
Serializer.Serialize(New FileStream(File, FileMode.Create), nPBL)
End Sub
Public Shared Function Load(ByVal File As String) As RedSimEngine.Map
Dim nMap As New Map
Dim nListOfSE As List(Of StrippedElement) = Serializer.Deserialize(Of List(Of StrippedElement))(New FileStream(File, FileMode.Open))
For Each elm As StrippedElement In nListOfSE
Dim nElm As GridElement = GridElement.createElementByIdAndLayer(elm.Layer, elm.Id)
nElm.X = elm.X
nElm.Y = elm.Y
nMap.Level.setCell(nElm)
Next
Return nMap
End Function
I have to add 3 or more class types to the save file, I'd rather not have it split up because then it would get confusing for my clients.
Basically, I have to add things similar to the following:
A class with X, Y, and Value
A class with Name and Value
A class with Name, ENUMVALUE, X, Y, INTEGERVALUE, and a couple other things (This one will have to contain quite a bit of data).
I'm using VB.net so all .net answers are acceptable. Thanks! If you need any clarification, just say so in the comments.
You have 3 options here:
The first option is to write a wrapper class with 3 containers:
[ProtoContract] public class MyData {
[ProtoMember(1)] public List<Foo> SomeName {get;set;} // x,y,value
[ProtoMember(2)] public List<Bar> AnotherName {get;set;} // name,value
[ProtoMember(3)] public List<Blap> ThirdName {get;set;} // etc
}
and serialize an instance of that; note, however, that the order will be lost here - i.e. after deserializing there is no difference between Foo0, Bar0, Foo1 and Foo0, Foo1, Bar0 - either will result in SomeName with Foo0 and Foo1, and AnotherName with Bar0. This option is compatible with your existing data, as internally there is no difference between serializing "a list of Foo" vs "a wrapper class with a list of Foo as field 1".
The second option is to mimic the above, but with a manual type-check during deserialization -this involves using the non-generic API and providing a delegate that maps field numbers (1,2,3) to types (Foo,Bar,Blap). This is useful for very large streams, or selectively plucking objects from the stream, as it allows you to process individual objects (or ignore them) individually. With this approach you can also build the file cumulatively rather than building an entire list. The example is a bit more complex, though, so I'd rather not add one unless it is of interest. This approach is compatible with your existing data.
The third approach is inheritance, i.e.
[ProtoContract, ProtoInclude(1, typeof(Foo))]
[ProtoInclude(2, typeof(Bar)), ProtoInclude(3, typeof(Blap))]
public class SomeBaseType {}
[ProtoContract] public class Foo : SomeBaseType { /* properties etc*/ }
[ProtoContract] public class Bar: SomeBaseType { /* properties etc*/ }
[ProtoContract] public class Blap: SomeBaseType { /* properties etc*/ }
then serialize a List<SomeBaseType> which happens to contain instances that are Foo / Bar / Blap. This is simple and convenient, and preserves order nicely; but it is not quite compatible with data serialized simply as a List<Foo> - if existing serialized data is an issue you will need to migrate between the formats.
i find "Serializer.Serialize" very unclean in this kind of cases, so here is how i would proceed :
i would write variables manually one at time!
for example, here is how i would write and read a StrippedElement :
Sub WriteStrippedElement(ByVal Stream As IO.Stream, ByVal SE As StrippedElement)
Stream.Write(BitConverter.GetBytes(SE.X), 0, 4) 'Write X:integer (4 bytes)
Stream.Write(BitConverter.GetBytes(SE.Y), 0, 4) 'Write Y:integer (4 bytes)
Stream.Write(BitConverter.GetBytes(SE.Id), 0, 8) 'Write Id:Long (8 bytes)
Dim LayerBuffer() As Byte = System.Text.Encoding.Default.GetBytes(SE.Layer) 'Converting String To Bytes
Stream.Write(BitConverter.GetBytes(LayerBuffer.Length), 0, 4) 'Write The length of layer, since it can't have a fixed size:integer (4 bytes)
Stream.Write(LayerBuffer, 0, LayerBuffer.Length) 'Write The Layer Data
Stream.Flush() 'We're Done :)
End Sub
Sub ReadStrippedElement(ByVal Stream As IO.Stream, ByRef SE As StrippedElement)
Dim BinRead As New IO.BinaryReader(Stream) 'Making reading Easier, We can also use a BinaryWriter in the WriteStrippedElement For example
SE.X = BinRead.ReadInt32 'Read 4 Bytes
SE.Y = BinRead.ReadInt32 'Read 4 Bytes
SE.Id = BinRead.ReadInt64 'Read 8 Bytes
Dim Length As Integer = BinRead.ReadInt32 'Read 4 Bytes, the length of Layer
Dim LayerBuffer() As Byte = BinRead.ReadBytes(Length) 'Read Layer Bytes
SE.Layer = System.Text.Encoding.Default.GetString(LayerBuffer) 'Convert Back To String, and done.
End Sub
So if you like to write alot of those StrippedElement, just write down the number of elements (int32, 4bytes) to know how much to read from the file next time.

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

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();