diff --git a/src/api.jl b/src/api.jl index 3c83a74b..b70ab5e0 100644 --- a/src/api.jl +++ b/src/api.jl @@ -73,7 +73,7 @@ Initializes the Optimizer library. This must be called before any other library """ function init() - @checked Lib.XPRSinit(C_NULL) + Lib.XPRSinit(C_NULL) end """ @@ -83,7 +83,7 @@ Frees any allocated memory and closes all open files. """ function free() - @checked Lib.XPRSfree() + Lib.XPRSfree() end """ @@ -125,7 +125,7 @@ You can use this function to disable some of the checking and validation of func """ function setcheckedmode(checked_mode) - @checked Lib.XPRSsetcheckedmode(checked_mode) + Lib.XPRSsetcheckedmode(checked_mode) end """ @@ -135,7 +135,7 @@ You can use this function to interrogate whether checking and validation of all """ function getcheckedmode(r_checked_mode) - @checked Lib.XPRSgetcheckedmode(r_checked_mode) + Lib.XPRSgetcheckedmode(r_checked_mode) end function license(lic, path) @@ -143,11 +143,11 @@ function license(lic, path) end function beginlicensing(r_dontAlreadyHaveLicense) - @checked Lib.XPRSbeginlicensing(r_dontAlreadyHaveLicense) + Lib.XPRSbeginlicensing(r_dontAlreadyHaveLicense) end function endlicensing() - @checked Lib.XPRSendlicensing() + Lib.XPRSendlicensing() end """ @@ -162,11 +162,12 @@ Retrieves an error message describing the last licensing error, if any occurred. """ function getlicerrmsg(; len = 1024) - msg = Cstring(pointer(Array{Cchar}(undef, len*8))) - Lib.XPRSgetlicerrmsg(msg, len) - # TODO - @ checked version does not work - # @checked Lib.XPRSgetlicerrmsg(msg, len) - return unsafe_string(msg) + buffer = Array{Cchar}(undef, len*8) + buffer_p = pointer(buffer) + GC.@preserve buffer begin + Lib.XPRSgetlicerrmsg(msg, len) + return unsafe_string(msg) + end end """ @@ -2788,7 +2789,13 @@ Returns the error message corresponding to the last error encountered by a libra """ function getlasterror(prob::XpressProblem) @invoke Lib.XPRSgetlasterror(prob, _)::String -end + buffer = Array{Cchar}(undef, 512) + buffer_p = pointer(buffer) + GC.@preserve buffer begin + s = Lib.XPRSgetlasterror(prob, buffer_p) + return s == 0 ? unsafe_string(buffer_p) : "Unable to get last error" + end + end """ int XPRS_CC XPRSbasiscondition(XPRSprob prob, double *condnum, double *scondnum); diff --git a/src/helper.jl b/src/helper.jl index 1394ec75..d6556c93 100644 --- a/src/helper.jl +++ b/src/helper.jl @@ -62,6 +62,10 @@ mutable struct XpressProblem <: CWrapper end end +function get_xpress_error_message(prob::XpressProblem) + lstrip(Xpress.getlasterror(prob), ['?']) +end + function XpressProblem(; logfile = "") ref = Ref{Lib.XPRSprob}() createprob(ref) diff --git a/src/license.jl b/src/license.jl index 994b98a9..82a41642 100644 --- a/src/license.jl +++ b/src/license.jl @@ -74,8 +74,11 @@ function userlic(; verbose::Bool = true, liccheck::Function = emptyliccheck, xpa lic = liccheck(lic) # Send GIVEN LIC to XPRESS lib - slicmsg = Cstring(pointer(Array{Cchar}(undef, 1024*8))) - ierr = license(lic, slicmsg) + buffer = Array{Cchar}(undef, 1024*8) + buffer_p = pointer(buffer) + ierr = GC.@preserve buffer begin + license(lic, Cstring(buffer_p)) + end # check LIC TYPE if ierr == 16 diff --git a/src/utils.jl b/src/utils.jl index 3969c6d4..bc0ab024 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -19,13 +19,15 @@ function invoke(f::Function, pos::Int, ::Type{Int}, args...) end function invoke(f::Function, pos::Int, ::Type{String}, args...) - out = Cstring(pointer(Array{Cchar}(undef, 1024))) - - args = collect(Any, args) - insert!(args, pos-1, out) - - r = f(args...) - r != 0 ? throw(XpressError(r, "Unable to invoke $f")) : unsafe_string(out) + buffer = Array{Cchar}(undef, 1024) + buffer_p = pointer(buffer) + GC.@preserve buffer begin + out = Cstring(buffer_p) + args = collect(Any, args) + insert!(args, pos-1, out) + r = f(args...) + return r != 0 ? throw(XpressError(r, "Unable to invoke $f")) : unsafe_string(out) + end end """ @@ -88,15 +90,50 @@ macro invoke(expr) return f end +function get_xpress_error_message(xprs_ptr) + "(Unable to extract error message for $(typeof(xprs_ptr)).)" +end + +""" + @checked f(prob) + +Lets you invoke a lower level `Lib` function and check that Xpress does not error. +Use this macro to minimize repetition and increase readability. + +The first argument must be a object that can be cast into an Xpress pointer, e.g. `Ptr{XpressProblem}`. +This is passed to `get_xpress_error_message(xprs_ptr)` to get the error message. +Examples: + + @checked Lib.XPRSsetprobname(prob, name) + +As an example of what @checked expands to: + +``` +julia> @macroexpand @checked Lib.XPRSsetprobname(prob, name) +quote + r = Lib.XPRSsetprobname(prob, name) + if r != 0 + xprs_ptr = prob + e = get_xpress_error_message(xprs_ptr) + throw(XpressError(r, "Unable to call `Xpress.setprobname`:\n\n\$(e).\n")) + else + nothing + end +end +``` + +""" macro checked(expr) @assert expr.head == :call "Can only use @checked on function calls" @assert ( expr.args[1].head == :(.) ) && ( expr.args[1].args[1] == :Lib) "Can only use @checked on Lib.\$function" + @assert length(expr.args) >= 2 "Lib.\$function must be contain atleast one argument and the first argument must be of type XpressProblem" f = replace(String(expr.args[1].args[2].value), "XPRS" => "") return esc(quote r = $(expr) if r != 0 - e = lstrip(Xpress.getlasterror(prob), ['?']) + xprs_ptr = $(expr.args[2]) + e = get_xpress_error_message(xprs_ptr) throw(XpressError(r, "Unable to call `Xpress.$($f)`:\n\n$e.\n")) else nothing diff --git a/test/runtests.jl b/test/runtests.jl index 5a4005c5..10e6b512 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -16,4 +16,6 @@ end @test Xpress.getcontrol(prob, "HEURTHREADS") == 0 @test Xpress.getcontrol(prob, :HEURTHREADS) == 0 + msg = "Unable to call `Xpress.copyprob`:\n\n91 Error: No problem has been input.\n" + @test_throws Xpress.XpressError(32,msg) Xpress.copyprob(prob, prob) end