Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Preset: Black Magic Decklink API #528

Closed
masterav10 opened this issue Feb 18, 2018 · 8 comments
Closed

New Preset: Black Magic Decklink API #528

masterav10 opened this issue Feb 18, 2018 · 8 comments

Comments

@masterav10
Copy link

I'm working on building a Windows x64 preset for the Desktop Video SDK, which interfaces with several Black Magic devices including Decklink cards. Currently, I'm running into an issue where the Parser seems to choke on a particular class definition in the Windows SDK.

I've experimented with the headers a bit, but ultimately the Parser cannot figure out the IUnknown interface (which is COM).

Here is the error I get from Maven:
[ERROR] Failed to execute goal org.bytedeco:javacpp:1.4.1-SNAPSHOT:build (javacp p.parser) on project bmddecklink: Failed to execute JavaCPP Builder: C:\Program Files (x86)\Windows Kits\10\Include\10.0.16299.0\um\Unknwnbase.h:110:Could not parse declaration at '{' -> [Help 1]

I followed the instructions for building on Windows very thoroughly, and was able to build and install cuda successfully, so I'm pretty sure the environment is setup right.

If you get a moment, take a quick peek at my fork which has the bmddecklink project it in; maybe there is something glaring obvious.

Some info about my system:
Win 10 Pro
JavaCPP 1.4.1.SNAPSHOT
Visual Studio 2017 Community
Oracle Java 1.8.0_162

@saudet
Copy link
Member

saudet commented Feb 18, 2018

You'll probably need to add more Info to get it to parse it properly. Check what has been done for all the other presets and try to apply the same ideas to this case. If you are having issues figuring out what to do for one declaration in particular, please provide the details about it here. Thank you!

@masterav10
Copy link
Author

Still getting the same error above. The parser is failing on a brace of the structure IUnknown. It is declared in the file Unknwnbase.h, which is part of the Windows SDK. Relevant parts below:

typedef interface IUnknown IUnknown;
...

typedef /* [unique] */ IUnknown *LPUNKNOWN;
...

