Export dll not in exporters folder


#1

I have created a sketchup exporter that works great, however I would not like to have to move it to the exporters folder every time to use it. Is there anyway I can export from Sketchup without a dll needing to be in that location?


#2

Why doesn’t your code just copy the dll from your …/Plugins/SubFolder, into the SketchUp/Exporters folder when it initially installs from the RBZ ?
It’s easy enough to find the dll file and its target location, and then copy it…
Your installer might need to ask for a SketchUp restart to load the dll when it’s been first installed…


#3

These dlls are being deployed to many productions and we would prefer to not have it being moved as part of the process. If at all avoidable. As we have a spot for all our plugins already. Hoping to avoid Sketchup specific code in our deployment.


#4

IF the effective user has permissions to write to the SketchUp binary path folders…


What about having a small Ruby script that uses the Fiddle library to call the windows LoadLibrary system call ?

EDIT: Actually, DLL loading is built-in with the mixin module Importer, via the dlload method. See the example in the docs:
http://ruby-doc.org/stdlib-2.0.0/libdoc/fiddle/rdoc/Fiddle/Importer.html


Call C++ from Ruby in SketchUp
#5

Simply loading the library would enable Sketchup to find it? Or do you mean call the export function myself and pass it a model object?

I seem to be having some trouble loading my 64 bit dll in Ruby using that example you posted. I am fairly new to ruby.


#6

If you mean to have it automatically appear in the filetypes of the export dialog, no I would think you would need to tell SketchUp it is loaded, guessing perhaps there might be a register_exporter() SDK function.

Yes in the Ruby script you’d need to add a export option to the “File” menu:

Here is a sample. You’d need to adjust the CTypes to match your library function.
See the constants in the Fiddle module:
http://ruby-doc.org/stdlib-2.0.0/libdoc/fiddle/rdoc/Fiddle.html

# Encoding: UTF-8

module Marsh
  module NiftyExport

    # Initialize a module variable indicating if menuitem added:
    @@menuitem = false if !defined?(@@menuitem)

    MENUTEXT = "Export to Nifty"

    LIBNAME = "nifty_export.dll"
    LIBPATH = "some/path/to/external/directory"
    LIBRARY = File.join( LIBPATH, LIBNAME )
    
    EXPORT_FUNCNAME = "export_func_name"

    def self::export()

      # Make sure Fiddle library is loaded:
      require 'fiddle' unless defined?(Fiddle)
      require 'fiddle/import' unless defined?(Fiddle::Importer)

      # Extend this module with Fiddle::Importer mixin module:
      # (extend checks the ancestors, so only does this once.)
      extend Fiddle::Importer

      # Load the export library:
      @lib = Fiddle::dlopen LIBRARY

      # Import the export function:
      @export = import_function(
        @lib[EXPORT_FUNCNAME],
        Fiddle::TYPE_VOID,        # ctype : see Fiddle module constants
        [Fiddle::TYPE_UINTPTR_T], # arg array of ctypes : see Fiddle module constants
        call_type = nil           # the ABI of the function
      )
      
      # Do the export:
      @export.call( Sketchup::active_model )
      
    rescue => e

      puts Module::nesting[0].name<<" Error: #<#{e.class.name}: #{e.message}>"
      puts e.backtrace if $VERBOSE && $DEBUG

    ensure

      @lib.close    # let GarbageCollection call dlclose() on the library
      @export = nil # release reference to function
      @lib = nil    # release reference to handle

    end ###


    if !@@menuitem  # Add the menu item only once:

      UI.menu("File").add_item(MENUTEXT) {
        self::export()
      }
      @@menuitem = true

    end

  end # inner export module
end # outer author namespace module

I could have used simple local variables within the export method for the lib handle and function reference, but I chose to use @var instance variables, so they would stand out as transitory references.


#7

I forgot, at the top you’d likely need a conditional expression to choose the correct path or library DLL, depending upon bitness:

 if Sketchup.respond_to?(:is_64bit?) && Sketchup.is_64bit?
   # Load 64bit binaries.
 else
   # Load 32bit binaries.
 end

#8

Thanks for all the help, one thing I am confused about is handing c++ the ruby model object. How would I use this in c++? Is it the same model object as the c api?

Great place to start, thank you for the code. I will try it out and get back to you.


#9

First of all, I’m not a C person. Can read it, but always hated it. (@tt_su or @bugra are better people to ask.)

