UFFI C basic types passed-by-reference for output
What is the recommended way for a C basic type to be passed-by-reference to function wanting to use it for output. For example 'width' & 'height' here in this library unction... int FPDF_GetPageSizeByIndex(FPDF_DOCUMENT document, int page_index, double* width, double* height); void mytestGetPageSizeByIndex(doc) { double width, height; FPDF_GetPageSizeByIndex(doc, 0, &width, &height); printf("width=%f, height=%f\n", width, height); } gives... width=200.000000, height=200.000000 In Pharo I'm trying... FFIOpaqueObject subclass: #FPDF_DOCUMENT FPDF_GetPageSizeByIndex__document: document page_index: page_index width: width height: height ^self ffiCall: #(int FPDF_GetPageSizeByIndex( FPDF_DOCUMENT *document, int page_index, FFIFloat64 * width, FFIFloat64 * height)) testGetPageSizeByIndex | document page_index width height result| PDFium FPDF_InitLibrary. document := PDFium FPDF_LoadDocument__file_path: helloPdf password: ''. width := 0.0. height := 0.0. page_index := 0. "Its zero based" result := PDFium FPDF_GetPageSizeByIndex__document: document page_index: 0 width: width height: height. PDFium FPDF_CloseDocument__document: document. PDFium FPDF_DestroyLibrary. self assert: document isNull not. "Document opened okay, and btw this works for a different pageCount test" self assert: result > 0. "Non-zero for success. 0 for error (document or page not found)" self halt. and at the halt the Inspector shows... result = 1 width = 0.0 height = 0.0 cheers -ben
On 15 Nov 2017, at 02:05, Ben Coman <btc@openInWorld.com> wrote:
What is the recommended way for a C basic type to be passed-by-reference to function wanting to use it for output. For example 'width' & 'height' here in this library unction...
int FPDF_GetPageSizeByIndex(FPDF_DOCUMENT document, int page_index, double* width, double* height);
void mytestGetPageSizeByIndex(doc) { double width, height; FPDF_GetPageSizeByIndex(doc, 0, &width, &height); printf("width=%f, height=%f\n", width, height); }
gives... width=200.000000, height=200.000000
In Pharo I'm trying...
FFIOpaqueObject subclass: #FPDF_DOCUMENT
FPDF_GetPageSizeByIndex__document: document page_index: page_index width: width height: height ^self ffiCall: #(int FPDF_GetPageSizeByIndex( FPDF_DOCUMENT *document, int page_index, FFIFloat64 * width, FFIFloat64 * height))
testGetPageSizeByIndex | document page_index width height result| PDFium FPDF_InitLibrary. document := PDFium FPDF_LoadDocument__file_path: helloPdf password: ''. width := 0.0. height := 0.0. page_index := 0. "Its zero based" result := PDFium FPDF_GetPageSizeByIndex__document: document page_index: 0 width: width height: height. PDFium FPDF_CloseDocument__document: document. PDFium FPDF_DestroyLibrary. self assert: document isNull not. "Document opened okay, and btw this works for a different pageCount test" self assert: result > 0. "Non-zero for success. 0 for error (document or page not found)" self halt.
and at the halt the Inspector shows... result = 1 width = 0.0 height = 0.0
no, that will not work :) what you need to do here is to pass a âbufferâ to contain the width and height: testGetPageSizeByIndex | document page_index widthBuffer heightBuffer width height result| PDFium FPDF_InitLibrary. document := PDFium FPDF_LoadDocument__file_path: helloPdf password: ''. widthBuffer := ByteArray new: (FFIFloat64 typeSize). heightBuffer := ByteArray new: (FFIFloat64 typeSize). page_index := 0. "Its zero based" result := PDFium FPDF_GetPageSizeByIndex__document: document page_index: 0 width: widthBuffer height: heightBuffer. width := widthBuffer doubleAt: 1. height := heightBuffer doubleAt: 1. PDFium FPDF_CloseDocument__document: document. PDFium FPDF_DestroyLibrary. self assert: document isNull not. "Document opened okay, and btw this works for a different pageCount test" self assert: result > 0. "Non-zero for success. 0 for error (document or page not found)" self halt. side note: I do not recommend using the UFFI type in declarations. In the long way is worst for comprehension and maintainability. This thingy: ^self ffiCall: #(int FPDF_GetPageSizeByIndex( FPDF_DOCUMENT *document, int page_index, FFIFloat64 * width, FFIFloat64 * height)) would be better as in original version: ^self ffiCall: #(int FPDF_GetPageSizeByIndex( FPDF_DOCUMENT *document, int page_index, double * width, double * height)) Esteban
cheers -ben
On 15 November 2017 at 17:27, Esteban Lorenzano <estebanlm@gmail.com> wrote:
On 15 Nov 2017, at 02:05, Ben Coman <btc@openInWorld.com> wrote:
What is the recommended way for a C basic type to be passed-by-reference to function wanting to use it for output. For example 'width' & 'height' here in this library unction...
int FPDF_GetPageSizeByIndex(FPDF_DOCUMENT document, int page_index, double* width, double* height);
void mytestGetPageSizeByIndex(doc) { double width, height; FPDF_GetPageSizeByIndex(doc, 0, &width, &height); printf("width=%f, height=%f\n", width, height); }
gives... width=200.000000, height=200.000000
In Pharo I'm trying...
FFIOpaqueObject subclass: #FPDF_DOCUMENT
FPDF_GetPageSizeByIndex__document: document page_index: page_index width: width height: height ^self ffiCall: #(int FPDF_GetPageSizeByIndex( FPDF_DOCUMENT *document, int page_index, FFIFloat64 * width, FFIFloat64 * height))
testGetPageSizeByIndex | document page_index width height result| PDFium FPDF_InitLibrary. document := PDFium FPDF_LoadDocument__file_path: helloPdf password: ''. width := 0.0. height := 0.0. page_index := 0. "Its zero based" result := PDFium FPDF_GetPageSizeByIndex__document: document page_index: 0 width: width height: height. PDFium FPDF_CloseDocument__document: document. PDFium FPDF_DestroyLibrary. self assert: document isNull not. "Document opened okay, and btw this works for a different pageCount test" self assert: result > 0. "Non-zero for success. 0 for error (document or page not found)" self halt.
and at the halt the Inspector shows... result = 1 width = 0.0 height = 0.0
no, that will not work :) what you need to do here is to pass a âbufferâ to contain the width and height:
testGetPageSizeByIndex | document page_index widthBuffer heightBuffer width height result| PDFium FPDF_InitLibrary. document := PDFium FPDF_LoadDocument__file_path: helloPdf password: ''. widthBuffer := ByteArray new: (FFIFloat64 typeSize). heightBuffer := ByteArray new: (FFIFloat64 typeSize).
Thanks Esteban. That worked. However I needed a minor tweak... widthBuffer := ByteArray new: (FFIFloat64 new typeSize). or... widthBuffer := ByteArray new: (FFIFloat64 externalTypeSize). Now it would be nice to do... widthBuffer := FFIFloat64 newBuffer. which could be implemented... FFIExternalType class >> newBuffer. ^ByteArray new: (self externalTypeSize) testGetPageSizeByIndex | document page_index widthBuffer heightBuffer width height result| PDFium FPDF_InitLibrary. document := PDFium FPDF_LoadDocument__file_path: helloPdf password: ''. widthBuffer := FFIFloat64 buffer. heightBuffer := FFIFloat64 buffer. page_index := 0. "Its zero based" result := PDFium FPDF_GetPageSizeByIndex__document: document page_index: 0 width: widthBuffer height: heightBuffer. width := widthBuffer doubleAt: 1. height := heightBuffer doubleAt: 1. btw, a broader curiousity even though this is likely set in stone and not even your personal choice - why "FFIFloat64" and not "FFIDouble" ? widthBuffer := FFIDouble newBuffer. would be a nice match to the callout declaration. Would it be overkill to have in Pharo an empty FFIDouble subclassed from FFIFloat64 ? the superclass being explicit about the format, while the subclass provides a nice identifier for users?
page_index := 0. "Its zero based" result := PDFium FPDF_GetPageSizeByIndex__document: document page_index: 0 width: widthBuffer height: heightBuffer. width := widthBuffer doubleAt: 1. height := heightBuffer doubleAt: 1.
PDFium FPDF_CloseDocument__document: document.
PDFium FPDF_DestroyLibrary. self assert: document isNull not. "Document opened okay, and btw this works for a different pageCount test" self assert: result > 0. "Non-zero for success. 0 for error (document or page not found)" self halt.
side note: I do not recommend using the UFFI type in declarations. In the long way is worst for comprehension and maintainability. This thingy:
^self ffiCall: #(int FPDF_GetPageSizeByIndex( FPDF_DOCUMENT *document, int page_index, FFIFloat64 * width, FFIFloat64 * height))
would be better as in original version:
^self ffiCall: #(int FPDF_GetPageSizeByIndex( FPDF_DOCUMENT *document, int page_index, double * width, double * height))
Thanks for the heads up. cheers -ben
On 15 Nov 2017, at 09:36, Ben Coman <btc@openInWorld.com> wrote:
On 15 November 2017 at 17:27, Esteban Lorenzano <estebanlm@gmail.com <mailto:estebanlm@gmail.com>> wrote:
On 15 Nov 2017, at 02:05, Ben Coman <btc@openInWorld.com> wrote:
What is the recommended way for a C basic type to be passed-by-reference to function wanting to use it for output. For example 'width' & 'height' here in this library unction...
int FPDF_GetPageSizeByIndex(FPDF_DOCUMENT document, int page_index, double* width, double* height);
void mytestGetPageSizeByIndex(doc) { double width, height; FPDF_GetPageSizeByIndex(doc, 0, &width, &height); printf("width=%f, height=%f\n", width, height); }
gives... width=200.000000, height=200.000000
In Pharo I'm trying...
FFIOpaqueObject subclass: #FPDF_DOCUMENT
FPDF_GetPageSizeByIndex__document: document page_index: page_index width: width height: height ^self ffiCall: #(int FPDF_GetPageSizeByIndex( FPDF_DOCUMENT *document, int page_index, FFIFloat64 * width, FFIFloat64 * height))
testGetPageSizeByIndex | document page_index width height result| PDFium FPDF_InitLibrary. document := PDFium FPDF_LoadDocument__file_path: helloPdf password: ''. width := 0.0. height := 0.0. page_index := 0. "Its zero based" result := PDFium FPDF_GetPageSizeByIndex__document: document page_index: 0 width: width height: height. PDFium FPDF_CloseDocument__document: document. PDFium FPDF_DestroyLibrary. self assert: document isNull not. "Document opened okay, and btw this works for a different pageCount test" self assert: result > 0. "Non-zero for success. 0 for error (document or page not found)" self halt.
and at the halt the Inspector shows... result = 1 width = 0.0 height = 0.0
no, that will not work :) what you need to do here is to pass a âbufferâ to contain the width and height:
testGetPageSizeByIndex | document page_index widthBuffer heightBuffer width height result| PDFium FPDF_InitLibrary. document := PDFium FPDF_LoadDocument__file_path: helloPdf password: ''. widthBuffer := ByteArray new: (FFIFloat64 typeSize). heightBuffer := ByteArray new: (FFIFloat64 typeSize).
Thanks Esteban. That worked. However I needed a minor tweak... widthBuffer := ByteArray new: (FFIFloat64 new typeSize). or... widthBuffer := ByteArray new: (FFIFloat64 externalTypeSize).
Now it would be nice to do... widthBuffer := FFIFloat64 newBuffer.
you know, I thought the same :) but since Iâm officially on holiday I didnât do it (yet). If you want to add an issue and send a PR, it would be perfect :)
which could be implemented... FFIExternalType class >> newBuffer. ^ByteArray new: (self externalTypeSize)
testGetPageSizeByIndex | document page_index widthBuffer heightBuffer width height result| PDFium FPDF_InitLibrary. document := PDFium FPDF_LoadDocument__file_path: helloPdf password: ''. widthBuffer := FFIFloat64 buffer. heightBuffer := FFIFloat64 buffer. page_index := 0. "Its zero based" result := PDFium FPDF_GetPageSizeByIndex__document: document page_index: 0 width: widthBuffer height: heightBuffer. width := widthBuffer doubleAt: 1. height := heightBuffer doubleAt: 1.
btw, a broader curiousity even though this is likely set in stone and not even your personal choice - why "FFIFloat64" and not "FFIDouble" ? widthBuffer := FFIDouble newBuffer. would be a nice match to the callout declaration.
because the ABI defines float64 as the right name, heh. and yes, it was not my personal choice ;)
Would it be overkill to have in Pharo an empty FFIDouble subclassed from FFIFloat64 ? the superclass being explicit about the format, while the subclass provides a nice identifier for users?
maybe it is overkilling. Because also⦠where do you put the limit? (doubles, floats, longs, etc⦠?) Esteban
page_index := 0. "Its zero based" result := PDFium FPDF_GetPageSizeByIndex__document: document page_index: 0 width: widthBuffer height: heightBuffer. width := widthBuffer doubleAt: 1. height := heightBuffer doubleAt: 1. PDFium FPDF_CloseDocument__document: document. PDFium FPDF_DestroyLibrary. self assert: document isNull not. "Document opened okay, and btw this works for a different pageCount test" self assert: result > 0. "Non-zero for success. 0 for error (document or page not found)" self halt.
side note: I do not recommend using the UFFI type in declarations. In the long way is worst for comprehension and maintainability. This thingy:
^self ffiCall: #(int FPDF_GetPageSizeByIndex( FPDF_DOCUMENT *document, int page_index, FFIFloat64 * width, FFIFloat64 * height))
would be better as in original version:
^self ffiCall: #(int FPDF_GetPageSizeByIndex( FPDF_DOCUMENT *document, int page_index, double * width, double * height))
Thanks for the heads up.
cheers -ben
On 15 November 2017 at 20:47, Esteban Lorenzano <estebanlm@gmail.com> wrote:
On 15 Nov 2017, at 09:36, Ben Coman <btc@openInWorld.com> wrote:
On 15 November 2017 at 17:27, Esteban Lorenzano <estebanlm@gmail.com> wrote:
On 15 Nov 2017, at 02:05, Ben Coman <btc@openInWorld.com> wrote:
What is the recommended way for a C basic type to be passed-by-reference to function wanting to use it for output. For example 'width' & 'height' here in this library unction...
In Pharo...
FFIOpaqueObject subclass: #FPDF_DOCUMENT
FPDF_GetPageSizeByIndex__document: document page_index: page_index width: width height: height ^self ffiCall: #(int FPDF_GetPageSizeByIndex( FPDF_DOCUMENT *document, int page_index, FFIFloat64 * width, FFIFloat64 * height))
what you need to do here is to pass a âbufferâ to contain the width and height:
testGetPageSizeByIndex | document page_index widthBuffer heightBuffer width height result| PDFium FPDF_InitLibrary. document := PDFium FPDF_LoadDocument__file_path: helloPdf password: ''. widthBuffer := ByteArray new: (FFIFloat64 typeSize). heightBuffer := ByteArray new: (FFIFloat64 typeSize).
Thanks Esteban. That worked. However I needed a minor tweak... widthBuffer := ByteArray new: (FFIFloat64 new typeSize). or... widthBuffer := ByteArray new: (FFIFloat64 externalTypeSize).
Now it would be nice to do... widthBuffer := FFIFloat64 newBuffer.
you know, I thought the same :) but since Iâm officially on holiday I didnât do it (yet).
If you want to add an issue and send a PR, it would be perfect :)
https://pharo.fogbugz.com/f/cases/20699/ btw, how do we write tests for FFI? Do we (or can we) deliver as part of standard opensmalltalk-vm a small shared library "libFFITestFrame.so" to write ffi tests against? With functions like... void passByRefOutputs(int *p_int, double *p_double) { *p_int = 42; *p_double = 4.2; } cheers -ben
On 15 November 2017 at 20:47, Esteban Lorenzano <estebanlm@gmail.com> wrote:
On 15 Nov 2017, at 09:36, Ben Coman <btc@openInWorld.com> wrote:
On 15 November 2017 at 17:27, Esteban Lorenzano <estebanlm@gmail.com> wrote:
On 15 Nov 2017, at 02:05, Ben Coman <btc@openInWorld.com> wrote:
What is the recommended way for a C basic type to be passed-by-reference to function wanting to use it for output. For example 'width' & 'height' here in this library unction...
int FPDF_GetPageSizeByIndex(FPDF_DOCUMENT document, int page_index, double* width, double* height);
void mytestGetPageSizeByIndex(doc) { double width, height; FPDF_GetPageSizeByIndex(doc, 0, &width, &height); printf("width=%f, height=%f\n", width, height); }
gives... width=200.000000, height=200.000000
In Pharo I'm trying...
FFIOpaqueObject subclass: #FPDF_DOCUMENT
FPDF_GetPageSizeByIndex__document: document page_index: page_index width: width height: height ^self ffiCall: #(int FPDF_GetPageSizeByIndex( FPDF_DOCUMENT *document, int page_index, FFIFloat64 * width, FFIFloat64 * height))
testGetPageSizeByIndex | document page_index width height result| PDFium FPDF_InitLibrary. document := PDFium FPDF_LoadDocument__file_path: helloPdf password: ''. width := 0.0. height := 0.0. page_index := 0. "Its zero based" result := PDFium FPDF_GetPageSizeByIndex__document: document page_index: 0 width: width height: height. PDFium FPDF_CloseDocument__document: document. PDFium FPDF_DestroyLibrary. self assert: document isNull not. "Document opened okay, and btw this works for a different pageCount test" self assert: result > 0. "Non-zero for success. 0 for error (document or page not found)" self halt.
and at the halt the Inspector shows... result = 1 width = 0.0 height = 0.0
no, that will not work :) what you need to do here is to pass a âbufferâ to contain the width and height:
testGetPageSizeByIndex | document page_index widthBuffer heightBuffer width height result| PDFium FPDF_InitLibrary. document := PDFium FPDF_LoadDocument__file_path: helloPdf password: ''. widthBuffer := ByteArray new: (FFIFloat64 typeSize). heightBuffer := ByteArray new: (FFIFloat64 typeSize).
Thanks Esteban. That worked. However I needed a minor tweak... widthBuffer := ByteArray new: (FFIFloat64 new typeSize). or... widthBuffer := ByteArray new: (FFIFloat64 externalTypeSize).
Now it would be nice to do... widthBuffer := FFIFloat64 newBuffer.
you know, I thought the same :) but since Iâm officially on holiday I didnât do it (yet).
If you want to add an issue and send a PR, it would be perfect :)
which could be implemented... FFIExternalType class >> newBuffer. ^ByteArray new: (self externalTypeSize)
testGetPageSizeByIndex | document page_index widthBuffer heightBuffer width height result| PDFium FPDF_InitLibrary. document := PDFium FPDF_LoadDocument__file_path: helloPdf password: ''. widthBuffer := FFIFloat64 buffer. heightBuffer := FFIFloat64 buffer. page_index := 0. "Its zero based" result := PDFium FPDF_GetPageSizeByIndex__document: document page_index: 0 width: widthBuffer height: heightBuffer. width := widthBuffer doubleAt: 1. height := heightBuffer doubleAt: 1.
btw, a broader curiousity even though this is likely set in stone and not even your personal choice - why "FFIFloat64" and not "FFIDouble" ? widthBuffer := FFIDouble newBuffer. would be a nice match to the callout declaration.
because the ABI defines float64 as the right name, heh. and yes, it was not my personal choice ;)
Would it be overkill to have in Pharo an empty FFIDouble subclassed from FFIFloat64 ? the superclass being explicit about the format, while the subclass provides a nice identifier for users?
maybe it is overkilling. Because also⦠where do you put the limit? (doubles, floats, longs, etc⦠?)
Well, there are not so many... ?? cheers -ben
On 15 November 2017 at 20:47, Esteban Lorenzano <estebanlm@gmail.com> wrote:
which could be implemented... FFIExternalType class >> newBuffer. ^ByteArray new: (self externalTypeSize)
testGetPageSizeByIndex | document page_index widthBuffer heightBuffer width height result| PDFium FPDF_InitLibrary. document := PDFium FPDF_LoadDocument__file_path: helloPdf password: ''. widthBuffer := FFIFloat64 buffer. heightBuffer := FFIFloat64 buffer. page_index := 0. "Its zero based" result := PDFium FPDF_GetPageSizeByIndex__document: document page_index: 0 width: widthBuffer height: heightBuffer. width := widthBuffer doubleAt: 1. height := heightBuffer doubleAt: 1.
I'm about to do something similar with for this prototype... void clang_getFileLocation( CXSourceLocation location, CXFile *file, unsigned *line, unsigned *column, unsigned *offset); Now 'uints' are good to work with since they are consistently 4 bytes on all modern platforms (Windows / Linux / Unix) x (32bit / 64bit) http://nickdesaulniers.github.io/blog/2016/05/30/data-models-and-word-size/ So I using this FFI definition... Libclang >> clang_getFileLocation__location: location file: file line: line column: column offset: offset self ffiCall: #(void clang_getFileLocation( CXSourceLocation location, CXFile *file, uint *line, uint *column, uint *offset)) with this application code... lineBuffer := FFIUInt32 newBuffer. columnBuffer := FFIUInt32 newBuffer. offsetBuffer := FFIUInt32 newBuffer. Libclang clang_getFileLocation__location: cxLocation file: cxFile line: lineBuffer column: columnBuffer offset: offsetBuffer. but to unwrap the buffer the following is not available... line := lineBuffer uint32At: 1. Other candidates are senders of #integerAt:size:signed: from which the only one dealing in 4 bytes is... ByteArray >> unsignedLongAt: byteOffset ^self integerAt: byteOffset size: 4 signed: false which (apart from 'ulong' being semantically different from the original 'uint' C definition) seems a poor cross-platform choice, since 'long' is the only integer type whose size that varies between 32-bit & 64-bit. * https://software.intel.com/en-us/articles/size-of-long-integer-type-on-diffe... There is ByteArray >> platformUnsignedLongAt: but again the original C declaration was an uint, so it still feels wrong to use 'long' converter. The remaining option is directly using the lower level api, but its a bit ugly... lineBuffer := FFIUInt32 newBuffer. columnBuffer := FFIUInt32 newBuffer. offsetBuffer := FFIUInt32 newBuffer. Libclang clang_getFileLocation__location: cxLocation file: cxFile line: lineBuffer column: columnBuffer offset: offsetBuffer. line := lineBuffer integerAt: 1 size: 4 signed: false. column := columnBuffer integerAt: 1 size: 4 signed: false. offset := offsetBuffer integerAt: 1 size: 4 signed: false. ^{ line . column. offset } So are additional type converters required for ByteArray? Or alternatively, could FFIExternalType get an extra ByteArray instance variable that is usually nil, but is available when we want to deal concretely with an external type while keeping the type info associated, rather needing a raw ByteArray. For example... Object subclass: #FFIExternalType instanceVariableNames: 'pointerArity loader buffer' classVariableNames: '' package: 'UnifiedFFI-Types' FFIExternalType >> buffer ^ buffer ifNil: [ buffer := ByteArray new: (self externalTypeSize) ]. buffer pin. FFIExternalType >> dereference ^ self basicHandle: self buffer at: 1 then presumably I could do "something" like... line := FFIUInt32 new. column := FFIUInt32 new. offset := FFIUInt32 new. Libclang clang_getFileLocation__location: cxLocation file: cxFile line: line buffer column: column buffer offset: offset buffer. ^ { line value . column value . offset value} or maybe #bufferPointer would be better than #buffer I'm experimenting along this line, but I'm currently breaking my Image so I pause to seek feedback on the idea. cheers -ben
participants (2)
-
Ben Coman -
Esteban Lorenzano