Passing a type to a DLL API

BlitzMax Forums/BlitzMax Programming/Passing a type to a DLL API

semar(Posted 2005) [#1]
All,
I have this API function:
Extern "Os"
Function midiOutGetDevCapsA( uDeviceID:Int, lpCaps:MIDIOUTCAPS, uSize:Int)
End Extern


and this BMAX type structure:
Const MAXPNAMELEN = 32

Type MIDIOUTCAPS
	Field wMid:Short
	Field wPid:Short
	Field vDriverVersion:Short ' MMVERSION 
	Field szPname:String[MAXPNAMELEN]
	Field wTechnology:Short
	Field wVoices:Short
	Field wNotes:Short
	Field wChannelMask:Short
	Field dwSupport:Int
EndType


Question: how do I pass the type structure to the function above ?

I've tryed several ways, for example:
cap:MIDIOUTCAPS = new MIDIOUTCAPS

1)
midiOutGetDevCapsA(n, cap, SizeOf(MIDIOUTCAPS))

2)
midiOutGetDevCapsA(n, Byte Ptr(cap), SizeOf(MIDIOUTCAPS))

3)
midiOutGetDevCapsA(n, Int Ptr(cap), SizeOf(MIDIOUTCAPS))

4)
midiOutGetDevCapsA(n, Byte Ptr(cap.wmid), SizeOf(MIDIOUTCAPS))

5)
midiOutGetDevCapsA(n, Int Ptr(cap.wmid), SizeOf(MIDIOUTCAPS))

and so on...

This is a VB6 code that use the same function, and works
Const MAXPNAMELEN = 32
Private Type MIDIOUTCAPS
    wMid As Integer
    wPid As Integer
    vDriverVersion As Long
    szPname As String * MAXPNAMELEN
    wTechnology As Integer
    wVoices As Integer
    wNotes As Integer
    wChannelMask As Integer
    dwSupport As Long
End Type

Private Type test
    abc As Integer
End Type

Private Declare Function midiOutGetDevCaps Lib "winmm.dll" Alias "midiOutGetDevCapsA" (ByVal uDeviceID As Long, lpCaps As MIDIOUTCAPS, ByVal uSize As Long) As Long
Private Declare Function midiOutGetNumDevs Lib "winmm" () As Integer
Private Sub Form_Paint()
    'KPD-Team 1999
    'URL: http://www.allapi.net/
    'E-Mail: KPDTeam@...
    Dim MidiCaps As MIDIOUTCAPS
    Dim Cnt As Long
    
    'Clear the form
    Me.Cls
    'Get the number of installed MIDI devices
    Me.Print "Available midi devices:" + Str$(midiOutGetNumDevs)
    For Cnt = 0 To midiOutGetNumDevs - 1
        'Get the device name and capabilities
        Me.Print midiOutGetDevCaps(Cnt, MidiCaps, Len(MidiCaps))
        Me.Print "Device name" + Str$(Cnt + 1) + ": " + MidiCaps.szPname
    Next Cnt
End Sub


Could someone of you explain in detail how to pass a type structure to such a DLL ?

And also, is this conversion table right ?

VB Integer = Bmax Short
VB Long = Bmax Int

C WORD = Bmax Short
C DWORD = Bmax Int

Sergio.


