I am working on retrofitting an application written in VB.NET. It is a simple program that uses 0.5GB (!) of RAM, because of a large amount (30+) of globally defined arrays, like:
Public Temp(1000000) As Double
Public ThisIsAnotherVariable(5000, 10) As String
Public ThisIsAVeryLargeArray(64, 50000) As Double
Most of the time, these large "buffers" are barely used, so I would like to convert them to use something from Collections.Generic. Is there any semi-transparent way to convert these? Or a bit of trickery to get the CLR to only allocate the used segments?
If these are "sparse" arrays, i.e., if almost all array entries are entries, the simplest solution might be to replace them with dictionaries:
Public Temp(1000000) As Double ' Old
Public Temp As New Dictionary(Of Int32, Double)() ' New
Assignment would be source-code compatible:
Temp(10) = 2.0 ' Works for arrays and dictionaries
Reading would be source-code compatible, if the value is present:
Dim x = Temp(3) ' Works for arrays and dictionary, if Temp(3) has been assigned
However, accessing values which have not been assigned will yield a KeyNotFoundException. If you need that, you'd have to use a "default value dictionary" instead of a regular dictionary. Unfortunately, no such dictionary is built-in in the BCL, but there are others who have already looked at that problem and shared their implementation. If you want to use a plain .NET dictionary, you will need to replace every read access with a method call.
Related
I currently have functioning code that creates and modifies Nuance PDF objects contained in a Hashtable, where the hashkey is the output stream (a string value). Unfortunately, when each PDF gets over 10 MB or so, merging additional PDFs into it slows the program to a crawl.
I'd like to add a dimension to my Hashtable using an additional integer index so I can simply open a fresh PDF whenever the number of merged documents exceeds a value from my config file.
When I initialize the nested Hashtable, all appears fine at first. I'm able to assign the Nuance PDF objects to it. However, when I attempt to access one of the PDF elements, TryCast fails (returns nothing).
Here's how I set up the first instance of OutStream1 (OutStream1 is just a hard-coded example for simplification - the actual output streams are read from the config file).
My goal is to continue adding new PDF objects to the inner Hashtable as needed when the threshold is reached.
Dim pdfOutput As Hashtable = New Hashtable
Dim outTemp As Hashtable = New Hashtable
outTemp(0) = CreateObject("NuancePDF.DDDoc")
pdfOutput("OutStream1") = outTemp(0)
When ready to manipulate the PDF object, the assignment to pdMrgDoc fails. Without using TryCast, the error message is:
Unable to cast COM object of type 'System.__ComObject' to class type 'System.Collections.Hashtable'. Instances of types that represent COM components cannot be cast to types that do not represent COM components; however they can be cast to interfaces as long as the underlying COM component supports QueryInterface calls for the IID of the interface.
Dim pdMrgStream As Hashtable = pdfOutput("OutStream1")
Dim pdMrgDoc As New Object
pdMrgDoc = pdMrgStream(0)
I'm not sure what that error message means. Is there a way I can accomplish this?
I'd recommend to use typed containers instead of the legacy untyped Hashtable.
I haven't tested this, but it ought to work as you intend:
Dim pdfOutput As New Dictionary(Of String, Dictionary(Of Integer, Object))
Dim entry as New Dictionary(Of Integer, Object)
entry.Add(0, CreateObject("NuancePDF.DDDoc"))
pdfOutput.Add("OutStream1", entry)
Then, the compiler will catch basic errors due to compile-time type errors, instead of waiting to fail until a cast fails at runtime.
You might also want to consider encapsulating the behavior in a class instead of storing a dictionary and managing the behavior outside. It should result in cleaner, easier-to-maintain code.
I would like to populate a listbox with a list of installed printers in VB.net.
This works:
Dim printerList As System.Drawing.Printing.PrinterSettings.StringCollection
printerList = System.Drawing.Printing.PrinterSettings.InstalledPrinters
For Each printerName In printerList
ListBox1.Items.Add(printerName)
Next
This does not work:
ListBox1.Items.AddRange(printerList)
...because of the following type-conversion error:
Public Sub AddRange (value As
System.Windows.Forms.ListBox.ObjectCollection)': Value of type
'System.Drawing.Printing.PrinterSettings.StringCollection' cannot be
converted to 'System.Windows.Forms.ListBox.ObjectCollection'.
Is it possible to directly cast one to the other for use in AddRange() as shown? Or is the loop the only (or most efficient) way?
Well, you're dealing with 2 collections that were created before the more modern generic lists and enumerables, so their use is less fluid.
In this case, the AddRange method accepts either another ObjectCollection instance (not your case), or an array of Objects. If you want to benefit from the latter, you'll need to transform the StringCollection instance to an array of Objects. Here is how this can be done:
ListBox1.Items.AddRange(printerList.Cast(Of Object)().ToArray())
That said, I would stick with your current For Each loop. It is very readable, and doesn't create an intermediate array. But, I doubt either choice will make much difference, so pick your favorite.
I'm looking for a quick way to serialize custom structures consisting of basic value types and strings.
Using C++CLI to pin the pointer of the structure instance and destination array and then memcpy the data over is working quite well for all the value types. However, if I include any reference types such as string then all I get is the reference address.
Expected as much since otherwise it would be impossible for the structure to have a fixed.. structure. I figured that maybe, if I make the string fixed size, it might place it inside the structure though. Adding < VBFixedString(256) > to the string declaration did not achieve that.
Is there anything else that would place the actual data inside the structure?
Pinning a managed object and memcpy-ing the content will never give you what you want. Any managed object, be it String, a character array, or anything else will show up as a reference, and you'll just get a memory location.
If I read between the lines, it sounds like you need to call some C or C++ (not C++/CLI) code, and pass it a C struct that looks similar to this:
struct UnmanagedFoo
{
int a_number;
char a_string[256];
};
If that's the case, then I'd solve this by setting up the automatic marshaling to handle this for you. Here's how you'd define that struct so that it marshals properly. (I'm using C# syntax here, but it should be an easy conversion to VB.net syntax.)
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public struct ManagedFoo
{
public int a_number;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=256)]
public string a_string;
}
Explanation:
StructLayout(LayoutKind.Sequential) specifies that the fields should be in the declared order. The default LayoutKind, Auto, allows the fields to be re-ordered if the compiler wants.
CharSet=CharSet.Ansi specifies the type of strings to marshal. You can specify CharSet.Ansi to get char strings on the C++ side, or CharSet.Unicode to get wchar_t strings in C++.
MarshalAs(UnmanagedType.ByValTStr) specifies a string inline to the struct, which is what you were asking about. There are several other string types, with different semantics, see the UnmanagedType page on MSDN for descriptions.
SizeConst=256 specifies the size of the character array. Note that this specifies the number of characters (or when doing arrays, number of array elements), not the number of bytes.
Now, these marshal attributes are an instruction to the built-in marshaler in .Net, which you can call directly from your VB.Net code. To use it, call Marshal.StructureToPtr to go from the .Net object to unmanaged memory, and Marshal.PtrToStructure to go from unmanaged memory to a .Net object. MSDN has some good examples of calling those two methods, take a look at the linked pages.
Wait, what about C++/CLI? Yes, you could use C++/CLI to marshal from the .Net object to a C struct. If your structs get too complex to represent with the MarshalAs attribute, it's highly appropriate to do that. In that case, here's what you do: Declare your .Net struct like I listed above, without the MarshalAs or StructLayout. Also declare the C struct, plain and ordinary, also as listed above. When you need to switch from one to the other, copy things field by field, not a big memcpy. Yes, all the fields that are basic types (integers, doubles, etc.) will be a repetitive output.a_number = input.a_number, but that's the proper way to do it.
I use pin_ptr for cli::array types and everything works fine.
Is it possible to do the same with System::Collection::Generic::List which I believe is a contiguous block of memory?
The obvious
List<double>^ stuff = gcnew List<double>( 10 );
cli::pin_ptr<double> resultPtr = &stuff[ 0 ];
gives a compiler error "error C2102: '&' requires l-value" presumably because the indexed property returns something that is not a l-value! So is there another way to do this. I have played around with interior_ptr as well but have not found anything that works yet.
I know that I could call ToArray on the List but the whole point is to not copy stuff around.
No, this is not possible.
True, a List does use an array behind the scenes, but the [] operator is different. With an array, [] is simple pointer math, but with a List, [] is a full-fledged method call. That's why the & isn't working: you can take the address of an array location, but you can't take the address of a value returned from a method.
Think about it like this: If they wanted to, they could change the implementation of List without changing its external interface. It would be possible to change List to store the list contents in memory gzip-compressed. In that case, stuff[0] is generated on-the-fly by the [] method which does the decompression, so there is no single memory location that contains stuff[0] to pin.
Edit
Yes, internal to the List class, the contents are contiguous in memory. You can see this in the source that Microsoft has provided. However, the List class does not make that array public: The public interface to the List class is the public methods & properties, only. The public methods & properties present a contract, and the array that the values are stored in are not part of that contract. Microsoft would never do this, but they could do a gzip-compressed implementation of List, and the public contract of the List class wouldn't change. You should only write your code to the public methods & properties of a class, not to the internals that may change at any time.
I am adding a feature to an existing VB .Net application that involves retrieving data from a .Net web service. The web service returns an array of Locations. A Location is pretty simple, it has 3 properties – an integer and two strings.
So that the rest of my application does not have to be dependent on this web service, I would like to create my own Location type within my application. My thought is I could call a method that returns a generic list of my Location type which internally calls the web service and populates the list I return. That way, if the data source for Locations changes in the future to something other than the web service, I only have to fix the method instead of fixing all the callers.
So I created my own Location that has identical properties as the service Location. But I don’t seem to be able to cast the array of service locations into a generic list of my locations. I also tried casting a single service Location into one of my Locations and that didn’t work either.
So is casting an option or do I have to loop through each service Location and assign each property to a new one of my Locations? Or is there a completely different way to go about this?
By default you will not be able to cast one Location to another. They are completely unrelated types and thus cannot be the subject of casting. You can make it possible to cast though by defining a custom operator for the application version of CType.
' Location for application
Public Class Location
...
Public Shared Operator Widening CType(p1 as Namespace.Of.WebService.Location) As Location
Dim loc = ConvertWebServiceLocationToApplicationLocation
return loc
End Operator
End Class
This allows you to now do a CType operation between a WebService Location the Application Location.
Casting the array though, simply won't be possible. There is no way to define a conversion operator for arrays so they can't use the above trick. But you can write a quick and dirty function for this purpose
Public Shared Function ConvertArray(ByVal arr As Namespace.Of.WebServiec.Location()) As Location()
Dim newArray(arr.Length) As Location
For i as Integer = 0 To arr.Length - 1
newArray(i) = CType(arr(i), Location)
Next
return newArray
End Function
Casting will not work because these are not the same type. Even if two types look exactly the same, you cannot cast from one to the other, unless you define a CType operator which describes how to transform an object from one type into an object from another type.
Even then, you cannot cast a List(of Type1) into a List(Of Type2) directly.
You will have to loop through and create a new object of your class.