#if (_MSC_VER >= 1100) && defined(__cplusplus) && !defined(CINTERFACE)
    EXTERN_C const IID IID_IUnknown;
    extern "C++"
    {
        MIDL_INTERFACE("00000000-0000-0000-C000-000000000046")
        IUnknown
        {   // XXX: consistently fails on this line
        public:
            BEGIN_INTERFACE
            virtual HRESULT STDMETHODCALLTYPE QueryInterface( 
                /* [in] */ REFIID riid,
                /* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR *__RPC_FAR *ppvObject) = 0;

            virtual ULONG STDMETHODCALLTYPE AddRef( void) = 0;

            virtual ULONG STDMETHODCALLTYPE Release( void) = 0;

            template<class Q>
            HRESULT
#ifdef _M_CEE_PURE
            __clrcall
#else
            STDMETHODCALLTYPE
#endif
            QueryInterface(_COM_Outptr_ Q** pp)
            {
                return QueryInterface(__uuidof(Q), (void **)pp);
            }

            END_INTERFACE
        };
    } // extern C++

I did some probing into the parser using the debugger. Item 18 in Parser.declList has the definition of IUnknown, which is just the basic type. So it is at least finding the typedef, which is good. I also stepped through the parsing of the line with IUnknown on it. Was expecting it to go into the group(...) function, but it failed. I figured it would start heading down.

Here are some things I've tried:

infoMap .put(new Info("IUnknown").cppTypes().cppText("struct IUnknown"))
This would make it a valid token from the Parser's perspective. Based on the comments in InfoMap this seemed like it would replace the text IUnknown with new text, but upon debugging I found this behaves differently. Is this the right approach?

infoMap.put(new Info("CINTERFACE").define(true))
An attempt to use the C definition, which seems far less complex than the C++. Interestingly, I still get the same error, which means that path was followed during parsing anyway? Strange.

I tried creating the structure myself inside the presets .java file and ignoring Unknwnbase.h . Didn't seem to count as a real definition; is this something that can/should done when I only want to use a piece of the file?

It's also possible that this is actually an error with the Parser. Any advice is greatly appreciated; I will continue slugging in the meantime.

@saudet
Copy link
Member

saudet commented Feb 28, 2018

You'll need to ignore MIDL_INTERFACE, etc by telling it to convert it to an empty annotation and to not try to interpret any defined macros as variables like this:

.put(new Info("MIDL_INTERFACE", "__clrcall", "STDMETHODCALLTYPE").annotations().cppTypes())

You'll also need to override the BEGIN_INTERFACE and END_INTERFACE macros so they output nothing like this:

.put(new Info("BEGIN_INTERFACE").cppText("#define BEGIN_INTERFACE"))
.put(new Info("END_INTERFACE").cppText("#define END_INTERFACE"))

@masterav10
Copy link
Author

Hi Sam,

I've actually made decent progress on building bindings for BMD SDK; see the following project:
https://github.com/masterav10/immersed-presets/tree/main/immersed-presets-decklink

I have what appears to be last issue remaining and I'm not sure what magic incantation to use to alleviate it. Given the following struct:

typedef struct IBMDStreamingAudioPacketVtbl
    {
        BEGIN_INTERFACE
        
        HRESULT ( STDMETHODCALLTYPE *QueryInterface )( 
            IBMDStreamingAudioPacket * This,
            /* [in] */ REFIID riid,
            /* [annotation][iid_is][out] */ 
            _COM_Outptr_  void **ppvObject);
        
        ULONG ( STDMETHODCALLTYPE *AddRef )( 
            IBMDStreamingAudioPacket * This);
        
        ULONG ( STDMETHODCALLTYPE *Release )( 
            IBMDStreamingAudioPacket * This);
        
        BMDStreamingAudioCodec ( STDMETHODCALLTYPE *GetCodec )( 
            IBMDStreamingAudioPacket * This);
        
        long ( STDMETHODCALLTYPE *GetPayloadSize )( 
            IBMDStreamingAudioPacket * This);
        
        HRESULT ( STDMETHODCALLTYPE *GetBytes )( 
            IBMDStreamingAudioPacket * This,
            /* [out] */ void **buffer);
        
        HRESULT ( STDMETHODCALLTYPE *GetPlayTime )( 
            IBMDStreamingAudioPacket * This,
            /* [in] */ ULONGLONG requestedTimeScale,
            /* [out] */ ULONGLONG *playTime);
        
        HRESULT ( STDMETHODCALLTYPE *GetPacketIndex )( 
            IBMDStreamingAudioPacket * This,
            /* [out] */ unsigned int *packetIndex);
        
        END_INTERFACE
    } IBMDStreamingAudioPacketVtbl;

    interface IBMDStreamingAudioPacket
    {
        CONST_VTBL struct IBMDStreamingAudioPacketVtbl *lpVtbl;
    };

The structures generated by javacpp for callbacks have JNI constructs (jlong) rather than their original values:

Snippets from jnidecklink.cpp

struct JavaCPP_hidden JavaCPP_org_bytedeco_decklink_IBMDStreamingAudioPacketVtbl_00024GetPayloadSize_1IBMDStreamingAudioPacket {
    JavaCPP_org_bytedeco_decklink_IBMDStreamingAudioPacketVtbl_00024GetPayloadSize_1IBMDStreamingAudioPacket() : ptr(NULL), obj(NULL) { }
    jlong operator()(IBMDStreamingAudioPacket* arg0);
    jlong (__stdcall *ptr)(IBMDStreamingAudioPacket* arg0);
    jobject obj; static jmethodID mid;
};

As a result, I get the following compilation error:

jnidecklink.cpp(21148): error C2440: '=': cannot convert from 'jlong (__cdecl *)(IBMDStreamingAudioPacket *)' to 'long (__cdecl *)(IBMDStreamingAudioPacket *)'
jnidecklink.cpp(21148): note: This conversion requires a reinterpret_cast, a C-style cast or function-style cast
JNIEXPORT jobject JNICALL Java_org_bytedeco_decklink_IBMDStreamingAudioPacketVtbl_GetPayloadSize__Lorg_bytedeco_decklink_IBMDStreamingAudioPacketVtbl_00024GetPayloadSize_1IBMDStreamingAudioPacket_2(JNIEnv* env, jobject obj, jobject arg0) {
    IBMDStreamingAudioPacketVtbl* ptr = (IBMDStreamingAudioPacketVtbl*)jlong_to_ptr(env->GetLongField(obj, JavaCPP_addressFID));
    if (ptr == NULL) {
        env->ThrowNew(JavaCPP_getClass(env, 9), "This pointer address is NULL.");
        return 0;
    }
    jlong position = env->GetLongField(obj, JavaCPP_positionFID);
    ptr += position;
    JavaCPP_org_bytedeco_decklink_IBMDStreamingAudioPacketVtbl_00024GetPayloadSize_1IBMDStreamingAudioPacket* ptr0 = arg0 == NULL ? NULL : (JavaCPP_org_bytedeco_decklink_IBMDStreamingAudioPacketVtbl_00024GetPayloadSize_1IBMDStreamingAudioPacket*)jlong_to_ptr(env->GetLongField(arg0, JavaCPP_addressFID));
    jlong position0 = arg0 == NULL ? 0 : env->GetLongField(arg0, JavaCPP_positionFID);
    ptr0 += position0;
    jobject rarg = obj;
    ptr->GetPayloadSize = (ptr0 == NULL ? NULL : ptr0->ptr);  // line 21148
    return rarg;
}

Is there some way to overcome this using Info and InfoMap?

@saudet
Copy link
Member

saudet commented Sep 16, 2021

We'll probably need to add a cast for this to work, with something like this:

infoMap.putFirst(new Info("long").cast().valueTypes("long").pointerTypes("CLongPointer"))

@masterav10
Copy link
Author

Success! This was the secret sauce I was missing:

infoMap.put(new Info("MIDL_INTERFACE").cppText("#define MIDL_INTERFACE(x) struct"));

Now the structures are finally being recognized as structures and I don't have to CINTERFACE flag. I have a card at home and was able to easily verify the API is working by mirroring some samples.

Here's my current plan of attack now that I have a working prototype:

  1. Clean up any junk in my preset classes.
  2. Modify systems preset to include the com stuff needed for this to work on Windows.
  3. Figure out if I can get Linux/Mac bindings working.
  4. Create official binding.

Thanks for the help Sam; this project just keeps getting better and easier to use!

@saudet
Copy link
Member

saudet commented Sep 29, 2021

Awesome! Looking forward to your update at pull #543! BTW, there's no cross-platform API that doesn't require COM? I don't see how we're supposed to be using COM on Linux and Mac...

@masterav10
Copy link
Author

They provide COM emulation in the form of headers within their SDK.

As for #543, the licensing is a non-starter. I plan on closing it once I've completed the Decklink Bindings.

Licensing shouldn't be a problem for the bindings since we can use a more permissive license here (or simply duplicate the SDK license).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants