Part of the work I've been doing requires that I make use of an external library in the form of a DLL. This article describes about 2/3 of the techniques needed for the process. If you like it, I'll write another one that deals with the other side of the coin --- "callbacks". That's a tricky bit of work in vs.Net.
vs.Net applications are like Java in that they run in their own runtime engine. In Java, its the JVM. In vs.Net its called the "CLR" (Common Language Runtime). One big disadvantage to the CLR is that you can't just easily pass data in and out of it. Its actually pretty complicated -- and in doing so you remove the benefits of the runtime environment. Normally, you don't worry about memory allocation at all. The CLR does that for you. When you're working with Unmanaged Memory, you have to allocate it to your variables and if you don't remember to free it when you're done, it creates a "memory leak". If all your memory leaks away the computer will forget how to work until you reboot.
The code that follows is a snippet which I find well illustrates about 2/3 of the issues surrounding these calls. There are three parts to look at. First, is the actual declaration that defines the function call and what data gets sent to and returned from the DLL itself. In this case, its a bit complicated. You pass in five pointers (memory locations) with room to store numeric values. The function will fill those locations with useful information, and will return to your function another integer value just indicating if it succeed or failed.
So, in the first part, we're telling the CLR that we want call a function called "iaxc_audio_devices_get" which is located in "iaxclient.dll". We want to pass it five numbers, and get back a number. We don't really tell the CLR what the numbers are for, just that they're numbers. In this case, each number is a POINTER to a memory slot -- like a cubbyhole -- which has enough room to hold pretty big number. What we know, because we read the documentation for the library, is that these numbers we get back when we go back and read the data at those locations will represent the following:
1. Another pointer, which is a memory location where we can find an array of records, each of which is an audio device description.
2. The total number of records held in the memory location described in number 1 above
3. Of the records pointed to by the value in number 1, which device is currently set to accept input
4. Of the records pointed to by the value in number 1, which device is currently set to accept output
5. Of the records pointed to by the value in number 1, which device is currently set to be used for RINGING noises
The second bit of code defines the structure of an audio device definition. Remember, we're going to get back a pointer to a group of these and a number telling us how many in the group. Notice that one part of the structure is itself a pointer to yet another place in memory where a text value is stored with the name of the device. That's because text strings vary in size, so you can't stack them neatly.
Finally, the third bit of code is the subroutine that allocates the 5 memory locations (cubbyholes), calls the library, and reads the data that got placed in the cubbyholes. Then it copies that information to variables we don't have to worry about and tells the system that it doesn't need the cubbyholes any more. Finally, it looks at the data and reads the text descriptions from the locations given for each, and prints the result.
' this is the function declaration for the call to the unmanaged dll itself
' note that the use of "_" allows me to wrap the lines so it isn't just
' just one long line of text.
Friend Declare Function iaxc_audio_devices_get Lib "iaxclient.dll" Alias _
"iaxc_audio_devices_get" (ByVal devlistptrInt As Integer, _
ByVal ndevsptrInt As Integer, ByVal InputptrInt As Integer, _
ByVal OutputptrInt As Integer, ByVal ringptrInt As Integer) As Integer
Structure iaxc_audio_device
' this is the structure definition - we will read an array of these
' from unmanaged memory by their address in memory
Public devnameptrint As Integer
Public caps As UInt32
Public devid As Integer
End Structure
Friend Sub getDevices()
' allocate pointer space in unmanaged memor
Dim devListPtr As IntPtr = Marshal.AllocHGlobal(4)
Dim devNoPtr As IntPtr = Marshal.AllocHGlobal(4)
Dim inputPtr As IntPtr = Marshal.AllocHGlobal(4)
Dim outputPtr As IntPtr = Marshal.AllocHGlobal(4)
Dim ringPtr As IntPtr = Marshal.AllocHGlobal(4)
' create managed memory value holders
Dim devlistPointer As IntPtr, devno As Integer, output As Integer, _
input As Integer, ring As Integer, result As Integer
' ********************************************************************
' Here is the actual API call to iaxclient.dll
' ********************************************************************
' fill the unmanaged locations with values
result = iaxc_audio_devices_get(devListPtr.ToInt32, devNoPtr.ToInt32, _
inputPtr.ToInt32, outputPtr.ToInt32, ringPtr.ToInt32)
' ********************************************************************
' ********************************************************************
' copy the unmanaged values to managed variables
devlistPointer = Marshal.ReadIntPtr(devListPtr, 0)
devno = Marshal.ReadInt32(devNoPtr, 0)
input = Marshal.ReadInt32(inputPtr, 0)
output = Marshal.ReadInt32(outputPtr, 0)
ring = Marshal.ReadInt32(ringPtr, 0)
' free the unmanaged memory to prevent leaks
Marshal.FreeHGlobal(devListPtr)
Marshal.FreeHGlobal(devNoPtr)
Marshal.FreeHGlobal(inputPtr)
Marshal.FreeHGlobal(outputPtr)
Marshal.FreeHGlobal(ringPtr)
' now try to read the array
Dim devices(devno - 1) As iaxc_audio_device
Dim devicenames(devno - 1) As String
Dim x As Integer
For x = 0 To devno - 1
' read raw bytes from unmanaged memory and convert them into a managed structure
devices(x) = CType(Marshal.PtrToStructure(devlistPointer, GetType(iaxc_audio_device)), iaxc_audio_device)
' One of the structure members is itself a pointer to a memory location with
' a character string, containing the text value we actually want.
' read that string from a memory location we get from the pointer
devicenames(x) = Marshal.PtrToStringAnsi(New IntPtr(devices(x).devnameptrint))
' now advance the memory location pointer forward by the size of one record
devlistPointer = IntPtr.op_Explicit(devlistPointer.ToInt32 + Marshal.SizeOf(devices(x)))
Next
' lets see what we ended up with
Debug.WriteLine("Audio in: " + devicenames(input))
Debug.WriteLine("Audio Out: " + devicenames(output))
Debug.WriteLine("Ring Sound: " + devicenames(ring))
End Sub
Comment Entry |
Please wait while your document is saved.
right away. It's always good to see that kind of stuff and keep it in the back
of your mind.
Even more interesting to me would be examples of calling a .NET DLL from
LotusScript. I haven't had to venture down that road yet, but I know I will
someday.