Best practice to support multiple SketchUp versions?

I developed a plugin using the SketchUp 2018 SDK. The plugin actually uses the Ruby API to interact with the open model but it also uses the SDK to export SKP files. So the end result after compiling is a .so or .bundle Ruby C Extension that calls various functions that are part of the SketchUp SDK.

Now I want to add compatibility for SketchUp 2017. Unfortunately, my plugin is currently utilizing some SDK functions that were only added in the SU 2018 version. That’s OK, the functions aren’t critical but I’d still like to use them for SU2018+.

In order to support SU2017, is the proper way to build a separate .so file (and create a new Visual Studio configuration) that uses pre-processor directives to compile different code based on whether the configuration is set to support SU2017 or not?

I see that the SDK has a method ‘SUGetAPIVersion’ that can probably tell me which SU version is currently running, but I expect I’ll get load errors if I compile my .so that has any references to future functions that don’t yet exist in the SDK for SU2017.

Ideally, I would be able to support both SU2017 and SU2018+ using the same .so file. Is this even possible if the code includes references to SU2018-only functions?

Lastly, do I need to use the SU2017 SDK .lib files when building for 2017 or can I keep using 2018? In my initial tests, building with the 2018 .lib files did not cause any noticeable errors. If you think I should be using the SU2017 SDK files to build, can someone please post a link to download them?

I hope this all makes sense. Thanks!

1 Like

While I’m not the person to answer your query. I love your extensions! Thank you Dale.

1 Like

As far as Ruby is concerned, both v17 and v18 use Ruby 2.2 so they can both load the same so file. But you’d need to compile a separate so for v19 that uses Ruby 2.5.1.

The newer SDK should be able to read older version SKP files even using the latest functions.

Running a Ruby C extension under live SketchUp is a question for @thomthom or @bugra.

You might be able to load the “new” 2018 functions dynamically using GetProcAddress (and the macOS equivalent), which would allow you to check if those are available. If you link with the 2018 link library (.lib) and your code is referring to symbols that do not exist in 2017, then I don’t think your .so will even load in 2017. So you’d have to link with 2017 .lib and handle the new 2018 functions dynamically.

The most straightforward solution is to make separate builds in my opinion.

1 Like

Thanks Bugra, do you know where I can get the SDK (.lib) files for 2017?

Hmm I’m not sure if those are still available now that I think about it. But if you are carefully loading those “new” functions purely dynamically, it won’t matter, you can still use the latest .lib. The linker won’t be linking them from the lib and your .so should still load in 2017.

Are you linking against sketchup.lib or SketchUpAPI.lib? The former links to the C API within Sketchup.exe, the latter links against the standalone version. Mixing those two doesn’t work well.

If you need to use features from the C API released with SU2018 with older versions you have to look into some other way of communicating with the C API.

I’m afraid that function was neglected for a long time so it’s not reliable for older versions I’m afraid.

That should be fine - the main thing to keep in mind is that if you link against the version of the SDK that SketchUp shipped with (sketchup.lib) then you must be careful not to call new functions from older versions.

An alternative you can try is to make a CLI executable that links against the standalone C API. If you only use the SDK to write SKP files it shouldn’t be that much more work to get it working - call your executable with an output path.

If you use the SDK to read from a live model then that becomes a challenge.

Thanks Thom and Bugra. I appreciate the advice

So, I’m back working on this problem using the method of dynamic loading of the DLL functions at runtime. The function in question is called ‘SUModelFixErrors’ and it was added in the SU2018 SDK. My plan is to call this function if it exists but simply do nothing if the function is not defined within the DLL.

On Windows, this seems to be working using the code below:

However, I haven’t been able to get it to work on OSX yet using the methods dlopen, dlsym, and dlclose.

I am able to load a .dylib using dlopen but I don’t know which .dylib to load. On OSX, the SDK is buried in a .framework package which contains a bunch of .dylibs none of which seem to have the function ‘SUModelFixErrors’.

@bugra do you know which .dylib file should contain that function? No matter which .dylib I try to load within the SketchUpAPI.framework, dlsym always returns null when trying to retrieve the function I want.