TartanTangerine (was Indiepath)(Posted 2005) [#2]
Yeah I tried this, asked the question and could not get a solution. sorry this does not help, if you do get a solution I would like to see it.


Dreamora(Posted 2005) [#3]
I would say
VB Integer = BM Int
VB Long = BM Long


Sarge(Posted 2005) [#4]
VB Integer = BM Short
VB Long = BM Int
--
WORD = BM Short ; 80% sure
DWORD = BM Int ; 80% sure

I will have a example coming soon.


Robert(Posted 2005) [#5]
@Dremora,

semar's original conversion table is correct. A VB6 int is two bytes long (ie. a short in most other languages):

VB6 Int = BMX Short
VB6 Long = BMX Int

@semar

Unfortunately it doesn't seem to be possible to create C-style arrays in BlitzMAX. String[] is an array of strings, whereas the MIDIOUTCAPS structure should be an array of 32 chars. Using Byte[] in BlitzMAX doesn't work either, since arrays are objects.

I think the easiest option would be to have a C wrapper for these functions.


Robert(Posted 2005) [#6]
I'm sure Mark or Skid could produce a much shorter and easier version, but this at least seems to work:

BlitzMAX Code:
Import "midi.c"

Rem
typedef struct { 
    WORD      wMid; 
    WORD      wPid; 
    MMVERSION vDriverVersion; 
    CHAR      szPname[MAXPNAMELEN]; 
    WORD      wTechnology; 
    WORD      wVoices; 
    WORD      wNotes; 
    WORD      wChannelMask; 
    DWORD     dwSupport; 
} MIDIOUTCAPS; 
EndRem

Extern
	Function bbMidiOutCapsSize()
	Function bbMidiOutGetDevCaps(device:Int,buf:Byte Ptr)
End Extern

Type MIDICaps
	Field manufacturerId:Int
	Field productId:Int
	Field driverVersion:Int
	Field productName:String
	Field technology:String
	Field voices:Int
	Field notes:Int
	Field channelMask:Int
	Field support:Int
End Type

Local caps:MIDICaps=GetMidiDevCaps(0)

Print caps.productName

Function GetMidiDevCaps:MIDICaps(device:Int)
	Local caps:MIDICaps=New MIDICaps
	
	Local bank:TBank=CreateBank(bbMidiOutCapsSize())
	
	Local result:Int=bbMidiOutGetDevCaps(device,BankBuf(bank))
	
	caps.manufacturerId=PeekShort(bank,0)
	caps.productId=PeekShort(bank,2)
	caps.driverVersion=PeekInt(bank,4)
	
	Local out:String
	
	For Local k:Byte=0 Until 32
		out = out + Chr(PeekByte(bank,8+k))	
	Next
	
	caps.productName=out
	caps.technology=PeekShort(bank,40)
	caps.voices=PeekShort(bank,42)
	caps.notes=PeekShort(bank,44)
	caps.channelMask=PeekShort(bank,46)
	caps.support=PeekInt(bank,48)
	
	Return caps
	
End Function


midi.c code (MinGW required to compile under Windows):
#include <windows.h>

int bbMidiOutCapsSize()
{
	return sizeof(MIDIOUTCAPS);
}

int bbMidiOutGetDevCaps(int device, char* out)
{
	MIDIOUTCAPS caps;
	
	int result=midiOutGetDevCaps(device,&caps,sizeof(MIDIOUTCAPS));

	
	memcpy(out,&caps,sizeof(MIDIOUTCAPS));

	return result;
}



gman(Posted 2005) [#7]
another way without the .c but still using banks. other than having to count your bytes, banks are fantastic for this stuff.

Extern "Os"
Function midiOutGetDevCapsA( uDeviceID:Int, lpCaps:Byte Ptr, uSize:Int)
End Extern

Local tb:TBank=CreateBank(52)

DebugLog(midiOutGetDevCapsA(0, BankBuf(tb), 52))

DebugLog("wMid: "+PeekShort(tb,0))
DebugLog("wPid: "+PeekShort(tb,2))
DebugLog("vDriverVersion: "+PeekInt(tb,4))

Local name:String
For i=0 To 31
	name:+Chr(PeekByte(tb,8+i))
Next
DebugLog("szPname: "+name)
DebugLog("wTechnology: "+PeekShort(tb,40))
DebugLog("wVoices: "+PeekShort(tb,42))
DebugLog("wNotes: "+PeekShort(tb,44))
DebugLog("wChannelMask: "+PeekShort(tb,46))
DebugLog("dwSupport: "+PeekInt(tb,48))



Sarge(Posted 2005) [#8]
heh, i think i have to start learning to use banks too


semar(Posted 2005) [#9]
Thanks all for the replies - again, what a nice community !

@Robert,
that was indeed an interesting example. Sadly I don't have MingW installed to try it, but I guess it's worth the installation.
I've just copied the code you posted, to study it better. The possibility of using C code intrigues me - but scary too ! ;-)

@gman,
thank you for that unvaluable example. Just tryed, and it worked like a charm. I didn't thought about using bank instead of structures.. it's a really good way to interface with API, isn't it ?

So, one way to pass a structure to a DLL is: using bank !

I guess that to transfer the data from a bank to a type structure, the way is the same: mytype.myfield = peekshort(2,offset).. and so on for each field is needed. Right ?
Or is there a way to, say, do something like:
ptr(t:mytype) = ptr(bank).. ?? I must experiment now.. 8)

I wish these info were more ducumented though.. there should be some detailed chapter covering this kind of topics.

BMAX has an *huge* potential, I discover it every day, but the documentation, IMO, offers lots of fields of improvements.

Best regards,
Sergio.


Robert(Posted 2005) [#10]
I guess that to transfer the data from a bank to a type structure, the way is the same: mytype.myfield = peekshort(2,offset).. and so on for each field is needed. Right ?


If the MIDIOUTCAPS structure didn't have that char array in it (the product name), it would be very easy (just pass a type object as a Byte Ptr to the function) - there would be no need to copy the fields one by one. Unfortunately BlitzMAX provides no way of creating C-style arrays in structures - this has given me an idea :)


Robert(Posted 2005) [#11]
Sorry for the double post - I've had a quick idea: This gets around the problem using 4 longs (giving 8*4=32 bytes) to create the padding where the string is supposed to be stored.

Hopefully it is a bit easier to understand (no C code or Banks required!)

Edit: I've just seen Mark's own thread in the Beginners Area - this is what he suggested as well. Doh!

Extern "Win32"
	Function midiOutGetDevCapsA(uDeviceID:Int,lpCaps:Byte Ptr,uSize:Int)
End Extern

Type MIDICaps
	Field wMid:Short
	Field wPid:Short
	Field vDriverVersion:Int
	
	Field padA:Long
	Field padB:Long
	Field padC:Long
	Field padD:Long
	
	Field wTechnology:Short
	Field wVoices:Short
	Field wNotes:Short
	Field wChannelMask:Short
	Field dwSupport:Int
End Type

Local caps:MIDICaps=New MIDICaps
Local capsPtr:Byte Ptr=Byte Ptr(caps)

midiOutGetDevCapsA(0,capsPtr,SizeOf(caps))

Print bufferToString(capsPtr+8)

Function bufferToString:String(buf:Byte Ptr)
	Local i:Int=0
	Local result:String
	
	While (buf[i] <> 0)
		result = result + Chr(buf[i])
		i :+ 1
	Wend
	
	Return result
End Function



gman(Posted 2005) [#12]
robert is correct. if it would have been a pointer to a C string, it would have been much easier and you were on the right track for that one in your initial post.

here is a wrapped up version of the bank stuff. of course, it could be done many ways and this is just off the top of my head...

Strict
Framework BRL.Blitz
Import BRL.Bank

Extern "Os"
	Function midiOutGetDevCapsA( uDeviceID:Int, lpCaps:Byte Ptr, uSize:Int)
End Extern


Local midi:MIDIOUTCAPS=MIDIOUTCAPS.midiOutGetDevCaps(0)

If midi<>Null
	DebugLog("deviceid: "+midi.uDeviceID)
	DebugLog("wMid: "+midi.wMid)
	DebugLog("wPid: "+midi.wPid)
	DebugLog("vDriverVersion: "+midi.vDriverVersion)
	DebugLog("szPname: "+midi.szPname)
	DebugLog("wTechnology: "+midi.wTechnology)
	DebugLog("wVoices: "+midi.wVoices)
	DebugLog("wNotes: "+midi.wNotes)
	DebugLog("wChannelMask: "+midi.wChannelMask)
	DebugLog("dwSupport: "+midi.dwSupport)
EndIf

Type MIDIOUTCAPS
	Const MAXPNAMELEN = 32
	' struct size (minus the String) + MAXPNAMELEN (for the bytes) = 52
	Const STRUCTSIZE = 52
	
	Field uDeviceID:Int
	
	Field wMid:Short
	Field wPid:Short
	Field vDriverVersion:Int
	Field szPname:String
	Field wTechnology:Short
	Field wVoices:Short
	Field wNotes:Short
	Field wChannelMask:Short
	Field dwSupport:Int

	' returns NULL if unsuccessful
	Function midiOutGetDevCaps:MIDIOUTCAPS(uDeviceID:Int)

		Local retval:MIDIOUTCAPS=Null,i:Int
		Local tb:TBank=CreateBank(MIDIOUTCAPS.STRUCTSIZE)
		
		If midiOutGetDevCapsA(uDeviceID, BankBuf(tb), MIDIOUTCAPS.STRUCTSIZE)=0
			
			retval=New MIDIOUTCAPS
			retval.uDeviceID=uDeviceID
			
			retval.wMid=PeekShort(tb,0)
			retval.wPid=PeekShort(tb,2)
			retval.vDriverVersion=PeekInt(tb,4)
			
			For i=0 Until MIDIOUTCAPS.MAXPNAMELEN
				retval.szPname:+Chr(PeekByte(tb,8+i))
			Next
			
			retval.wTechnology=PeekShort(tb,40)
			retval.wVoices=PeekShort(tb,42)
			retval.wNotes=PeekShort(tb,44)
			retval.wChannelMask=PeekShort(tb,46)
			retval.dwSupport=PeekInt(tb,48)
		EndIf

		tb=Null

		Return retval
	EndFunction	
EndType



gman(Posted 2005) [#13]
@Robert - very nice. reminds of the ol' ASCII fixed length format importing days :)


Ziltch(Posted 2005) [#14]
This is how I have done it.



This is the trick;

Local InfoOut:MidiOutCaps = New MidiOutCaps
Local MidiOutCapsPtr:Byte Ptr = Varptr(InfoOut.wMid)
Using the 1st field not the Type pointer.

And for looking at Cstrings in memory use String.FromCString(Varptr( StringPos))

Now we can use structures from external libs!!!!


Sweenie(Posted 2005) [#15]
@Robert and Gman.

Skip the loops and use String.FromBytes() instead.


gman(Posted 2005) [#16]
@Sweenie - how do you pull multiple bytes from the middle of a bank at one time? thx.


Sweenie(Posted 2005) [#17]
Like this...
retval.szPname = String.FromBytes(BankBuf(tb)+8,MIDIOUTCAPS.MAXPNAMELEN)



gman(Posted 2005) [#18]
lol... thx :) i got wrapped up in the peek functions. wasnt thinking outside the box!


taxlerendiosk(Posted 2005) [#19]
What about getting a structure out, returned from a DLL function? I'm attempting to write a Blitz method of using Winamp plugins (for especially esoteric music formats like NSF). Winamp plugin DLLs have a common function, winampGetInModule2(), which returns a custom struct containing references and stuff, I found a definition of it in another wrapper:

typedef struct 
{
	int version;				// module type (IN_VER)
	char *description;			// description of module, with version string

	HWND hMainWindow;			// winamp's main window (filled in by winamp)
	HINSTANCE hDllInstance;		// DLL instance handle (Also filled in by winamp)

	char *FileExtensions;		// "mp3\0Layer 3 MPEG\0mp2\0Layer 2 MPEG\0mpg\0Layer 1 MPEG\0"

... etc.

I can't work out how to even pull the first string out (description). Can anyone help?