Administrator
|
I'm wrapping the FMOD cross-platform audio library. The code is MIT and lives at http://smalltalkhub.com/#!/~SeanDeNigris/FMOD
======== Problem #1: ======== I'm calling into the FMOD library with: primStoreIsPlaying: channelHandle in: isPlayingHandle <primitive: #primitiveNativeCall module: #NativeBoostPlugin> "FMOD_RESULT FMOD_Channel_IsPlaying( FMOD_CHANNEL *channel, bool *isplaying);" ^ self nbCall: #(FMOD_RESULT FMOD_Channel_IsPlaying(NBExternalAddress channel, NBExternalAddress isPlayingHandle)). I call the above with: isPlaying | isPlaying | isPlaying := NBExternalAddress new. self primStoreIsPlaying: channel in: isPlaying. ^ isPlaying value > 0. isPlaying is always 0. The method works directly from C with: int isPlaying = 1; while (isPlaying) { FMOD_Channel_IsPlaying(channel, &isPlaying); } I also tried changing the callout signature to "... bool* isPlayingHandle)" and passing "isPlaying := true." instead of using the NBExternalAddress stuff. I have a few more questions, but this is the most pressing as it's holding up any further development. Thanks!
Cheers,
Sean |
We'll soon be able to do this in Pharo then :-) --- Philippe Back Dramatic Performance Improvements Mob: +32(0) 478 650 140 | Fax: +32 (0) 70 408 027
Blog: http://philippeback.be | Twitter: @philippeback
High Octane SPRL rue cour Boisacq 101 | 1301 Bierges | Belgium
Pharo Consortium Member - http://consortium.pharo.org/ Featured on the Software Process and Measurement Cast - http://spamcast.libsyn.com Sparx Systems Enterprise Architect and Ability Engineering EADocX Value Added Reseller On Thu, Nov 21, 2013 at 10:40 PM, Sean P. DeNigris <[hidden email]> wrote: I'm wrapping the FMOD cross-platform audio library. The code is MIT and lives |
In reply to this post by Sean P. DeNigris
On 21 November 2013 22:40, Sean P. DeNigris <[hidden email]> wrote: I'm wrapping the FMOD cross-platform audio library. The code is MIT and lives err.. again, you must pass an address where value will be stored, ^ self nbCall: #(FMOD_RESULT FMOD_Channel_IsPlaying( NBExternalAddress channel, NBExternalAddress * isPlayingHandle)). otherwise you passing NULL pointer, and i quite surprised it not segfaults when you call it like that (looks like they have a check in the library ) boolValueClass := NBExternalTypeValue ofType: 'bool'. "sure thing, creating anonymous class for each call is overkill, this should be done once, somewhere else" boolValue := boolValueClass new. self callTheThingWith: boolValue. boolValue value ifTrue: [... blah] (and this will work, assuming you have bool* in signature for this argument). I have a few more questions, but this is the most pressing as it's holding -- Best regards, Igor Stasenko. |
Administrator
|
hee hee... sorry... I don't understand enough of what's going on behind the scenes to adapt well. I reasoned that since last time, the signature was "Whatever**" and you said to make it "NBExternalAddress*", that therefore "Whatever*" (one less *) would be "NBExternalAddress" (also one less *). While we're here, I'll ask my other questions... 1. What's the differenct between <primitive: #primitiveNativeCall module: #NativeBoostPlugin> and the variant with error:? What does the second one buy you? 2. instead of returning useful objects, FMOD returns error codes and you pass a pointer to receive the object. - The first consequence is that I have to wrap all the calls e.g. "self processErrorCode: self primCreate." where processErrorCode: anInteger anInteger = 0 ifFalse: [ self error: 'FMOD returned error code ', anInteger asString ]. Is there a more graceful way to do that? - The second issue is how to create a Smalltalk object from the pointer. What I've been doing is: | soundHandle | soundHandle := NBExternalAddress new. self processErrorCode: (self primCreate: soundHandle on: system handle fromFile: file fullName). sound := FmodSystemSound on: soundHandle. Again, is there a better way? I thought to subclass NBExternalAddress, but evaluating "sound := FmodSystemSound new" to pass to the callout seemed a bit dirty i.e. it is not a invalid instance until initialized by the callout. I also played around with NBExternalObject, but couldn't get that to work either... Thanks for all the support. This is fun!!
Cheers,
Sean |
On 22 November 2013 01:23, Sean P. DeNigris <[hidden email]> wrote: Igor Stasenko wrote Historically, NB was first running on Squeak VM, which has no support for primitive error. Using extra #error: keyword in primitive is HIGHLY recommended, unless you want to
deal with some quite rare (but possible and thus very hard to track down) wrong error reporting. 2. instead of returning useful objects, FMOD returns error codes and you i doubt so.. since it is library API which dictates you to use it in certain way. The proper error handling never hurts (except from causing extra code bloat, of course :) - The second issue is how to create a Smalltalk object from the pointer. The better way is to subclass from NBExternalObject then which made exactly for such purposes, by holding an opaque handle to something (you don't care what is inside), and simplifies a lot of things. Thanks for all the support. This is fun!! You are very welcome.
-- Best regards, Igor Stasenko. |
Administrator
|
I made "NBExternalObject subclass: #FMOD_SYSTEM". I then tried to use it via: system := FMOD_SYSTEM new. err := self System_CreateNBExternalObject: system. With callout: ^ self nbCall: #(FMOD_RESULT FMOD_System_Create(NBExternalObject system)). For the argument type in the signature, for good measure I tried: 1. NBExternalObject 2. FMOD_SYSTEM 3. NBExternalAddress All three with zero, one, and two *'s after. The only one that didn't report some variety of "An instance of Xyz expected" was "FMOD_System_Create(FMOD_SYSTEM system)" for which the library returns an invalid argument error code. btw if anyone wants to play with it: 1. Gofer it smalltalkhubUser: 'SeanDeNigris' project: 'FMOD'; package: 'FMOD'; load. 2. Download the FMOD library for your platform: - windows - http://www.fmod.org/download/fmodstudio/api/Win/fmodstudioapi10208win-installer.exe - Mac - http://www.fmod.org/download/fmodstudio/api/Mac/fmodstudioapi10208mac-installer.dmg 3. Copy the library to "FileLocator imageDirectory / 'FMOD Programmers API/api/lowlevel/lib/libfmod.dylib'" The working example is: | sound | sound := FmodSound fromFile: '/path/to/file.mp3' asFileReference. [ sound play ] fork. The broken one described above is: "FMOD exampleNBExternalObject."
Cheers,
Sean |
On 22 November 2013 05:09, Sean P. DeNigris <[hidden email]> wrote: Igor Stasenko wrote yet again, you miss the right solution: you must pass a pointer to where value will be stored (since function doing exactly that). so you should do it like: 1. "NBExternalObject subclass: #FMOD_SYSTEM". 2. method to call the function will look like following: create: system ^ self nbCall: #(FMOD_RESULT FMOD_System_Create(FMOD_SYSTEM * system)). 3. and call it like following: system := FMOD_SYSTEM new. self handleError: (self create: system) ifOk: [ ^ system ] 3a. optionally, you want want to initialize it just after you know that you obtained correct handle, to do that, you could do following: FMOD_SYSTEM class>>new | system | system := super new. self handleError: (self create: system) ifOk: [ ^ self newWithHandle: system handle ] newWithHandle: aHandle system := super basicNew. system handle: aHandle. ^ system initialize (because it is important to set the handle first, like that you can actually initialize something more by using it, which you logically do, just after creating a new handle.
and note you must not call 'super initialize' then, because it will reset handle.) and i'm sure you can find more elegant solution :) btw if anyone wants to play with it: -- Best regards, Igor Stasenko. |
FMOD_SYSTEM class, because you can just subclass FmodSystem from NBExternalObject directly (and use it in signatures) and so it will be holding the handle, and naturally work as a representation of external entity. Also note that in this case FmodSystem is alias to C FMOD_SYSTEM*
so, for every function which expects FMOD_SYSTEM*, you should just use FmodSystem, and if it expects FMOD_SYSTEM**, you using FmodSystem* basically, same as if you would have: typedef FmodSystem FMOD_SYSTEM*; in C. On 22 November 2013 15:10, Igor Stasenko <[hidden email]> wrote:
-- Best regards, Igor Stasenko. FmodSystem.st (6K) Download Attachment |
In reply to this post by Igor Stasenko
I really think that NativeBoost MUST HAVE a decent documentation.
It is a central part of Pharo and Igor you should do something about it. If I would know I would have written a chapter on it but I cannot because I DO NOT KNOW. Stef
|
On Nov 23, 2013, at 9:07 AM, Stéphane Ducasse <[hidden email]> wrote:
We should focus on our clients and our clients are smart Pharoers that can learn fast.
|
However we should acknowledge here that using a FFI, any FFI , in any programming language has the mandatory requirement of knowing C. I certainly do believe that documenting Nativeboost is extremely important for helping people to port C libraries to pharo and making Pharo far more useful but we should not kid ourselves. Coding with Nativeboost even though its inside Pharo , even though its smalltalk syntax, even though it has the tools to make your life easier, its still C coding or even Assembly coding if you are brave enough to use the inline assembler. I want to help with documentation the problem is that I dont know a lot of stuff about Nativeboost besides the basic pointer things I did with NBOpenGL.
On Sat, Nov 23, 2013 at 10:10 AM, Stéphane Ducasse <[hidden email]> wrote:
|
In reply to this post by Igor Stasenko
Hi 2013/11/22 Igor Stasenko <[hidden email]>
Is it good idea to override (or create new) #nbCall: method to handle library common logic? (when any library function returns error code) |
On 23 November 2013 17:42, Denis Kudriashov <[hidden email]> wrote:
of course, the #nbCall: is just a convenience method and meant to be overridden if necessary. nbCall: fnSpec " you can override this method if you need to" ^ (self nbCalloutIn: thisContext sender) convention: self nbCallingConvention; function: fnSpec module: self nbLibraryNameOrHandle And nbCallingConvention, nbLibraryNameOrHandle is there for override as well. A more verbose form is: self nbCallout stdcall; options: #( - optDirectProxyFnAddress optAllowExternalAddressPtr); function: #( NBBootstrapUlong HeapAlloc (ulong heapHandle , 0 , SIZE_T size) ) module: #Kernel32 But be aware though, that this code usually invoked once during code generation, and later no method code is invoked because it just runs a primitive (primitiveNativeCall), and if it succeeds, it just returns with result, without running a method's body. So, nbcall can be changed/extended if you need extra thing at code generation stage, or you want to handle primitive error(s) differently. But not for handling things like function return values (which happens to be a library's error/success code). -- Best regards, Igor Stasenko. |
In reply to this post by Stéphane Ducasse
On 23 November 2013 09:07, Stéphane Ducasse <[hidden email]> wrote:
i agree. i need to invest into it.
-- Best regards, Igor Stasenko. |
JB and Damien could help. I can play the naive idiot and editor but I cannot be the driven force because the gap to learn will cost me too much energy. But this is on my todo to read the tutorial of laurent. Stef
|
Administrator
|
In reply to this post by Sean P. DeNigris
======== Problem #1: ======== I have the following callout: <primitive: #primitiveNativeCall module: #NativeBoostPlugin> "FMOD_RESULT FMOD_System_Init( FMOD_SYSTEM *system, int maxchannels, FMOD_INITFLAGS flags, void *extradriverdata);" ^ self nbCall: #(FMOD_RESULT System_Init(NBExternalAddress system, 32, 0, nil)). It works fine on Mac. On Windows, it returns some crazy error code that is not listed in the API. However, if I wrap the FMOD DLL in another DLL that just forwards all calls: FMOD_RESULT System_Init(FMOD_SYSTEM *system, int maxchannels, FMOD_INITFLAGS flags, void *extradriverdata) { return FMOD_System_Init(system, maxchannels, flags, extradriverdata); } When I call out to the wrapper DLL, it works! Does that provide a clue as to what's going wrong when calling from NB? Other info: - Other dll functions succeed, so there is communication with the library. In fact, if I proceed past that first error, a sound starts to play... but then the VM crashes... ======== Problem #2: ======== (much less important) I wanted to be able to bundle the DLL with the image so one doesn't have to copy it into the VM folder. If I use the full path for the wrapper DLL described above, it is found, but when it calls fmodL.dll, which is in the same directory, it can't be found. I could only get it to work if at least fmodL.dll is in the VM plugins folder. Is there a way to specify more search locations for dynamic libraries from the image side? Thanks!
Cheers,
Sean |
On 25 November 2013 21:53, Sean P. DeNigris <[hidden email]> wrote: Sean P. DeNigris wrote This seems like a calling convention issue. By default, unless you specify, NB using cdecl calling convention, but on windows, many libs (especially kernel) using stdcall convention. Check how the library is built and which call convention it uses. Or just try changing it and see if it solves the problem. Other info: No, you can do it yourself in a form of: self nbCall: ... module: (self searchAndLoadLibrary)
-- Best regards, Igor Stasenko. |
In reply to this post by Sean P. DeNigris
Hi Sean, 2013/11/25 Sean P. DeNigris <[hidden email]> Sean P. DeNigris wrote could it be a calling convention problem? cdecl, ...
I do not know if one can add more search locations. But, you can rebuild the full path from image side: Smalltalk imageDirectory / 'lib.dll'
Cheers, Luc
|
Free forum by Nabble | Edit this page |