I would think so, see this source files:
http://www.sketchup.com/intl/en/developer/su-api/defs_8h_source.html
and
http://www.sketchup.com/intl/en/developer/su-api/model_8h_source.html
they’re used within an extern "C" { block, and compiled with Visual Studio.

I think that the Fiddle CType constant to use for the model argument might be Fiddle::TYPE_UINTPTR_T
as in the SUModelFromExisting function (2nd file above.)

So perhaps ?:

# Import the export function:
@export = import_function(
  @lib[EXPORT_FUNCNAME],
  Fiddle::TYPE_VOID,        # ctype : see Fiddle module constants
  [Fiddle::TYPE_UINTPTR_T], # arg array of ctypes : see Fiddle module constants
  call_type = nil           # the ABI of the function
)

#10

I got it working, thanks a lot for the help. I definitely would not have been able to get through that Ruby as I have never used it before.

Some tips if anyone else tries this:

If your dll requires another dll that one must be loaded first with dlload
Fiddle::Function.new worked for me while I could not get import_function to work.
I am not sure if that model would go across correctly or not. But the way the current export works is that it saves a temporary skp file and then you get access to that path. So I just took a input and output string as well.

  # Import the export function:
  @export = Fiddle::Function.new(
      @lib['ExportFromRuby'],  
      [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP],
      Fiddle::TYPE_VOIDP
    )
@export.call( inputPath, outputPath )

This also means that users can export a custom filetype from the free version of Sketchup as a side effect.


#11

Is there anyway to get around this? Add a ruby dll search path perhaps? The list of dlls that I have to load manually is slowing growing.


#12

You can add any search location to the Ruby global $LOAD_PATH (alias $:,) and it will be searched when using Ruby’s global require or load methods. This includes binary libraries like so, dll and dylib (but in later Ruby releases you may need to explicitly add the file extension.)

$: << my_dll_folder
# load dlls
$:.delete_if {|path| path == my_dll_folder }

But require will also search the current working directory, so you can avoid that pushing and removing by using the block form of the Dir::chdir class method:

Dir::chdir(my_dll_folder) {
  # load dlls
}
# Previous working directory is automatically
# restored by Dir::chdir when it's block ends.

ADD: The dll loading should probably be wrapped in a beginrescue block to recover gracefully in case there is some loading failure. (Otherwise the error might cause the previous working dir not to be restored. But it always good to wrap and rescue disk IO operations.)
see: http://ruby-doc.org/core-2.0.0/doc/syntax/exceptions_rdoc.html


In normal command shell executed Ruby there is an environment variable DLN_LIBRARY_PATH that gets checked when Ruby first starts that "Specifies the search path for dynamically loaded modules."
see: http://phrogz.net/ProgrammingRuby/rubyworld.html#environmentvariables

But in SketchUp embedded Ruby, it is not set (ie, it is nil) in the ENV hash-like object unless it is preset in the User or System environment prior to SketchUp starting. Even then I am not sure if embedded SketchUp Ruby will even check and use it’s setting as the interpreter loading is non-standard and controlled by the SketchUp application code.


In addition, some info found via Google search for “Ruby fiddle”:

Usable Fiddle docs
https://www.ruby-forum.com/topic/6876177
which suggests the ffi gem wiki (becaus fiddle is a ffi wrapper.):
(Foriegn Function Interface gem wiki)

Use any C library from Ruby via Fiddle - the Ruby standard library’s best kept secret.
http://blog.honeybadger.io/use-any-c-library-from-ruby-via-fiddle-the-ruby-standard-librarys-best-kept-secret/


#13

I have tried LOAD_PATH and DLN_LIBRARY_PATH with no avail. You mentioned that “it will be searched when using Ruby’s global require or load methods”. But what about fiddle dl loading? This it work for that as well?

I have tried the following:

$: << my_dll_folder
$LOAD_PATH.unshift(“C:/Folder”)
ENV[‘RUBYLIB’] = "C:/Folder"
ENV[‘LD_LIBRARY_PATH’] = “C:/Folder”


#14

Get it from the horse’s mouth:
http://ruby-doc.org/core-2.0.0/Kernel.html#method-i-require
http://ruby-doc.org/core-2.0.0/Kernel.html#method-i-load

Actually looking at the source, it appears not.

Fiddle::Importer#dlload()
http://ruby-doc.org/stdlib-2.0.0/libdoc/fiddle/rdoc/Fiddle/Importer.html#method-i-dlload
Hovering over the method doc block, the Toggle source link appears with a magnifying glass icon, at the upper right of the method documentation block. Click it and you see the the dlload method is a processing loop that calls the Fiddle::dlopen() module function for each library pathstring.

And toggling the source for Fiddle::dlopen()
http://ruby-doc.org/stdlib-2.0.0/libdoc/fiddle/rdoc/Fiddle.html#method-c-dlopen
shows that it just passes the lib pathstring argument on to Fiddle::Handle::new

And toggling the source for Fiddle::Handle::new()
http://ruby-doc.org/stdlib-2.0.0/libdoc/fiddle/rdoc/Fiddle/Handle.html#method-c-new
shows that (1) this part of Fiddle is implemented in C, and (2) appears to call the Ruby C core function dlopen.

So we are back to the either using full absolute paths for libs, ie:

errors = {}
lib_path = "C:/path/to/my/libs"
my_libs = ["this_lib.dll","that_lib.dll","another_lib.dll"]

my_libs.each {|lib|
  begin
    Fiddle::dlload( File.join(lib_path,lib) )
  rescue => error
    errors[lib]= error
  end
}
if !errors.empty?
  # Process saved exceptions in errors hash.
end

… or use the Dir::chr class method:

errors = {}
lib_path = "C:/path/to/my/libs"
my_libs = ["this_lib.dll","that_lib.dll","another_lib.dll"]

Dir::chdir(lib_path) {
  my_libs.each {|lib|
  begin
    Fiddle::dlload(lib)
  rescue => error
    errors[lib]= error
  end
}
if !errors.empty?
  # Process saved exceptions in errors hash.
end

#15

Thank you very much for your insight. I do not want to hard code any path or dll name if I do not have to. I ended up using the following, but it only works on windows 8 or updated windows 7 systems.

extern 'int SetDefaultDllDirectories(uint)'
extern 'int MultiByteToWideChar(uint, uint, char*, int, wchar_t*, int)'
extern 'void* AddDllDirectory(wchar_t*)'

https://msdn.microsoft.com/en-us/library/windows/desktop/hh310513(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/dd319072(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/hh310515(v=vs.85).aspx