callback function from unmanaged dll in VB .NET - vb.net

I'm trying to use an unmanaged dll in VB.NET. The example source code provided with the dll is in VB6 and below is my attempt to convert it to .NET. When the dll tries to do a callback I get a "Attempted to read or write protected memory" exception. I really don't care about the callback function getting actually called.
My code:
<DllImport("AlertMan.dll")> _
Public Shared Function AlertManC( _
ByVal CallbackAddr As AlertManCallbackDel) As Long
End Function
Public Delegate Sub AlertManCallbackDel(ByVal data As Long)
Public Sub AlertManCallback(ByVal data As Long)
End Sub
Public mydel As New AlertManCallbackDel(AddressOf AlertManCallback)
'protected memeory exception here
Dim IStat as Long = AlertManC(mydel)
Original VB6 example code:
Declare Function AlertManC _
Lib "AlertMan.dll" _
Alias "AlertManC" (ByVal CallbackAddr As Long) As Long
Private Sub AlertManCallback(ByVal data As Long)
End Sub
' calling code
Dim IStat As Long
IStat = AlertManC(AddressOf AlertManCallBack)
Original dll header
typedef void TACBFUNC(char *);
int AlertManC(TACBFUNC *WriteCaller cHANDLEPARM);

Can you post the original native definiton for AlertManC?
My guess though is that the data parameter of the callback function is actually an Integer vs. a Long. In VB6 I believe Long's were actually only 32 bits vs. VB.Net where they are 64 bit. Try this
<DllImport("AlertMan.dll")> _
Public Shared Function AlertManC(ByVal CallbackAddr As AlertManCallbackDel) As Long
End Function
Public Delegate Sub AlertManCallbackDel(ByVal data As IntPtr)
Public Sub AlertManCallback(ByVal data As IntPtr)
End Sub
Edit
I updated the code based on the native signature you posted. Can you try this out?

Your callback should look like this:
Public Delegate Sub AlertManCallbackDel(ByRef data As Byte)
The reason for this being that you are passing a single-byte value by reference.
As for the declaration of the unmanaged function, it should look like this:
<DllImport("AlertMan.dll")> _
Public Shared Function AlertManC( _
ByVal CallbackAddr As AlertManCallbackDel) As Integer
End Function
Note that the return type is an Integer, which in VB.NET is a 32-bit value. In VB6, a Long was a 32-bit value, hence the need for a change in VB.NET.
The callback definition is important to get right as well, btw.

If the callback's calling convention is cdecl, you cant do that directly in C# or VB.NET.
You will have to modify the IL of the delegate to behave correctly.
You can search on CodeProject for an in-depth article.
Update:
I guess not the correct answer :) But will leave my response.

Related

OpenXML IsolatedStorage threading

According to this article OpenXML is not thread safe when it's "MemoryStreams reach the high water mark" and has to switch to IsolatedStorage.
This occurs on workbooks smaller than even 1mb because the UNCOMPRESSED data is likely 10x that size.
I really need to create xlsx files concurrently, in particular, abnormally large ones with concurrency. The MS solution is to implement something like the following(converted from C#), but I'm not sure what to do with this.
Public Class PackagePartStream
Private _stream As Stream
Private Shared _m As New Mutex(False)
Public Sub New(ByVal Stream As Stream)
_stream = Stream
End Sub
Public Function Read(ByVal buffer() As Byte, ByVal offset As Integer, ByVal count As Integer) As Integer
Return _stream.Read(buffer, offset, count)
End Function
Public Sub Write(ByVal buffer() As Byte, ByVal offset As Integer, ByVal count As Integer)
_m.WaitOne(Timeout.Infinite, False)
_stream.Write(buffer, offset, count)
_m.ReleaseMutex()
End Sub
Public Sub Flush()
_m.WaitOne(Timeout.Infinite, False)
_stream.Flush()
_m.ReleaseMutex()
End Sub
End Class
My best guess so far would be something like this but I have a feeling I'm over simplifying this and the mutex needs to be closer to the function handling OpenXML's WriteElement
Dim stream As New PackagePartStream()
Using document As SpreadsheetDocument = SpreadsheetDocument.Create(stream, SpreadsheetDocumentType.Workbook, True)
WriteExcelFile(ds, document)
End Using
I haven't done much threading in .NET but hopefully i can be pointed in the right direction by someone.
I hate to answer my own question but don't use the OpenXML SDK if you're building dynamic reports on a webserver. Use EPplus instead. It uses an alternative packaging library that does not use IsolagedStorage. It's a shame Microsoft hasn't addressed this issue.

Can't get pchar from Delphi 2007 DLL to Visual Basisc application (got garbage)

VB code
I have DLL function decalration in my VB application code:
Declare Function TstCharReturn Lib "myLib" Alias "TstCharReturn" (ByVal c As System.Text.StringBuilder) As Boolean
This is code that calls that function
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim s As String
Dim builder As New System.Text.StringBuilder
r = TstCharReturn(builder)
LogIt(s)
LogIt(r)
End Sub
I got recomendation to use StringBuilder instead of string because String is immutable, but both works the same way.
Delphi Dll code:
Function TstCharReturn (var c: pchar) : Boolean; stdcall;
var
BuffSize: Integer;
sOut: string;
begin
sOut:='abcdefghijklmnoprst';
BuffSize:=SizeOf(Char)*(Length(sOut)+1);
getmem(c, BuffSize);
FillChar(c^,BuffSize,0);
Result := Length(sOut)>0;
if Result then
begin
Move(sOut[1], PChar(c)^, BuffSize);
end;
end;
I got garbage in VB output. What is a problem?
And one more question. If I used GetMem, must I free memory somewhere or VB will do that? Is there any difference between VB6 and VB2010, because I need my dll to work with both?
Your delphi procedure is wrong. You must not allocate new memory for the returned string, you already have a pointer to it passed in c, and you must fill starting from that pointer. You will need to introduce another parameter to pass the size of that buffer.
So you will have:
Declare Function TstCharReturn Lib "myLib" Alias "TstCharReturn" _
(ByVal c As System.Text.StringBuilder, _
ByVal cchSize As Integer) As Boolean
And:
Dim sb As New System.Text.StringBuilder(50) 'Buffer capacity: 50
TstCharReturn(sb, sb.Capacity)
Don't think that StringBuilder will be magically passed to unmanaged code as something that can accept arbitrary amount of data. You need to ensure initial capacity, which is the size available for the procedure.

Managed method for SetParent() on form

How can I show a form as a child of a window that isn't in my program?
I have a window handle to what should be the parent, but I don't see any managed method for SetParent() on a form. Is there one? It also seems that the form.Show() method only accepts managed objects implementing IWin32Window.
If there isn't a managed method, what is the preferred method for declaring the API for maximum compatibility with future systems? Like this?:
<DllImport("user32.dll")> _
Private Shared Function SetParent(hWndChild As IntPtr, hWndNewParent As IntPtr) As IntPtr
End Function
Is it possible to build a class that implements IWin32Window and somehow wraps up a window? It would be handy do something like this, but I am not familiar with IWin32Window:
frmMyForm.Show(New NativeWindowWrapper(12345)) 'Where 12345 is the hWnd of the window I want to wrap
Oh wow, I just found the documentation on IWin32Window, and see that it is only one property... Handle. Yes, then of course I can easily make this NativeWindowWrapper class...
I haven't tested it yet, but I am sure it will work just fine...
Public Class NativeWindowWrapper
Implements IWin32Window
Private _Handle As IntPtr
Public ReadOnly Property Handle As System.IntPtr Implements System.Windows.Forms.IWin32Window.Handle
Get
Return _Handle
End Get
End Property
Public Sub New(ByVal Handle As IntPtr)
Me._Handle = Handle
End Sub
End Class

Using Double.NaN in optional parameter on interface

I have come across something confusing or potentially a bug in Visual Studio 2010 when defining an interface in my VB application: When defining an interface method with a default parameter of type Double, using the Double.NaN constant as the default value causes the code editor/intellisense/precompiler some issues.
The following code underlines "INaNTest" and "INaNTest.DoSomething" claiming that 'DoSomething' cannot implement 'DoSomething' because there is no matching sub on interface 'INaNTest':
Public Class NaNTest
Implements INaNTest
Public Sub DoSomething(ByVal x As Double,
Optional ByVal a As Double = Double.NaN)
Implements INaNTest.DoSomething
End Sub
End Class
Public Interface INaNTest
Sub DoSomething(ByVal x As Double,
Optional ByVal a As Double = Double.NaN)
End Interface
Removing the implementation and starting from:
Public Class NaNTest
Implements INaNTest
End Class
Public Interface INaNTest
Sub DoSomething(ByVal x As Double,
Optional ByVal a As Double = Double.NaN)
End Interface
where now "NaNTest" is underlined (Class 'NaNTest' must ...), hitting the return key at the end of the line "Implements INaNTest" (i.e. automatically insert implementation) adds the implementation:
Public Sub DoSomething(ByVal x As Double,
Optional ByVal a As Double = -1.#IND)
Implements INaNTest.DoSomething
End Sub
in which the code editor then underlines '#' (Identifier expected.). Thus the code automatically added code that is not right.
Alternatively now, starting with the original code above, using the Error Correction Options button on the underlined "INaNTest.DoSomething" and selecting 'Generate method stub for 'DoSomething' in 'INaNTest', the added method stub is:
Sub DoSomething(ByVal x As Double,
Optional ByVal a As Double = NaN)
where now "NaN" has been divorced from the "Double." prefix and underlined ('NaN' is not declared. It may be inaccessible due to its protection level.) The code editor has inserted invalid code again.
Is there a correct solution to using Double.NaN as the default value for a method as defined on an interface, in VB.net, or is there a fundamental reason why this is impossible?
Many thanks,
JCollins
Ugh, that's fugly. Hard to characterize this as anything other than a bug. The default formatting for NaN when you let the IDE generate the method signature shows what language the VB.NET team uses, that's the way the C++ runtime library formats NaN. Attempts to convince it you know what you are doing are indeed futile, afaict.
You can report this at connect.microsoft.com. While you wait for the 'fixed in the next version of Visual Studio' to see the light of day, you might consider using nullable types as the workaround:
Public Class NaNTest
Implements INaNTest
Public Sub DoSomething(ByVal x As Double, Optional ByVal a As Double? = Nothing) Implements INaNTest.DoSomething
If a.HasValue Then
'' etc..
End If
End Sub
End Class
Public Interface INaNTest
Sub DoSomething(ByVal x As Double,
Optional ByVal a As Double? = Nothing)
End Interface
Fwiw, it does work when you use Double.Epsilon as the default value. Kinda silly but not an entirely unreasonable workaround either. Just don't let the IDE generate the implementation, then it gets silly.

User Defined Type (UDT) as parameter in public Sub in class module (VB6)

I've tried to solve this problem, but can't find any solution. I have a UDT defined in a normal module, and wanted to use it as parameter in a Public Sub in a Class Module. I then get a compile error:
Only public user defined types defined in public object modules can be used as parameters or return type for public procedures of class modules or as fields of public user defined types
I then try to move my UDT in the class, declared as Private. I get this compile error:
Private Enum and user defined types cannot be used as parameters or return types for public procedures, public data members, or fields of public user defined types.
I finaly try to declare it as Public in the class, and get this compile error:
Cannot define a Public user-defined type within a private object module.
So is there any way to have a public UDT used as a parameter in a public sub in a class?
Just define the sub as Friend scope. This compiles fine for me in a VB6 class.
Private Type testtype
x As String
End Type
Friend Sub testmethod(y As testtype)
End Sub
From your error messages it appears your class is private. If you do want your class to be public - i.e. you are making an ActiveX exe or DLL and you want clients to be able to access the sub - then just make both the type and the sub Public.
So is there any way to have a public
UDT used as a parameter in a public
sub in a class?
In a word, no. The closest you can come with just Classic VB code would be to create a class that replicates the UDT and use that instead. There are definitely advantages there, but you're hosed if you need to pass that to, say, an API as well.
Another option is to define the UDT in a typelib. If you do that, it can be used as a parameter for a public method.
Ok, here's how to do it, if I can get my cat to leave me alone, that is.
In Form1 (with one command button on it):
Option Explicit
'
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal dst As Long, ByVal src As Long, ByVal nBytes As Long)
'
Private Sub Command1_Click()
' Okay, this is what won't work in VB6:
' Dim MyUdt1 As MyUdtType ' Declare a variable with a publicly defined UDT (no problem).
' Form2.Show ' We could have created some object with a class. This was just easier for the demo.
' INSIDE OF FORM2:
' Public Sub MySub(MyUdt2 As MyUdtType) ' It won't even let you compile this.
' Msgbox MyUdt2.l
' MyUdt2.l = 5
' End Sub
' Form2.MySub MyUdt1 ' You'll never get this far.
' Unload Form2
' Msgbox MyUdt1.l
'
' The following is a way to get it done:
'
Dim MyUdt1 As MyUdtType ' Declare a variable with a publicly defined UDT (no problem).
Dim ReturnUdtPtr As Long ' Declare a variable for a return pointer.
MyUdt1.l = 3 ' Give the variable of our UDT some value.
Form2.Show ' Create our other object.
'
' Now we're ready to call our procedure in the object.
' This is all we really wanted to do all along.
' Notice that the VarPtr of the UDT is passed and not the actual UDT.
' This allows us to circumvent the no passing of UDTs to objects.
ReturnUdtPtr = Form2.MyFunction(VarPtr(MyUdt1))
'
' If we don't want anything back, we could have just used a SUB procedure.
' However, I wanted to give an example of how to go both directions.
' All of this would be exactly the same even if we had started out in a module (BAS).
CopyMemory VarPtr(MyUdt1), ReturnUdtPtr, Len(MyUdt1)
'
' We can now kill our other object (Unload Form2).
' We probably shouldn't kill it until we've copied our UDT data
' because the lifetime of our UDT will be technically ended when we do.
Unload Form2 ' Kill the other object. We're done with it.
MsgBox MyUdt1.l ' Make sure we got the UDT data back.
End Sub
In form2 (no controls needed). (This could have just as easily been an object created with a class.):
Option Explicit
'
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal dst As Long, ByVal src As Long, ByVal nBytes As Long)
'
Public Function MyFunction(ArgUdtPtr As Long) As Long
' Ok, this is how we get it done.
' There are a couple of things to notice right off the bat.
' First, the POINTER to the UDT is passed (using VarPtr) rather than the actual UDT.
' This way, we can circumvent the restriction of UDT not passed into objects.
' Second, the following MyUdt2 is declared as STATIC.
' This second point is important because the lifetime of MyUdt2 technically ends
' when we return from this function if it is just DIMmed.
' If we want to pass changes back to our caller, we will want to have a slightly longer lifetime.
Static MyUdt2 As MyUdtType
' Ok, we're here, so now we move the argument's UDT's data into our local UDT.
CopyMemory VarPtr(MyUdt2), ArgUdtPtr, Len(MyUdt2)
' Let's see if we got it.
MsgBox MyUdt2.l
' Now we might want to change it, and then pass back our changes.
MyUdt2.l = 5
' Once again, we pass back the pointer, because we can't get the actual UDT back.
' This is where the MyUdt2 being declared as Static becomes important.
MyFunction = VarPtr(MyUdt2)
End Function
And Finally, this goes in a module (BAS) file.
Option Explicit
'
' This is just the UDT that is used for the example.
Public Type MyUdtType
l As Long
End Type
'
Just Pass the UDT as Reference parametre and it will work. :)
'method in the class
Public Sub CreateFile(ByRef udt1 As UdtTest)
End Sub
I had the same error message and after checking the application, I found that in the property window for the class, the "Instancing" setting was set to "1 - Private" for the referenced object. I changed it to "5 - MultiUse" and got the same error message. I then went back to a version of the project module prior to when I added that referenced object and added it again the project - it defaulted to "1 - Private". I changed it to "5 - MultiUse" before doing anything else and closed the project to for it to update before compiling. I re-opened the project, verified it was still set to "5 - MultiUse", then compiled the project and it compiled cleanly without the error message.
When the error message was saying it didn't allow referencing a private object, the object really was private. Once I declared it not private, and the project module accepted that new setting, it compiled cleanly.
Define UDF (public type) in a module:
Public Type TPVArticulo
Referencia As String
Descripcion As String
PVP As Double
Dto As Double
End Type
and use Friend in class, module o frm:
Friend Function GetArticulo() As TPVArticulo
The UDT must be declared in a Public Object, like:
Public Class Sample
Public Strucutre UDT
Dim Value As Object
End Structure
End Class