Thanks

And here is the current code on Mac that is not working. Here you can see my attempt at using the library libCommonGeoutils.dylib but the function I want does not seem to be included in that library.

Hi Dale,
That function is included in the SketchUpAPI binary inside the framework:

$ cd /Applications/SketchUp\ 2019/SketchUp.app/Contents/Frameworks/SketchUpAPI.framework/Versions/A
$ nm -g SketchUpAPI | grep SUModelFixErrors
000000000002b182 T _SUModelFixErrors

For pre-2018 versions, on Windows you can alternatively use a send action ID for the validity check tool.

Ruby …

Sketchup::send_action(21124) # launch the validity check tool

It will always display the modal results dialog (captioned “Validity Check”) needing to be dismissed by the user.

C …

rb_eval_string("Sketchup::send_action(21124)");

… or, use rb_funcall if you prefer.


REF:

P.S.

I logged a new Ruby API issue in the Public Issue tracker for everyone to watch or comment on …
(even though Thomas has already logged it internally.)

Thanks Bugra! I was able to get the Mac version to work using this code:

    typedef SUResult (*fixModelErrorsFncPtr) (SUModelRef);
    void* hModule = dlopen(NULL, RTLD_LAZY); // If filename is NULL, then the returned handle is for the main program
    
    if (hModule != NULL)
    {
        rb_puts("Found the module!");
        fixModelErrorsFncPtr fixModelErrorsFnc = (fixModelErrorsFncPtr)dlsym(hModule, "SUModelFixErrors");
        if (!fixModelErrorsFnc)
        {
            // handle the error
            dlclose(hModule);
            rb_puts("Could not find function to fix model errors");
            return false;
        }
        else
        {
            // call the function
            rb_puts("Fixing Model Errors!");
            res = fixModelErrorsFnc(model);
        }
    }
    else {
        rb_puts("Could not load DLL to fix model errors");
        return false;
    }
    
    return true;
2 Likes

Right! For working with the live model, the function is exported from the main program executable. So calling dlopen with NULL will do the trick. Glad you figured it out.

1 Like

For the record, in regard to the Live C API in general:

The read-only live C API wasn’t really complete until SU2019.2 that was just released. While the symbols has been exported on Windows a couple of versions ago, mac did not export the C API symbols from the SketchUp app.

People working with the live C API on mac up til now have been linking against the standalone SketchUpAPI.framework which have appeared to mostly work. But this isn’t the correct way to consume it, and will lead to incorrect behaviour (such as not getting the cutout holes in faces) or in some cases crashes (SUSectionIsActive for instance.) Since it mostly appeared to work, this went under our radar.

We will provide better documentation to how the Live C API should be consumed. What we currently have is the licensed_ruby_extension example in the SDK package. But it’s not clear exactly what is going on. I’ll describe below - and we’ll get this documented publicly.

Windows:

Link against sketchup.lib.
If you link against SketchUpAPI.lib (the standalone version) you will run into the same issues as described before.

Mac:

Don’t link against SketchUpAPI.framework - due to same issues as above.
Instead add the path to the SketchUpAPI.framework to you Framework Search Paths in your Xcode project. This will allow Xcode to locate and use the header for the API.
To allow the project to build you must set the Bundle Loader to the path of the SketchUp application on your machine. Xcode will use the symbols exported from that binary when it links.

Both of the above is demonstrated in the licensed_ruby_extension (just not explained in detail).

Additionally we added SUApplicationGetActiveModel as the official way to obtain the model pointer for live C API. This saves you from doing the boilerplate of calling Ruby and interpret_cast’ing the pointer address. We will leave model.skpdoc(true) working! For backwards compatibility with existing users of the API. But going forward you should prefer SUApplicationGetActiveModel.

@tt_su
Thanks for pointing this out. I have learned myself in a hard way that linking against SketchUpAPI.lib is a terrible mistake. Some things just work… few fail miserably.

I would recommend adding a sub-folder in SDK called “live” or something similar to get the message straight to developers:
binaries\sketchup\x64\ live \sketchup.lib

1 Like