www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - C callbacks getting a value of 0! Bug in D?

reply Johnson Jones <JJ Dynomite.com> writes:
Trying to set a callback for portaudio and it's seeing zero for 
the value passed.

Pa_OpenStream(&stream, input, output, sampleRate, cast(ulong)0, 
cast(PaStreamFlags)(PaStreamFlags.NoFlag + 
0*PaStreamFlags.PrimeOutputBuffersUsingStreamCallback),
									 cast(PaStreamCallback)(a,b,c,d,e,f){
		callbackCalled = true;
										 return 0;
									 }, null);

I am using a debug build of portaudio that prints out all the 
parameters before anything else, so it's not an issue with 
portaudio.

I've tried passing a normal function:

__gshared int sawtooth(const(void)* inputBuffer, void* 
outputBuffer, ulong framesPerBuffer, 
const(PaStreamCallbackTimeInfo)* timeInfo, PaStreamCallbackFlags 
statusFlags, void *userData)
{ return 0; }

and passing it as &sawtooth.

with the same issue.

I've tried changing the calling convention and using __gshared 
but the value is always 0. It seems other values are 0 too and 
can't seem to get any value in to the callback(Even a random one).


The output essentially always looks like this:


Opening Stream!
Pa_OpenStream called:
         PaStream** stream: 0x00823EA0
         PaStreamParameters *inputParameters: NULL
         PaStreamParameters *outputParameters: 0x02C6C1C0
         PaDeviceIndex outputParameters->device: 3
         int outputParameters->channelCount: 4
         PaSampleFormat outputParameters->sampleFormat: 1
         PaTime outputParameters->suggestedLatency: 0.135000
         void *outputParameters->hostApiSpecificStreamInfo: 
0x00000000
         double sampleRate: 44100
         unsigned long framesPerBuffer: 256
         PaStreamFlags streamFlags: 0x0
         PaStreamCallback *streamCallback: 0x00000000
         void *userData: 0x00000000
Pa_GetSampleSize called:
         PaSampleFormat format: 8
Pa_GetSampleSize returned:
         int: 2
Pa_GetSampleSize called:
         PaSampleFormat format: 8
Pa_GetSampleSize returned:
         int: 2
Pa_GetSampleSize called:
         PaSampleFormat format: 1
Pa_GetSampleSize returned:
         int: 4
Pa_GetSampleSize called:
         PaSampleFormat format: 8
Pa_GetSampleSize returned:
         int: 2
Pa_GetSampleSize called:
         PaSampleFormat format: 8
Pa_GetSampleSize returned:
         int: 2
Pa_OpenStream returned:
         *(PaStream** stream): 0x03BEAD50
         PaError: 0 ( Success )


everything seems correct except:

         PaStreamCallback *streamCallback: 0x00000000
         void *userData: 0x00000000

You can find the full code at

https://forum.dlang.org/thread/lkbswgpsgxynhfyzwfqz forum.dlang.org			


This is either a bug in D, an issue with calling conventions, or 
how one passes the data.


The original portaudio open stream function, so you can see that 
it simply prints it's arguments:

