Skip to content

Calling Ruby API's DefinitionsList.load on a model released with SUModelRelease on Live C API causes Crash / Memory Errors #1088

@TommyKaneko

Description

@TommyKaneko

I described this in the forum too: https://forums.sketchup.com/t/creating-and-loading-models-using-live-c-extension-ruby-c-extension/343829

  1. If you are developing a Ruby C Extension and want access to the open "live" model, you use SUApplicationGetActiveModel , and make sure not to call SUModelRelease on the model reference. That is well documented on the C API. We are told that the SketchUp Application "owns" the model, so we do not release it in the extension - let's call this "Assumption 1".
  2. However, what if you were to use a Ruby C Extension (or Live C API) to create/open a new model outside of the current application? If the model is not open in the Sketchup application, then you would think that you should call SUModelRelease once you are done to avoid memory leaks. Call this "Assumption 2".
  3. Following assumption 2, you create a new model in the Live C API, save it to disk and call SUMorelRelease on it. You pass the file path of the model back to Sketchup, and using Ruby API's DefinitionsList::load method, you attempt to load the model as a component to the active model. Assumption 3 is that the SketchUp Application loads the model afresh using DefinitionsList::load, and there will be no problem.

However, doing the above will cause an exception and crash. I have reproduced the error minimally, by adapting the Live-C-Examples project, by adding the function below into example_extension.cpp

// Creates a model, saves it then returns the file_path.
VALUE load_definition(VALUE self) {
  // Create a Model and fill it with a simple face:
  SUModelRef output_model = SU_INVALID;
  SU(SUModelCreate(&output_model));

  SUEntitiesRef entities = SU_INVALID;
  SU(SUModelGetEntities(output_model, &entities));

  SUGeometryInputRef input = SU_INVALID;
  SU(SUGeometryInputCreate(&input));

  // Define a simple square in the XY plane
  std::vector<SUPoint3D> points{
    { 0.0, 0.0, 0.0 },
    { 9.0, 0.0, 0.0 },
    { 9.0, 9.0, 0.0 },
    { 0.0, 9.0, 0.0 }
  };
  for (size_t i = 0; i < points.size(); ++i) {
    SU(SUGeometryInputAddVertex(input, &points[i]));
  }

  // Outer loop
  SULoopInputRef outer_loop = SU_INVALID;
  SULoopInputCreate(&outer_loop);
  for (size_t i = 0; i < points.size(); ++i) {
    SU(SULoopInputAddVertexIndex(outer_loop, i));
  }
  size_t face_index = 0;
  SU(SUGeometryInputAddFace(input, &outer_loop, &face_index));

  // Fill entities using the geometry input
  SU(SUEntitiesFill(entities, input, true));

  SU(SUGeometryInputRelease(&input));

  // Put the file in the same folder as the open file
  SUModelRef open_model = SU_INVALID;
  SU(SUApplicationGetActiveModel(&open_model));
  if (SUIsInvalid(open_model)) {
    rb_raise(rb_eTypeError, "invalid model");
  }
  SUStringRef open_model_path = SU_INVALID;
  SUStringCreate(&open_model_path);
  SUModelGetPath(open_model, &open_model_path);
  std::string open_model_path_str = GetString(open_model_path);
  SUStringRelease(&open_model_path);

  size_t last_slash = open_model_path_str.find_last_of("/\\");
  if (last_slash != std::string::npos) {
    open_model_path_str = open_model_path_str.substr(0, last_slash);
  }

  // Save to a temporary path and return it
  std::string file_path = open_model_path_str + "/load_definition.skp";
  SU(SUModelSaveToFile(output_model, file_path.c_str()));

  SU(SUModelRelease(&output_model)); // <== this line will cause SketchUp to crash on Ruby API's DefinitionsList::load.  Commenting it out will make it work

  VALUE ruby_path = rb_str_new_cstr(file_path.c_str());
  return ruby_path;
}

