ByRef parameter to DLL using Structure - Access Violation - vb.net

UPDATE: Thanks for everyones help. I had no idea about Marshaling my Strings and after doing so everything is now working correctly. I have edited my code below for others who may find this issue
I am currently working on porting some VB6 code to .net
the vb6 application is using a precompiled .dll (made in C++ I beleive) and I do not have any access to its source code.
When googling the function name I get only one google result which DOES have information on its return value and its parameters and I believe I have declared the .DLL correctly
http://jelleybee.com/fun/vgames/emulate/snes/setup/super%20jukebox/Uematsu.html
.DLL function declaration
Declare Function Uematsu_LoadID666Tag Lib "uematsu.dll" (ByVal lpszFileName As String, ByRef lpTag As ID666_tag) As Boolean
I have defined my structure like this
Public Structure ID666_tag
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=256)> Public Song As String 'Title of song
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=256)> Public Game As String 'Name of game
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=256)> Public Artist As String 'Name of artist
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=256)> Public Dumper As String 'Name of dumper
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=256)> Public Dated As String 'Date dumped
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=256)> Public Emu As String 'Emulator used to dump
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=256)> Public Comment As String 'Optional comment
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=256)> Public OST As String 'Origonal soundtrak title
Public SongLength As Integer 'Length of song
Public LoopLength As Integer 'Length of the loop (extended ID666)
Public EndLength As Integer 'Length of end (extended ID666)
Public FadeLength As Integer 'length of fade out
Public OSTDiscNum As Byte 'Origonal sound track disc number
Public OSTTrack As Short 'Original sound track track number
Public Publisher As String 'Name Of Publisher
Public Copyright As Short 'Date of Copyright
Public Mute As Byte 'Channels to mute
End Structure
I am using the function like this
Function Extract_ID666(Fname As String) As ID666_tag
Dim TempExtr As ID666_tag
If Uematsu_LoadID666Tag(Fname, TempExtr) = True Then
MessageBox.Show("DONE")
Else
MessageBox.Show("FAIL")
End If
End Function
However when ever I run my Extract_ID666 function I receive an access violation error.
I know this has something to do with the way I setup to use TempExtr or how I have declared my .dll function. But I can not track it down.
Any ideas or solutions to this problem would be much appreciated.
I have searched SO for a long time trying to find a similar question but could not find a solution.

Access Violations are usually caused by an external .DLL trying to write to memory which is not designated. Here is an SO question with great detail on Access Violations
Common causes of - Access Violation errors under .NET
Marshaling is the process of converting a data field, or an entire set of related structures, into a serialized string that can be sent in a message. Here is an SO question with more information on Marshaling
What is marshalling? What is happening when something is "marshalled?"
Here is MSDN information on Marshaling Strings
http://msdn.microsoft.com/en-us/library/s9ts558h%28v=vs.71%29.aspx

Related

Improve class design – there is a function that should not be callable

Today, I thought to myself that I would quickly create a descriptive example of the access modifiers Public, Private and Protected for someone, taken from real life.
The following example: A caretaker can spend a budget provided by the landlord himself without having to call for every little thing. For moderate repairs, however, he must call the landlord or the landlord's son and ask for it. The son then knows what money he may spend. Big repairs can only be decided by the landlord (not the son).
I transferred that to source code.
I want to improve the class design, because I can still write Tom.DecideMajorRepair (" ") in Form1.vb, which should not work and what I find unclean.
Form1.vb
Public NotInheritable Class FormMain
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim Hans As New ClassVermieter ' Landlord
Dim Tom As New ClassVermietersSohn ' Landlord's son
'easily accessible
Dim MyBudget As UInt16 = Hans.Caretakers_budget_for_minor_repairs - 200US
Dim Budget_received_1 As UInt16 = Hans.DecideModerateRepair("Unfortunately, the heating is broken!")
' Call son because father didn't answer the phone (or whatever). He can also decide that (protected).
' I still have to ask permission from one of them.
Dim Budget_received_2 As UInt16 = Tom.DecideForHimselfModerateRepair("Unfortunately, the heating is broken!")
' Son cannot decide a major repair - only the landlord.
Dim Budget_received_3 As UInt16 = Tom.DecideForHimselfLargeRepair("Unfortunately, the heating is broken!")
Dim Budget_received_4 As UInt16 = Hans.DecideMajorRepair("Unfortunately, the heating is broken!")
End Sub
End Class
ClassVermieter.vb (landlord)
Public Class ClassVermieter
''' <summary>
''' per month
''' </summary>
Public Property Caretakers_budget_for_minor_repairs As UInt16 = 500US
'–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
Protected Property Permission_for_medium_repairs As UShort
'–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
Private Permission_for_large_repairs As UInt16
'answers the phone call from the caretaker
Public Function DecideModerateRepair(ByVal Message_from_the_caretaker As String) As UInt16
Permission_for_medium_repairs = 1000US
Return Permission_for_medium_repairs
End Function
'answers the phone call from the caretaker
Public Function DecideMajorRepair(ByVal Message_from_the_caretaker As String) As UInt16
Permission_for_large_repairs = 5000US
Return Permission_for_large_repairs
End Function
End Class
ClassVermietersSohn.vb (Landlord's son)
Public Class ClassVermietersSohn : Inherits ClassVermieter
Private ReadOnly zero As UInt16 = 0US
'answers the phone call from the caretaker
'He can decide for himself whether a moderate repair should be carried out.
Public Function DecideForHimselfModerateRepair(ByVal Message_from_the_caretaker As String) As UInt16
Permission_for_medium_repairs = 1000US
Return Permission_for_medium_repairs
End Function
'answers the phone call from the caretaker
Public Function DecideForHimselfLargeRepair(ByVal Message_from_the_caretaker As String) As UInt16
Return zero 'Well, that was nothing, because the son cannot spend (not see) the large amounts!
End Function
End Class
This code is kept elementarily. I am aware that money is not handled with Uint16. The first thing I wanted to do was build the structure.
The difficulty that kept me from solving it myself was that I didn't know how to change the Get and Set without an error message.
In terms of the domain, I would not put the burden of knowing "whom" to ask on the caretaker. Ideally, the caretaker should have a single point of contact which can internally route the request and return true/false.
One design decision is how to parameterise the repair request.
This is most easily done by using an enum RepairTypes with a list of possible repair requests.
Then, I would have an interface "RepairApprover" with a method "boolean request(RepairType rt) throws NotSupportedException" - the exception would be used to "go up the chain"
Next, I would either have a LandlordAssistant class implement RepairApprover,
and a separate Landlord class also implementing RepairApprover.
Internally, the Landlord class would have a private field for LandlordAssistant so that it could reuse the code for minor and medium repairs.
I would then make a "ApprovalHierarchy" class with method "RepairApproval getMyManager()"
I would inject the LandlordAssistant as a "RepairApprover" instance into the Caretaker class constructor.
Internally, the LandlordAssistant.request method would try to respond to a small or medium request. If the request type is MAJOR, it would call ApprovalHierarchy.getMyManager(this).request(..) thus passing the request up the chain.
Hope that helps?

VB6 how to consume a VB.NET array of Objects?

I have a VB.NET Dll, registered as COM interoperability, that exposes something like this:
Class Society
with a:
Property ListPersons As Person()
This is the VB.NET Code:
Public Class Society
...
<System.Xml.Serialization.XmlArrayItemAttribute("Person", Form:=System.Xml.Schema.XmlSchemaForm.Unqualified)> _
Public Property ListPersons() As Person()
Get
Return Me.ListPersonsField
End Get
Set
Me.ListPersonsField = value
Me.RaisePropertyChanged("ListPersons")
End Set
End Property
I have to fill that list with VB6 but I cannot find the way
I have struggled with this issue a lot in the past and to be honest I could not find a solution to pass an array of object.
One of the solutions I used in the past was to pass the data of the single object as parameters and then create the object in the .net DLL and add it to your list.
Example
<ServiceContract()>
Public Interface IPersonAdd
<OperationContract()>
Function AddPerson(ByVal id As Integer, ByVal value As Integer) As Boolean
End Interface
Public Function AddPerson(ByVal id As Integer, ByVal value As Integer)
Dim p as new Person(id, value)
ListPersons.Add(p)
End Function

Send String Msg between 2 .net applications (VB.NET)

I want to send a string Message from .net Application A and Receive the message from the Application B and here's the code:
-------- Application A
Private Const RF_TESTMESSAGE As Integer = &HA123
Public Structure MyData
Public M As String
Public I As Integer
End Structure
Public Function SendTest()
Dim Data As New MyData
Data.M = "QWERTY"
Data.I = 15
Dim P As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(Data))
Marshal.StructureToPtr(Data, P, False)
Dim Hdl As New IntPtr(11111) 'While 11111 is the WndHD for the application B for testing
SendMessage(Hdl, RF_TESTMESSAGE, IntPtr.Zero, P)
End Function
------- Application B
Private Const RF_TESTMESSAGE As Integer = &HA123
Public Structure MyData
Public M As String
Public I As Integer
End Structure
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
If m.Msg = RF_TESTMESSAGE Then
Dim A = DirectCast(m.GetLParam(GetType(MyData)), MyData)
MsgBox(A.M)
MsgBox(A.I)
Marshal.FreeHGlobal(m.LParam)
End If
MyBase.WndProc(m)
End Sub
The application B always receive the message, but unable to convert the point lParam to valid MyData Structure, and sometime raise access violation, sometime no error ....
Please advice.
The problem is that you're not properly marshalling the data between the two applications. You are allocating memory in one application, and then passing a pointer to that memory to a second application. But because application's have private memory spaces and cannot read each other's memory, that pointer is useless to the second application.
Depending on what it points to in that application's memory space, it might be triggering an access violation, or just not working properly.
Perhaps you're confused by the naming of the AllocHGlobal and FreeHGlobal functions. Contrary to what first impressions might suggest, they do not actually allocate and free global memory. At least not memory that is globally accessible to all processes running on a machine. The name comes from the Windows HGLOBAL data type, which used to mean exactly this back in the days of 16-bit Windows, where all applications did share a common memory space and could read each others' memory. But that is no longer the case in modern 32-bit Windows. The names were retained for backwards compatibility reasons. HGLOBAL and HLOCAL mean effectively the same thing nowadays. More on the nitty gritty details is available on MSDN. But that's mostly a curiosity. You don't need to know and understand all of it to get the code working correctly.
The point is that all AllocHGlobal does is allocate memory from the process's default heap, readable only to that process. Hence the need to marshal the memory across processes, making it accessible from the other one receiving the message. Doing this manually is, of course, an option. But not a very good one. It's tricky to get right, and there's little point. Like Tim's comment hints at, the easier option is to use the WM_COPYDATA message, which does the marshalling for you. When you use this message, the data you want to share is packaged in a COPYDATASTRUCT structure.
You can keep most of your existing code to allocate memory, you just need to replace your custom RF_TESTMESSAGE window message with WM_COPYDATA. Sample code, including the necessary structure definition, is available over on the pinvoke website.
Something like this (warning—untested and uncompiled):
Private Const WM_COPYDATA As Integer = &H004A
<StructLayout(LayoutKind.Sequential)> _
Public Structure COPYDATASTRUCT
Public dwData As IntPtr
Public cdData As Integer
Public lpData As IntPtr
End Structure
<StructLayout(LayoutKind.Sequential)> _
Public Structure MyData
Public M As String
Public I As Integer
End Structure
Public Function SendTest()
' Create your data structure, MyData, and fill it.
Dim data As New MyData
data.M = "QWERTY"
data.I = 15
Dim pData As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(data))
Marshal.StructureToPtr(data, pData, False)
' Create the COPYDATASTRUCT you'll use to shuttle the data.
Dim copy As New COPYDATASTRUCT
copy.dwData = IntPtr.Zero
copy.lpData = pData
copy.cbData = Marshal.SizeOf(data)
Dim pCopy As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(copy))
Marshal.StructureToPtr(copy, pCopy, False)
' Send the message to the other application.
SendMessage(New IntPtr(11111), WM_COPYDATA, IntPtr.Zero, pCopy)
' Free the memory we allocated
' (This works because SendMessage is synchronous, and does not
' return until the other application has finished processing
' the data that you have sent it. That also means that the
' other application should not and cannot free the memory.
' If it needs it after processing the message, it needs to
' make a local copy.)
Marshal.FreeHGlobal(pCopy)
Marshal.FreeHGlobal(pData)
End Function
If you decide not to go the easy route using WM_COPYDATA and instead marshal the data yourself, you need to make sure to call the RegisterWindowMessage function (if you are not doing so already in code I can't see) to ensure that the ID of your custom window message is unique.

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

VB.NET: How to reference VB.NET module?

I got a Utility module since VB.NET doesn't have static class like C# and Module is the static class in VB.NET. The reason that I use module is because I'm using the Extension method and it can only be use in Module.
I can't reference to this module but if I put my code in a class. I can reference to it without any problem. What could be the reason? I missed C#.
Edit: The module is inside a class library call Utility.
You need to mark the module as Public Module.
I can't reference to this module but if i put my code in a class. I can reference to it without any problem. Does anyone know why?
Because Modules in VB aren't classes and can't be used to instantiate objects. Rather, they're something similar to namespaces, with the difference that namespaces can't contain functions directly. So the reason for modules is to provide a way to group functions logically that don't belong to a class.
This makes a lot of sense when you consider that not everything logically belongs to a class. Consider System.Math. There is absolutely no reason to make that a class, other than a weird OOP purism.
By the way, you can't reference static classes in C# either, at least not if I understand correctly what you mean by “reference”. Perhaps you can clarify this.
.NET compilers can take any type of language syntax and turn it into a .NET equivalent. Sometimes there is a one for one correspondence other times there isn't.
By using the .NET Reflector you can see what the compiler is really doing.
In VB.NET the module exists because of the heritage inherited from Visual BASIC and partly from Microsoft BASIC.
The VB.NET compiler will take this
Public Module CoreModule
Dim R As New System.Random(CInt(Microsoft.VisualBasic.Timer))
Public Function D(ByVal Roll As Integer) As Integer
Return R.Next(0, Roll) + 1
End Function
Public Function _1D6() As Integer
Return D(6)
End Function
Public Function _2D6() As Integer
Return D(6) + D(6)
End Function
Public Function _3D6() As Integer
Return D(6) + D(6) + D(6)
End Function
Public Function _4D6() As Integer
Return D(6) + D(6) + D(6) + D(6)
End Function
Public Function CRLF() As String
Return Microsoft.VisualBasic.ControlChars.CrLf
End Function
End Module
And turn it into this (code left out for brevity)
Public NotInheritable Class CoreModule
' Methods
Shared Sub New()
Public Shared Function _1D6() As Integer
Public Shared Function _2D6() As Integer
Public Shared Function _3D6() As Integer
Public Shared Function _4D6() As Integer
Public Shared Function CRLF() As String
Public Shared Function D(ByVal Roll As Integer) As Integer
' Fields
Private Shared R As Random
End Class
In C# the equivalent is this
public sealed class CoreModule
{
// Fields
private static Random R;
// Methods
static CoreModule();
public static int _1D6();
public static int _2D6();
public static int _3D6();
public static int _4D6();
public static string CRLF();
public static int D(int Roll);
}
All that matter is that the emitted CIL does the job correctly.
This capability is the main reason why so many older Visual BASIC 6 programmers are highly annoyed at MS's changes to the language. For example the keyword Integer emitting a Int32 instead of a Int16.
Modules are exposed to other assemblies referencing the original assembly as long as the module is declared public.
Maybe the methods/subs aren't public? I had that problem once, and it would allow access local code in your class, but not if it was outside your class and marked "Private".
Imports System.Web
Imports System.Web.UI
Module ResponseHelper
<System.Runtime.CompilerServices.Extension()> _
Public Sub Redirect(ByVal response As Net.HttpWebResponse, _
ByVal url As String, ByVal target As String, _
ByVal windowFeatures As String)
If String.IsNullOrEmpty(target) Or _
target.Equals("_self", StringComparison.OrdinalIgnoreCase) And _
String.IsNullOrEmpty(windowFeatures) Then
response.Redirect(url, target, windowFeatures)
Else
Dim page As Page = CType(HttpContext.Current.Handler, Page)
If page Is Nothing Then
Throw New InvalidOperationException("Cannot redirect to new window outside Page context.")
End If
url = page.ResolveClientUrl(url)
Dim script As String
If String.IsNullOrEmpty(windowFeatures) Then
script = "window.open(""{0}"", ""{1}"", ""{2}"";"
Else
script = "window.open(""{0}"", ""{1}"");"
End If
script = String.Format(script, url, target, windowFeatures)
ScriptManager.RegisterStartupScript(page, GetType(Page), "Redirect", script, True)
End If
End Sub
End Module
I don't understand what you are asking.
VB.NET does have static classes, just like in C#, because in VB.NET a Module IS a static class. They are one and the same. Anything you can do with a static class you can do with a Module. Perhaps you haven't marked your Public/Private/Protected access correctly?