PaError Pa_OpenStream( PaStream** stream,
                        const PaStreamParameters *inputParameters,
                        const PaStreamParameters *outputParameters,
                        double sampleRate,
                        unsigned long framesPerBuffer,
                        PaStreamFlags streamFlags,
                        PaStreamCallback *streamCallback,
                        void *userData )
{
     PaError result;
     PaUtilHostApiRepresentation *hostApi = 0;
     PaDeviceIndex hostApiInputDevice = paNoDevice, 
hostApiOutputDevice = paNoDevice;
     PaStreamParameters hostApiInputParameters, 
hostApiOutputParameters;
     PaStreamParameters *hostApiInputParametersPtr, 
*hostApiOutputParametersPtr;


#ifdef PA_LOG_API_CALLS
     PA_LOGAPI_ENTER_PARAMS( "Pa_OpenStream" );
     PA_LOGAPI(("\tPaStream** stream: 0x%p\n", stream ));

     if( inputParameters == NULL ){
         PA_LOGAPI(("\tPaStreamParameters *inputParameters: 
NULL\n" ));
     }else{
         PA_LOGAPI(("\tPaStreamParameters *inputParameters: 
0x%p\n", inputParameters ));
         PA_LOGAPI(("\tPaDeviceIndex inputParameters->device: 
%d\n", inputParameters->device ));
         PA_LOGAPI(("\tint inputParameters->channelCount: %d\n", 
inputParameters->channelCount ));
         PA_LOGAPI(("\tPaSampleFormat 
inputParameters->sampleFormat: %d\n", 
inputParameters->sampleFormat ));
         PA_LOGAPI(("\tPaTime inputParameters->suggestedLatency: 
%f\n", inputParameters->suggestedLatency ));
         PA_LOGAPI(("\tvoid 
*inputParameters->hostApiSpecificStreamInfo: 0x%p\n", 
inputParameters->hostApiSpecificStreamInfo ));
     }

     if( outputParameters == NULL ){
         PA_LOGAPI(("\tPaStreamParameters *outputParameters: 
NULL\n" ));
     }else{
         PA_LOGAPI(("\tPaStreamParameters *outputParameters: 
0x%p\n", outputParameters ));
         PA_LOGAPI(("\tPaDeviceIndex outputParameters->device: 
%d\n", outputParameters->device ));
         PA_LOGAPI(("\tint outputParameters->channelCount: %d\n", 
outputParameters->channelCount ));
         PA_LOGAPI(("\tPaSampleFormat 
outputParameters->sampleFormat: %d\n", 
outputParameters->sampleFormat ));
         PA_LOGAPI(("\tPaTime outputParameters->suggestedLatency: 
%f\n", outputParameters->suggestedLatency ));
         PA_LOGAPI(("\tvoid 
*outputParameters->hostApiSpecificStreamInfo: 0x%p\n", 
outputParameters->hostApiSpecificStreamInfo ));
     }

     PA_LOGAPI(("\tdouble sampleRate: %g\n", sampleRate ));
     PA_LOGAPI(("\tunsigned long framesPerBuffer: %d\n", 
framesPerBuffer ));
     PA_LOGAPI(("\tPaStreamFlags streamFlags: 0x%x\n", streamFlags 
));
     PA_LOGAPI(("\tPaStreamCallback *streamCallback: 0x%p\n", 
streamCallback ));
     PA_LOGAPI(("\tvoid *userData: 0x%p\n", userData ));
#endif


and this is how I define it in the code(which you can see if you 
follow the link):

PaError function(PaStream** stream, const PaStreamParameters 
*inputParameters, const PaStreamParameters *outputParameters, 
double sampleRate, ulong framesPerBuffer, PaStreamFlags 
streamFlags, PaStreamCallback streamCallback, void *userData) 
Pa_OpenStream;

I can play audio if I do a hard write, but obviously that is 
generally useless.

This seems like it might be a bug in D in that the last 
parameters are invalid. user_data, which is passing a simple 
pointer is also 0, when it isn't.

Even trying something like

PaError function(PaStream** stream, const PaStreamParameters 
*inputParameters, const PaStreamParameters *outputParameters, 
double sampleRate, ulong framesPerBuffer, PaStreamFlags 
streamFlags, size_t streamCallback, size_t userData) 
Pa_OpenStream;

and passing numbers still shows zero values!


Any ideas?
Aug 27
parent reply Johnson Jones <JJ Dynomite.com> writes:
Looking at the assembly shows something like this:

0041ea98  push 0x0
0041ea9a  push 0x0
0041ea9c  push 0x0
0041ea9e  push dword 0x100
0041eaa3  mov ecx, [typeid(PaStreamParameters)+0xe36fc (0x80d4cc)]
0041eaa9  mov eax, [fs:0x2c]
0041eaaf  mov edx, [eax+ecx*4]
0041eab2  push dword [edx+0x1c]
0041eab8  push dword [edx+0x18]
0041eabe  push dword [ebp-0x54]
0041eac1  push dword [ebp-0x5c]
0041eac4  mov ebx, PA.stream (0x823f30)
0041eac9  push ebx
0041eaca  call dword near [Pa_OpenStream (0x823f18)]

I noticed that those 0's were the values being fed in to the 
function.

I remember converting c_ulong's to ulong's and that they were 
probably uint's in D. Converting those fixed the problem and the 
callback is now called! I converted all the ulongs to uint's but 
there were a few longs and I don't know if they are c_longs or 
d_longs...

Anyways, At least I'm on the right track.
Aug 27
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 8/27/17 10:17 PM, Johnson Jones wrote:
 Looking at the assembly shows something like this:
 
 0041ea98  push 0x0
 0041ea9a  push 0x0
 0041ea9c  push 0x0
 0041ea9e  push dword 0x100
 0041eaa3  mov ecx, [typeid(PaStreamParameters)+0xe36fc (0x80d4cc)]
 0041eaa9  mov eax, [fs:0x2c]
 0041eaaf  mov edx, [eax+ecx*4]
 0041eab2  push dword [edx+0x1c]
 0041eab8  push dword [edx+0x18]
 0041eabe  push dword [ebp-0x54]
 0041eac1  push dword [ebp-0x5c]
 0041eac4  mov ebx, PA.stream (0x823f30)
 0041eac9  push ebx
 0041eaca  call dword near [Pa_OpenStream (0x823f18)]
 
 I noticed that those 0's were the values being fed in to the function.
 
 I remember converting c_ulong's to ulong's and that they were probably 
 uint's in D. Converting those fixed the problem and the callback is now 
 called! I converted all the ulongs to uint's but there were a few longs 
 and I don't know if they are c_longs or d_longs...
 
 Anyways, At least I'm on the right track.
 
For C/C++ interaction, always use c_... types if they are available. The idea is both that they will be correctly defined for the width, and also it will mangle correctly for C++ compilers (yes, long and int are mangled differently even when they are the same thing). -Steve
Aug 28
next sibling parent reply Johnson Jones <JJ Dynomite.com> writes:
On Monday, 28 August 2017 at 21:35:27 UTC, Steven Schveighoffer 
wrote:
 On 8/27/17 10:17 PM, Johnson Jones wrote:
 [...]
For C/C++ interaction, always use c_... types if they are available. The idea is both that they will be correctly defined for the width, and also it will mangle correctly for C++ compilers (yes, long and int are mangled differently even when they are the same thing). -Steve
and where are these c_ types defined? The reason I replaced them was precisely because D was not finding them.
Aug 28
parent reply Moritz Maxeiner <moritz ucworks.org> writes:
On Monday, 28 August 2017 at 22:21:18 UTC, Johnson Jones wrote:
 On Monday, 28 August 2017 at 21:35:27 UTC, Steven Schveighoffer 
 wrote:
 On 8/27/17 10:17 PM, Johnson Jones wrote:
 [...]
For C/C++ interaction, always use c_... types if they are available. The idea is both that they will be correctly defined for the width, and also it will mangle correctly for C++ compilers (yes, long and int are mangled differently even when they are the same thing). -Steve
and where are these c_ types defined? The reason I replaced them was precisely because D was not finding them.
core.stdc.config , which unfortunately doesn't appear in the online documentation AFAICT (something that ought to be fixed). A common workaround is to use pattern searching tools like grep if you know the phrase to look for: $ grep -Er c_long /path/to/imports , or in this case, since these things are usually done with aliases: $ grep -Er 'alias\s+\w*\s+c_long' /path/to/imports
Aug 28
parent Johnson Jones <JJ Dynomite.com> writes:
On Monday, 28 August 2017 at 22:41:56 UTC, Moritz Maxeiner wrote:
 On Monday, 28 August 2017 at 22:21:18 UTC, Johnson Jones wrote:
 On Monday, 28 August 2017 at 21:35:27 UTC, Steven 
 Schveighoffer wrote:
 [...]
and where are these c_ types defined? The reason I replaced them was precisely because D was not finding them.
core.stdc.config , which unfortunately doesn't appear in the online documentation AFAICT (something that ought to be fixed). A common workaround is to use pattern searching tools like grep if you know the phrase to look for: $ grep -Er c_long /path/to/imports , or in this case, since these things are usually done with aliases: $ grep -Er 'alias\s+\w*\s+c_long' /path/to/imports
Thanks. I copied over stuff from the bindings and from the original header and I guess I missed the import.
Aug 28
prev sibling parent reply Johnson Jones <JJ Dynomite.com> writes:
On Monday, 28 August 2017 at 21:35:27 UTC, Steven Schveighoffer 
wrote:
 On 8/27/17 10:17 PM, Johnson Jones wrote:
 Looking at the assembly shows something like this:
 
 0041ea98  push 0x0
 0041ea9a  push 0x0
 0041ea9c  push 0x0
 0041ea9e  push dword 0x100
 0041eaa3  mov ecx, [typeid(PaStreamParameters)+0xe36fc 
 (0x80d4cc)]
 0041eaa9  mov eax, [fs:0x2c]
 0041eaaf  mov edx, [eax+ecx*4]
 0041eab2  push dword [edx+0x1c]
 0041eab8  push dword [edx+0x18]
 0041eabe  push dword [ebp-0x54]
 0041eac1  push dword [ebp-0x5c]
 0041eac4  mov ebx, PA.stream (0x823f30)
 0041eac9  push ebx
 0041eaca  call dword near [Pa_OpenStream (0x823f18)]
 
 I noticed that those 0's were the values being fed in to the 
 function.
 
 I remember converting c_ulong's to ulong's and that they were 
 probably uint's in D. Converting those fixed the problem and 
 the callback is now called! I converted all the ulongs to 
 uint's but there were a few longs and I don't know if they are 
 c_longs or d_longs...
 
 Anyways, At least I'm on the right track.
 
For C/C++ interaction, always use c_... types if they are available. The idea is both that they will be correctly defined for the width, and also it will mangle correctly for C++ compilers (yes, long and int are mangled differently even when they are the same thing). -Steve
In portaudio, this doesn't seem to be the case. I changed all the longs to ints and ran my code in x64 and it worked fine. It may just be that the stuff that uses long is not used in my code. In port audio I see it using unsigned long and so this should be 64-bits in x64. Surprised it worked. Maybe conversion is taking place or the high bits of the long are 0'ed and so there is not much difference. Anyways, I guess I'll deal with any of those bugs when I run in to them, if they exist.
Aug 28
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 8/28/17 7:47 PM, Johnson Jones wrote:
 On Monday, 28 August 2017 at 21:35:27 UTC, Steven Schveighoffer wrote:
 On 8/27/17 10:17 PM, Johnson Jones wrote:
 Looking at the assembly shows something like this:

 0041ea98  push 0x0
 0041ea9a  push 0x0
 0041ea9c  push 0x0
 0041ea9e  push dword 0x100
 0041eaa3  mov ecx, [typeid(PaStreamParameters)+0xe36fc (0x80d4cc)]
 0041eaa9  mov eax, [fs:0x2c]
 0041eaaf  mov edx, [eax+ecx*4]
 0041eab2  push dword [edx+0x1c]
 0041eab8  push dword [edx+0x18]
 0041eabe  push dword [ebp-0x54]
 0041eac1  push dword [ebp-0x5c]
 0041eac4  mov ebx, PA.stream (0x823f30)
 0041eac9  push ebx
 0041eaca  call dword near [Pa_OpenStream (0x823f18)]

 I noticed that those 0's were the values being fed in to the function.

 I remember converting c_ulong's to ulong's and that they were 
 probably uint's in D. Converting those fixed the problem and the 
 callback is now called! I converted all the ulongs to uint's but 
 there were a few longs and I don't know if they are c_longs or 
 d_longs...

 Anyways, At least I'm on the right track.
For C/C++ interaction, always use c_... types if they are available. The idea is both that they will be correctly defined for the width, and also it will mangle correctly for C++ compilers (yes, long and int are mangled differently even when they are the same thing).
In portaudio, this doesn't seem to be the case. I changed all the longs to ints and ran my code in x64 and it worked fine. It may just be that the stuff that uses long is not used in my code. In port audio I see it using unsigned long and so this should be 64-bits in x64. Surprised it worked. Maybe conversion is taking place or the high bits of the long are 0'ed and so there is not much difference.
Then I think possibly the port audio bindings are not correct. It's also possible that long is not 64-bit even on the platform you are using. Finally, it's also possible that the D compiler is not generating the call correctly. When I test on my mac, c_long is 8-bytes. I'm not sure what it is in your case, try this code and see what happens: import core.stdc.config; pragma(msg, c_long.sizeof); // prints 8 on my box. And in c: #include <stdio.h> int main() { printf("%lu\n", sizeof(long)); // also prints 8 on my box return 0; } They should match. If they don't, that's a D bug (in either core.stdc.config or the compiler, I'm not sure which). If they do, then one of the other two possibilities is happening, and I would lean towards the bindings being incorrect. -Steve
Aug 28
parent reply Johnson Jones <JJ Dynomite.com> writes:
On Tuesday, 29 August 2017 at 00:42:45 UTC, Steven Schveighoffer 
wrote:
 On 8/28/17 7:47 PM, Johnson Jones wrote:
 [...]
Then I think possibly the port audio bindings are not correct. It's also possible that long is not 64-bit even on the platform you are using. Finally, it's also possible that the D compiler is not generating the call correctly. When I test on my mac, c_long is 8-bytes. I'm not sure what it is in your case, try this code and see what happens: import core.stdc.config; pragma(msg, c_long.sizeof); // prints 8 on my box. And in c: #include <stdio.h> int main() { printf("%lu\n", sizeof(long)); // also prints 8 on my box return 0; } They should match. If they don't, that's a D bug (in either core.stdc.config or the compiler, I'm not sure which). If they do, then one of the other two possibilities is happening, and I would lean towards the bindings being incorrect. -Steve
import core.stdc.config; pragma(msg, c_long.sizeof); prints 4UL both on x64 and x86 and and C: void foo() { int dummy; switch (dummy) { case sizeof(long) : case sizeof(long) : break; } } produces 4 on both x86 and x64. So, I'm not sure how you are getting 8.
Aug 28
next sibling parent reply Moritz Maxeiner <moritz ucworks.org> writes:
On Tuesday, 29 August 2017 at 01:34:40 UTC, Johnson Jones wrote:
 [...]


 produces 4 on both x86 and x64. So, I'm not sure how you are 
 getting 8.
There are different 64bit data models [1] and it seems your platform uses LLP64, which uses 32bit longs. Am I correct in assuming you're on Windows (as they are the only major modern platform that I'm aware of that made this choice)? [1] https://en.wikipedia.org/wiki/64-bit_computing#64-bit_data_models
Aug 28
parent reply Johnson Jones <JJ Dynomite.com> writes:
On Tuesday, 29 August 2017 at 01:56:43 UTC, Moritz Maxeiner wrote:
 On Tuesday, 29 August 2017 at 01:34:40 UTC, Johnson Jones wrote:
 [...]


 produces 4 on both x86 and x64. So, I'm not sure how you are 
 getting 8.
There are different 64bit data models [1] and it seems your platform uses LLP64, which uses 32bit longs. Am I correct in assuming you're on Windows (as they are the only major modern platform that I'm aware of that made this choice)? [1] https://en.wikipedia.org/wiki/64-bit_computing#64-bit_data_models
Yes. I found this, which gives a map for all the types: https://dlang.org/spec/interfaceToC.html Seems only long and ulong are issues.
Aug 28
parent Moritz Maxeiner <moritz ucworks.org> writes:
On Tuesday, 29 August 2017 at 02:47:34 UTC, Johnson Jones wrote:
 [...]

 Seems only long and ulong are issues.
With respect to the currently major platforms you can reasonable expect software to run on, yes. Just don't try to use D on something with e.g. 32 bit C shorts unless you bind to it via c_short.
Aug 28
prev sibling next sibling parent Mike Parker <aldacron gmail.com> writes:
On Tuesday, 29 August 2017 at 01:34:40 UTC, Johnson Jones wrote:

 import core.stdc.config;
 pragma(msg, c_long.sizeof);

 prints 4UL

 both on x64 and x86

 and and C:

 void foo()
 {
 	int dummy;
 	switch (dummy) {
 	case sizeof(long) :
 	case sizeof(long) :
 		break;
 	}
 }


 produces 4 on both x86 and x64. So, I'm not sure how you are 
 getting 8.
It's because you're on Windows. There, long/ulong are 4 bytes in both 32- and 64-bit. On Linux/Mac/*BSD, they're 4 in 32-bit and 8 in 64-bit. This is why we have c_long and c_ulong, to hide those differences.
Aug 28
prev sibling parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 8/28/17 9:34 PM, Johnson Jones wrote:

 produces 4 on both x86 and x64. So, I'm not sure how you are getting 8.
Yes, this is exactly why you should use c_long and c_ulong, because just using int makes it not portable to other systems. -Steve
Aug 29