there are also bits you need to add to get this to run...for reference, I have attached the cpp file below:
example_extension.cpp
...and you will also need to add a ruby method to main.rb call the function:

      sub_menu.add_item('Load Definition') {
        file_path = self.load_definition
        definition = Sketchup.active_model.definitions.load(file_path)
        Sketchup.active_model.entities.add_instance(definition, Geom::Transformation.new)
      }

On Mac, using SketchUp v2025 the call stack following the crash is:

[Unknown/Just-In-Time compiled code] (Unknown Source:0)
SketchUp!CUndoOperation::GetModifiedParents(CEntitySet&) const (Unknown Source:0)
SketchUp!CBaseDoc::OnTransactionEnd(ETransactionType, unsigned int, unsigned int) (Unknown Source:0)
SketchUp!CBaseEditDoc::OnTransactionEnd(ETransactionType, unsigned int, unsigned int) (Unknown Source:0)
SketchUp!CMacSketchUpDoc::OnTransactionEnd(ETransactionType, unsigned int, unsigned int) (Unknown Source:0)
SketchUp!CDbTalker::EndTransactionNotification(ETransactionType, unsigned int, unsigned int) (Unknown Source:0)
SketchUp!CUndoManager::CommitCurrentOperation(unsigned int, bool) (Unknown Source:0)
SketchUp!CRubyUndoOperation::CommitRubyOperation(unsigned int) (Unknown Source:0)
SketchUp!___lldb_unnamed_symbol59538 (Unknown Source:0)
Ruby!vm_call_cfunc_with_frame (Unknown Source:0)
Ruby!vm_exec_core (Unknown Source:0)
Ruby!rb_vm_exec (Unknown Source:0)
Ruby!vm_invoke_proc (Unknown Source:0)
Ruby!vm_call0_body (Unknown Source:0)
Ruby!rb_funcallv (Unknown Source:0)
SketchUp!___lldb_unnamed_symbol61188 (Unknown Source:0)
Ruby!rb_protect (Unknown Source:0)
SketchUp!protect_funcall(unsigned long, unsigned long, std::__1::vector<unsigned long, std::__1::allocator<unsigned long>> const&) (Unknown Source:0)
SketchUp!protect_funcall(unsigned long, unsigned long, int, ...) (Unknown Source:0)
SketchUp!CRubyCommand::Invoke() const (Unknown Source:0)
SketchUp!common::CommandManager::RunCommand(unsigned int, command_bindings::ICommandEntryDocument*) const (Unknown Source:0)
AppKit!-[NSApplication(NSResponder) sendAction:to:from:] (Unknown Source:0)
[.....lots of other calls that I omit ....]
SketchUp!main (Unknown Source:0)
start (Unknown Source:0)

I get EXC_BAD_ACCESS error on my fuller extension that does something similar, which I think to non-Mac developers is a Segmentation Fault(?).

Now this raises a lot of issues and questions. Assumptions 2 or 3, or both must be wrong. To list the key questions:

  1. Should one ever call SUModelRelease on a model created with SUModelCreate when using the Live C API (Ruby Extension)? What about SUModelCreateFromFile, and the other C API functions used to load/create a model? NOT calling SUModelRelease makes me think you would get memory leaks every time.
  2. What is going on under the hood with Ruby API's DefinitionList::load ? I have known this method to be rather cryptic as I discovered some time ago: https://forums.sketchup.com/t/fix-definitionlist-load-problems-in-some-situations/5655 . I am inclined to think that "DefinitionList" may be better thought of as a collection of SketchUp models rather than components, some "internal" and others "external" to the active model. Does calling SUModelCreate create a new ComponentDefinition in DefinitionList - or does it access some sort of cache of "created" models?
  3. What is (or should be) the guaranteed behaviours of DefinitionList::load and the various SUModelCreate*** methods in the context of the Live C API? Or put it another way - how is model ownership handled between the Live C API and the active SketchUp Application?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions