I am trying to call TrackPopupMenu function to display a menu at runtime from managed VB.NET code.
Below is the error I am getting :
PInvokeStackImbalance was detected Message: A call to PInvoke function
'UeWIPopupX!UeWIPopupX.mDeclares::TrackPopupMenu' has unbalanced
the stack. This is likely because the managed PInvoke signature does
not match the unmanaged target signature. Check that the calling
convention and parameters of the PInvoke signature match the target
unmanaged signature.
Below is the declaration I am using for TrackPopupMenu function :
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True, CallingConvention:=CallingConvention.StdCall)> _
Friend Function TrackPopupMenu(ByVal hMenu As Long, ByVal wFlags As Integer, ByVal x As Integer, ByVal y As Integer, ByVal nReserved As Integer, ByVal hWnd As IntPtr, ByVal lprc As RECT) As Integer
End Function
Below is the code for calling TrackPopupMenu function :
dim lpRc as RECT
Dim tP As POINTAPI
Dim lR as Integer
Dim lUn as Integer
lUn = TPM_RIGHTBUTTON Or TPM_TOPALIGN Or TPM_LEFTALIGN Or TPM_RETURNCMD
tP.x = 50
tP.y = 100
'Here I am getting the error
lR = TrackPopupMenu(m_ppMenu.Tools(1).hMenu, lUn, tP.x, tP.y, 0, m_hWndOwner, lpRC)
Below is the declaration for rectangle RECT:
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
Structure RECT
Dim Left As Integer
Dim Top As Integer
Dim Right As Integer
Dim Bottom As Integer
End Structure
All the arguments during call of TrackPopupMenu has some values.
I tried different callingConvention but still getting the error.
I am not able to solve this. Does anyone know how to resolve this issue ?
Your declaration is wrong. The first argument is a handle to the menu, it must therefore be IntPtr. The last argument is a pointer to RECT. ByRef in VB.NET. Since it isn't actually used, you are better off declaring it ByVal IntPtr so you don't need the RECT declaration. Pass IntPtr.Zero in your call. The return value is Boolean, not Integer. Throw a Win32Exception if you get a False return. Fix:
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
Friend Function TrackPopupMenu(ByVal hMenu As IntPtr, ByVal wFlags As Integer, _
ByVal x As Integer, ByVal y As Integer, ByVal nReserved As Integer, _
ByVal hWnd As IntPtr, ByVal ignored As IntPtr) As Boolean
End Function
There are some hints that you didn't get the menu right, hard to imagine how you came up with Long as the argument type. Do note that this function is already ably wrapped in .NET, having to pinvoke it should be extremely rare. TrackPopupMenu is already called by the .NET ContextMenu class. The ContextMenuStrip class gives a more modern version of it with better rendering options.
Related
I'm looking for the fastest way to take a print-screen, and i found out that using Bitblt was my better choice, however, it only works for device context handle's, which means for me to retrieve a bitmap from that, i'd have to use multiple API's including CreateCompatibleBitmap, which in the end it probably takes the same time as using a managed way, like graphics.CopyFromScreen (which is a bit slow for me and also consumes alot of CPU, between 7-10% on a 2.3ghz quad-core processor...)
However, i still searched for a cleaner way of retrieving a bitmap from it, so i came up with this code:
<DllImport("user32.dll")> _
Public Shared Function GetDC(ByVal hWnd As IntPtr) As IntPtr
End Function
<DllImport("user32.dll")> _
Public Shared Function ReleaseDC(ByVal hWnd As IntPtr, ByVal hDC As IntPtr) As Integer
End Function
<DllImport("gdi32.dll")> _
Public Shared Function BitBlt(ByVal hdcDest As IntPtr, ByVal xDest As Integer, ByVal yDest As Integer, ByVal wDest As Integer, ByVal hDest As Integer, ByVal hdcSource As IntPtr, _
ByVal xSrc As Integer, ByVal ySrc As Integer, ByVal rop As TernaryRasterOperations) As Boolean
End Function
Dim hwNd As IntPtr = Nothing
hwNd = GetDC(GetDesktopWindow)
picHandle = GetDC(Me.PictureBox1.Handle)
BitBlt(picHandle, 0, 0, PictureBox1.Width, PictureBox1.Height, hwNd, 0, 0, TernaryRasterOperations.SRCCOPY)
ReleaseDC(hwNd, picHandle)
I can reach ~30 fps with this... But it has two problems as i said above:
Even if displaying it on a picturebox as i'm doing it above accomplished what i want, it doesn't resize to the picturebox control, even if i change those "0" values to the picturebox x and y coordinates.
I further searched and found there's a StretchBit API for that, and it does stretch, but it also reduces quality, (Even with the necessary call to SetStretchBltMode with parameter "HALFTONE" so it doesn't "corrupt" the pixels), it also reduces performance at least in 10+ fps...
But as i need to get it as bitmap object, with the other necessary API's for that, i ended up with almost half the performance (15~ fps) which is equivalent of graphics.CopyFromScreen.
So, i'm asking, is there another way to get a bitmap from the screen using Bitblt or similar without losing performance?
If there isn't a .Net way, i kindly ask for any language-way of doing that.
If you want raw performance, you will have to get away from managed code. This is easy enough using C++ with Visual Studio. You can make calls directly to the Windows API, bypassing the .NET runtime, managed code for your application, and the overhead of p/invokes in .NET.
If you are familiar with C#, you can take your C# code, convert it to C++ (which should be straightforward, with a lot of work to replace the CLI).
Private Declare Function BitBlt Lib "GDI32" ( _
ByVal hdcDest As Integer, _
ByVal nXDest As Integer, _
ByVal nYDest As Integer, _
ByVal nWidth As Integer, _
ByVal nHeight As Integer, _
ByVal hdcSrc As Integer, _
ByVal nXSrc As Integer, _
ByVal nYSrc As Integer, _
ByVal dwRop As System.Int32) As Boolean
Declare Function QueryPerformanceCounter Lib "Kernel32" (ByRef X As Long) As Short
Declare Function QueryPerformanceFrequency Lib "Kernel32" (ByRef X As Long) As Short
Const SRCCOPY As Integer = &HCC0020
Use a form with only a picturebox and a label in it. Set the anchors of picbox accordingly. In picbox down event:
Private Sub PictureBox1_MouseDown(sender As System.Object, e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseDown
Dim Ctr1, Ctr2, Freq As Long
Dim dbl As Double
QueryPerformanceCounter(Ctr1)
Dim desktopDC As IntPtr = Nothing
Dim picboxDC As IntPtr = Nothing
desktopDC = GetDC(New IntPtr(0))
picboxDC = GetDC(PictureBox1.Handle)
BitBlt(picboxDC, 0, 0, PictureBox1.Width, PictureBox1.Height, desktopDC, 0, 0, SRCCOPY)
QueryPerformanceCounter(Ctr2)
QueryPerformanceFrequency(Freq)
dbl = (Ctr2 - Ctr1) / Freq
dbl *= 1000000
Label1.Text = dbl.ToString 'it is in microseconds
ReleaseDC(New IntPtr(0), desktopDC)
ReleaseDC(PictureBox1.Handle, picboxDC)
End Sub
Maximize your form and click in picturebox.
Trying out some API programming, I encountered a problem with the capGetDriverDescription function of AVICAP32.dll:
Declare Function capGetDriverDescriptionA Lib "avicap32.dll" (ByVal wDriver As Short, _
ByVal lpszName As String, ByVal cbName As Integer, ByVal lpszVer As String, _
ByVal cbVer As Integer) As Boolean
As far as I know, the lpszName parameter is a buffer for getting the driver description, and I read somewhere that the buffer should be passed as a reference type (ByRef instead of ByVal), and of course it should be a reference because of storing the information I need after returning from the function. But in this function, it's a value type, and it works fine!
even I tried to change it to ByRef but the application didnt run!
What knowledge am I missing? How can this buffer store my information while it's being passed a value type?
This is how the code calls the function to get available webcams:
Private Sub LoadDeviceList()
On Error Resume Next
Dim strName As String = Space(100)
Dim strVer As String = Space(100)
Dim bReturn As Boolean
Dim x As Integer = 0
Do
bReturn = capGetDriverDescriptionA(x, strName, 100, strVer, 100)
If bReturn Then
lst1.Items.Add(strName.Trim)
End If
x += 1
Application.DoEvents()
Loop Until bReturn = False
End Sub
I'll start by saying I don't know much about this subject (marshaling data can be a deep subject), but I think Default Marshaling for Strings (MSDN) may help you. Scroll down to the part that says Fixed-Length String Buffers. According to this, when a string is marshaled to the API, even if passed byRef, it can't be modified by the callee.
I haven't tested, but according to the MSDN example, to get a value back from your function, the definition would become
Declare Function capGetDriverDescriptionA Lib "avicap32.dll" (ByVal wDriver As Short, _
ByVal lpszName As StringBuilder, ByVal cbName As Integer, ByVal lpszVer As String, _
ByVal cbVer As Integer) As Boolean
Changing lpszName from String to StringBuilder. Apparently StringBuilder will work as a buffer to get the string back from the function.
Then you would call your function like so,
StringBuilder sb = new StringBuilder(256)
capGetDriverDescription(Driver, sb, sb.Capacity + 1, ....
return sb.ToString()
I found a very similar question, How do I import and call unmanaged C dll with ANSI C string "char *" pointer string from VB.NET?.
I'm going little crazy about this and I already spent few hours to find the problem so I guess I need some WinAPI guru here :) I use VB .NET so please be kind :)
Trying to communicate with some USB device and at SetupDiGetDeviceInterfaceDetail() step, the (first) call to function will fail with ERROR_INVALID_USER_BUFFER. The second call will have non sense now :) The problem is why ? The DeviceInfoTable handle is correct since I use'it in few more functions with success and I tested Err.LastDllError after each call.
The InterfaceDataStructure I presume is also correct since otherways SetupDiEnumDeviceInterfaces() will fails. I suspect incorrect declarations of dll imports or structure.
If Not SetupDiGetDeviceInterfaceDetail(DeviceInfoTable, InterfaceDataStructure, Nothing, 0, StructureSize, Nothing) Then
ErrorStatus = Err.LastDllError ' <-- always 0x6F8, ERROR_INVALID_USER_BUFFER
End If
Here are declarations
Dim DeviceInfoTable As IntPtr = INVALID_HANDLE_VALUE
Dim InterfaceDataStructure As SP_DEVICE_INTERFACE_DATA = New SP_DEVICE_INTERFACE_DATA
Dim InterfaceIndex As Integer = 0
Dim ErrorStatus As Integer = 0
Dim DevInfoData As SP_DEVINFO_DATA = New SP_DEVINFO_DATA
Dim dwRegType As Integer
Dim dwRegSize As Integer
Dim DetailedInterfaceDataStructure As SP_DEVICE_INTERFACE_DETAIL_DATA = New SP_DEVICE_INTERFACE_DETAIL_DATA
Dim StructureSize As Integer = 0
'Structures declarations
<StructLayout(LayoutKind.Sequential, Pack:=1)> _
Public Structure SP_DEVINFO_DATA
Public cbSize As UInteger
Public InterfaceClassGUID As Guid
Public DevInst As UInteger
Public Reserved As IntPtr
End Structure
<StructLayout(LayoutKind.Sequential, Pack:=1)> _
Public Structure SP_DEVICE_INTERFACE_DATA
Public cbSize As UInteger
Public InterfaceClassGuid As Guid
Public Flags As UInteger
Public Reserved As IntPtr
End Structure
<StructLayout(LayoutKind.Sequential, Pack:=1)> _
Public Structure SP_DEVICE_INTERFACE_DETAIL_DATA
Public cbSize As UInteger
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=256)> Public DevicePath As String
End Structure
<DllImport("setupapi.dll",
CharSet:=CharSet.Auto,
SetLastError:=True)> _
Public Shared Function SetupDiGetDeviceInterfaceDetail(ByVal hDevInfo As IntPtr,
ByRef deviceInterfaceData As SP_DEVICE_INTERFACE_DATA,
ByRef deviceInterfaceDetailData As SP_DEVICE_INTERFACE_DETAIL_DATA,
ByVal deviceInterfaceDetailDataSize As Int32,
ByRef RequiredSize As Int32,
ByRef deviceInfo As SP_DEVINFO_DATA) As Boolean
Thanks very much in advance,
If Not SetupDiGetDeviceInterfaceDetail(..., Nothing, 0, StructureSize, Nothing)
You are struggling with basic VB.NET usage here, the keyword Nothing doesn't mean what you hope it does. What the api function wants you to do is pass a null pointer. IntPtr.Zero. That could be Nothing, but it is not in this case. You declared the argument types as structures. They are value types. Nothing means something else in the case of value types, it means "default value". So you are actually passing a pointer to a structure here, a structure that's zero initialized. The function is unhappy about that and tells you so.
You cannot pass IntPtr.Zero when you use these pinvoke declarations. You can cheat and declare an overload of the function, one that uses different argument types. Like this:
<DllImport("setupapi.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
Public Shared Function SetupDiGetDeviceInterfaceDetail(
ByVal hDevInfo As IntPtr,
ByRef deviceInterfaceData As SP_DEVICE_INTERFACE_DATA,
ByVal mustPassIntPtrZero As IntPtr,
ByVal mustPassZero As Int32,
ByRef RequiredSize As Int32,
ByVal mustPassIntPtrZero2 As IntPtr) As Boolean
Now you can make the first call to the function:
If Not SetupDiGetDeviceInterfaceDetail(..., IntPtr.Zero, 0, StructureSize, IntPtr.Zero)
You should use the returned RequiredSize to allocate memory with Marshal.AllocHGlobal(). Pass the returned pointer as the deviceInterfaceDetailData argument. Which must now be declared as ByVal IntPtr. And use Marshal.PtrToStructure() to convert it to the struct after the call. Yes, Pack is an issue on 64-bit operating systems.
Fix the string problem by declaring the structure with CharSet:=CharSet.Auto so you'll get the Unicode conversion instead of the Ansi conversion.
Now I figured out, in fact half working :) Maybe someone can en light me.
At least on VB .NET 2010, the SetupDiGetDeviceInterfaceDetail() doesn't line NULLs (aka nothing) as parameters, so I had to first pass something dummy:
DetailedInterfaceDataStructure.cbSize = 6
SetupDiGetDeviceInterfaceDetail(DeviceInfoTable, InterfaceDataStructure, DetailedInterfaceDataStructure, 1000, StructureSize, DevInfoData)
The cbSize must be <> 0 and on 32 bit systems must be 6 (4 for DWORD and 2 for null terminated wide string). The 1000 it's just a big number, so SetupDiGetDeviceInterfaceDetail() won't return even ERROR_INSUFFICIENT_BUFFER
Now the second pass supposed to get the real stuff:
SetupDiGetDeviceInterfaceDetail(DeviceInfoTable, InterfaceDataStructure, DetailedInterfaceDataStructure, StructureSize, StructureSize, Nothing)
Which doesn't return any system error, but path is only a backslash character "\" and this isn't good.... it supposed to be a valid USB path
"\\?\usb#vid_01E5&pid_00A2#5&1d4952dc&0&2#{18d0A210-85D2-........"
Anyone can help me ?...
yes I am struggling with VB since I'm coming from pure C99 and assembler for MCU :) In fact, after few hours of errors I got some snippet in C# that worked like a charm but I had to know why isn't working in VB :) I finally solved, partially like your advices
I overloaded SetupDiGetDeviceInterfaceDetail() with another one, just to accept a ptr to some unmanaged buffer:
<DllImport("setupapi.dll",
CharSet:=CharSet.Auto,
SetLastError:=True)> _
Public Shared Function SetupDiGetDeviceInterfaceDetail(ByVal hDevInfo As IntPtr,
ByRef deviceInterfaceData As SP_DEVICE_INTERFACE_DATA,
ByVal deviceInterfaceDetailData As IntPtr,
ByVal deviceInterfaceDetailDataSize As Int32,
ByRef RequiredSize As Int32,
ByRef deviceInfo As SP_DEVINFO_DATA) As Boolean
End Function
Now the problem was I used ByRef deviceInterfaceDetailData As IntPtr instead of ByVal deviceInterfaceDetailData As IntPtr, was my mistake from VB's quite simple perspective.
Indeed, now I can pass some unmanaged buffer at 2'nd call of SetupDiGetDeviceInterfaceDetail()
Dim PUnmanagedDetailedInterfaceDataStructure As IntPtr = IntPtr.Zero
PUnmanagedDetailedInterfaceDataStructure = Marshal.AllocHGlobal(StructureSize)
DetailedInterfaceDataStructure.cbSize = 6 ' cbSize = 4 bytes for DWORD + 2 bytes for Unicode null terminator
Marshal.StructureToPtr(DetailedInterfaceDataStructure, PUnmanagedDetailedInterfaceDataStructure, False) ' Copy the contents of the structure, to an unmanaged memory space
SetupDiGetDeviceInterfaceDetail(DeviceInfoTable, InterfaceDataStructure, PUnmanagedDetailedInterfaceDataStructure, StructureSize, StructureSize, DevInfoData)
And this is finally working. I expect some points for struggling :)
I saw a lot of similar threads on internet but none with a complete solve.
Merry Christmas to all
I am using this sample as a basis for a program I'm making. After approximately 618 keystrokes, the program throws this error:
CallbackOnCollectedDelegate was detected
Message: A callback was made on a garbage collected delegate of type 'KeyLogger!KeyLogger.CallBackFunction+DelegateCallBack::Invoke'. This may cause application crashes, corruption and data loss. When passing delegates to unmanaged code, they must be kept alive by the managed application until it is guaranteed that they will never be called.
The error is thrown most times the application is run, but not every time and not at the same keystroke count. From the error message, I think it sounds like the garbage collector is collecting the delegate, how can I prevent this?
The program I made is essentially a modified version of that vb.net project, but it does not actually store the keystrokes.
Thank you for your help!
Code within CallBack.vb:
Option Strict Off
Option Explicit On
Module CallBackFunction
'******************************************************************************************
' Sample for retrieving keystrokes by use of the "kbLog32.dll"
' (c) 2004 by Nadeem Afanah.
'******************************************************************************************
'CallBack function
Delegate Sub DelegateCallBack(ByVal msg As Integer, ByVal wParam As Integer, ByVal lParam As Integer)
Sub CallBack(ByVal msg As Integer, ByVal wParam As Integer, ByVal lParam As Integer)
'here we track only WM_CHAR and WM_KEYDOWN
If msg = WM_KEYDOWN Then
...
End If
End Sub
End Module
Code in Declarations.vb:
Option Strict Off
Option Explicit On
Module Declarations
'******************************************************************************************
' Sample for retrieving keystrokes by use of the "kbLog32.dll"
' (c) 2004 by Nadeem Afanah.
'******************************************************************************************
'******************************************************************************************
'DLL declarations
Public Declare Function StartLog Lib "kbLog32" (ByVal hWnd As Integer, ByVal lpFuncAddress As DelegateCallBack) As Integer
Public Declare Sub EndLog Lib "kbLog32" ()
'----------------------------------------------------------------------------------------
Declare Function SetWindowPos Lib "user32" (ByVal hWnd As Integer, ByVal hWndInsertAfter As Integer, ByVal x As Integer, ByVal y As Integer, ByVal cx As Integer, ByVal cy As Integer, ByVal wFlags As Integer) As Integer
Declare Function FindWindow Lib "user32" Alias "FindWindowA"(ByVal lpClassName As String, ByVal lpWindowName As String) As Integer
Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA"(ByVal hWnd1 As Integer, ByVal hWnd2 As Integer, ByVal lpsz1 As String, ByVal lpsz2 As String) As Integer
'****************************************************************************************
' Keyboard messages
Public Const WM_KEYUP As Short = &H101s
Public Const WM_KEYDOWN As Short = &H100s
Public Const WM_CHAR As Short = &H102s
Public Const WM_SYSKEYDOWN As Short = &H104s
Public Const WM_SYSKEYUP As Short = &H105s
'SetWindowPos messages
Public Const SWP_NOSIZE As Short = &H1s
Public Const SWP_NOMOVE As Short = &H2s
Public Const HWND_TOPMOST As Short = -1
Public Const SWP_SHOWWINDOW As Short = &H40s
'******************************************************************************************
End Module
Look at the code in Form1.vb where it does this:
StartLog(nhWnd_text, AddressOf CallBack)
This is where it's saying, take the location of the Callback function an use it to hanle messages I receive regarding keyboard events.
Try something like this:
Friend Class Form1
Inherits System.Windows.Forms.Form
''Add this ----------------------------
<MarshalAs(UnmanagedType.FunctionPtr)> _
Private DelSub as New DelegateCallBack(AdressOf CallBack)
''-------------------------------------
''In the sub Sub Command1_Click
''Change this -------------------------
StartLog(nhWnd_text, AddressOf CallBack)
''To this -----------------------------
StartLog(nhWnd_text, DelSub)
''-------------------------------------
End Class
What we're doing here is creating a local "delegate sub" (think of it as a variable which points at a sub). We're pointing this at the Callback sub. We're then using this delegate sub instead of passing in a reference directly to the Callback sub.
The difference is that the .Net framework now knows that there is something pointing at that sub so won't garbage collect it (clear it from memory)
The MarshallAs bit is a little superfluous as that's the default marshalling but it simply means we're explicitly telling .Net that we're using the delegate to access unmanaged code (something outside the .Net framework)
Just for the record, I still had to download the code as it was actually the bit in Form1.vb that was relevant - But thanks for trying :)
I´m having some problem to convert my VB6 project to VB.NET
I don't understand how this "AddressOf" function should be in VB.NET
My VB6 code:
Declare Function MP4_ClientStart Lib "hikclient.dll" _
(pClientinfo As CLIENT_VIDEOINFO, ByVal abab As Long) As Long
Public Sub ReadDataCallBack(ByVal nPort As Long, pPacketBuffer As Byte, _
ByVal nPacketSize As Long)
If Not bSaved_DVS Then
bSaved_DVS = True
HW_OpenStream hChannelHandle, pPacketBuffer, nPacketSize
End If
HW_InputData hChannelHandle, pPacketBuffer, nPacketSize
End Sub
nn1 = MP4_ClientStart(clientinfo, AddressOf ReadDataCallBack)
You are probably seeing this error:
'AddressOf' expression cannot be
converted to 'Long' because 'Long' is
not a delegate type.
What you probably want to do is create a delegate then change the type of adab to that delegate type. Add this to the class:
Public Delegate Sub ReadDataCallBackDelegate(ByVal nPort As Long, _
ByVal pPacketBuffer As Byte, ByVal nPacketSize As Long)
Then change your P/Invoke declaration to:
Declare Function MP4_ClientStart Lib "hikclient.dll" (ByVal pClientinfo As _
CLIENT_VIDEOINFO, ByVal abab As ReadDataCallBackDelegate) As Long
Do not delete/change your ReadDataCallBack Sub, you still need that.
At that point he compiler should be happy. However, the point made by others is important. The length of Integers and Longs is different in VB6 than in VB.NET. So in .NET you need to use Integer anytime you used a Long in VB6.
Regarding callbacks in unmanaged code see if this similar post helps you.
Regarding your question - I don't think you need callback functions or the example you posted is not correct/complet - see the post indicated above and clarify your code sample.
I assume that the second parameter to MP4_ClientStart is supposed to be the address of a callback function. Likely the problem is that you've defined it here as a Long, which in VB6 is a 32-bit value, but in VB.NET is a 64-bit value. You'll probably have some success by changing your declaration to:
Declare Function MP4_ClientStart Lib "hikclient.dll" _
(pClientinfo As CLIENT_VIDEOINFO, ByVal abab As Integer) As Integer
Here is the VB.NET implementation:
Declare Function MP4_ClientStart Lib "hikclient.dll" (ByRef pClientinfo As _
CLIENT_VIDEOINFO, ByVal abab As ReadDataCallBackDelegate) As Integer
Public Delegate Sub ReadDataCallBackDelegate(ByVal nPort As Long, _
ByRef pPacketBuffer As Byte, ByVal nPacketSize As Long)
Public Sub ReadDataCallBack(ByVal nPort As Integer, ByRef pPacketBuffer As _
Byte, ByVal nPacketSize As Integer)
If Not bSaved_DVS Then
bSaved_DVS = True
HW_OpenStream(hChannelHandle, pPacketBuffer, nPacketSize)
End If
HW_InputData(hChannelHandle, pPacketBuffer, nPacketSize)
End Sub
MP4_ClientStart(clientinfo, AddressOf ReadDataCallBack)