Calling C shared library function from LibreOffice Basic - c

I'm trying to call a C shared library function from LibreOffice Basic, but I always get "Basic Runtime Error. Not implemented" when it hits the Declare line. It's just for a fun thing but being unable to do it is bugging me.
The Declare statement looks like this:
Declare Function score_word Lib "libscrabblescore.so" (ByRef word As String, ByRef bonus As String) As Integer
The C function delaration looks like this:
int score_word(char* word, char* word_bonuses)
(Maybe ByRef word As String is not the right translation of char* word? I can't find documentation on how to use char* parameters to functions from LibreOffice Basic.)
I validated the shared library itself by calling it using Python's ctypes module:
>>> from ctypes import CDLL
>>> lib = CDLL("/usr/lib/libscrabblescore.so")
>>> lib.score_word("qi", "dw dlq")
42
>>>
(So I have the "Answer to the Ultimate Question of Life, the Universe, and Everything," just not for how to do this in LibreOffice Basic!)
I tried using the absolute path in the Declare statement as well, and it made no difference.
I found a Windows thread on the topic of calling DLL's where the asker said he needed to put the DLL in a specific location (the LibreOffice bin directory) so LibreOffice could access it. There is no LibreOffice bin directory per-se on Linux, and unfortunately there are 351 candidate directories I was able to identify on my machine (my path, and all folders with "libreoffice" in the name or under a folder with "libreoffice" in the name).
I tried a shotgun approach and put a symbolic link to the shared library in all 351 directories, but then Calc hangs on startup. So I removed those, started Calc, and put them all back in place and tried the function. If it was a location thing, you'd think that would work as LibreOffice Basic is supposed to load the library at the point of the Declare. Still no luck.
There was something that looked promising on oooforums but the site times out when I try to view the thread. (EDIT: I managed to view the thread this evening and it was Windows security problem. I turned off all macro security in my LibreOffice and still have the problem.)
So, has anybody ever successfully called a C shared library function from a LibreOffice Basic program that knows what I'm doing wrong? Thanks!

Jonathon Reinhart is correct; the "Declare" command for calling shared libraries is implemented on Windows but not on Linux. I was only able to verify this because of a reference to an OpenOffice bug report on the matter on a blog post.
My first attempt at a solution was to rewrite the function in LibreOffice Basic. It worked, but would take up to 3 seconds to return its results.
I considered giving up and leaving it like that, but it was too unpleasant to have a three second wait. I looked into how to implement a C++ function through UNO, which was overly complex for a fairly trivial task.
Finally what I did was to write a kludge that still gives "instant" results (around 0.025 seconds each function call).
I rewrote the DLL as a C++ console app that takes command line arguments and writes the result to a temp file. Then I replaced my LibreOffice Basic code with a function that calls the C++ console app in blocking mode using Shell and retrieves the results from the file. It's ugly, but it's not a multi-user thing and it works.
In case anyone stumbles across this issue themselves, here's the LibreOffice Basic code I used to do this--it's very simple.
option explicit
function scorewords(wordlist as string, bonuslist as string) as integer
dim cparams as string
dim fileno as integer
dim results_file as string
dim score as integer
if wordlist = "" then
scorewords = 0
exit function
end if
cparams = """" + wordlist + """" + " " + """" + bonuslist + """"
results_file = "/tmp/scrabblescore.dat"
Shell("/usr/bin/getscrabblescore", 6, cparams, true)
fileno = freefile
open results_file for input as fileno
input #fileno, score
close #fileno
kill results_file
scorewords = score
end function

Unless you are limited to Basic then it looks like your question already shows a good solution. Write something like this in Python UNO:
import ctypes
lib = ctypes.cdll.loadLibrary("/usr/lib/libscrabblescore.so")
result = lib.score_word("qi", "dw dlq")
oText.insertString(oTextCursor, result, 0)

Related

Renaming & moving my file based on the size is not always working in c. Why?

I have an application, written in C, which generates various data parameters that I am logging into a text file named debug_log.txt. Whenever this log file reaches 1 MB, I am renaming the filename with timestamp ex debug_log_20200106_133000.txt & moving it in same directory. I am then reopening debug_log.txt to log new parameters.
if(stat("/home/log/debug_log.txt", &statFiledbg) == 0)
{
if(statFiledbg.st_size >= 1048576) // 1MB
{
current_time = time(0);
strftime(time_buffer, sizeof(time_buffer), "%Y%m%d_%H-%M-%S", gmtime(&current_time));
sprintf(strSysCmddbg, "mv /home/log/debug_log.txt /home/log/debug_log%s.txt", time_buffer);
system(strSysCmddbg);
fp_dbglog = freopen("/home/log/debug_log.txt", "w", fp_dbglog);
}
}
The code works most of the time until it doesn't. After running the application for couple days, I see that debug_log.txt grows beyond 1 MB while the last moved & renamed log file is empty.
What could be the reason?
Use the rename function from the C standard library (in stdio.h) and check errno if it failed to know the exact reason why it is failing.
When working with files, and I/O in general, there are many, many things that can go wrong.
One of my senior developer in the company told me so. Is there anything wrong with using system()?
Yes: it is unnecessary (C and POSIX provide you with a function for basic usages like this), nonportable (it assumes you are in a system that has a "mv"), slower (it needs to spawn another process) and wrong for many use cases (eg. here there is no way to know what exactly failed unless you save the textual output of mv).
See questions and answers like Moving a file on Linux in C for an in-depth explanation.

tcl "open" command not working when replacing Tcl_Filesystem with a duplicate

I'm trying to write a custom filesystem for Tcl using the Tclapi (it's work related, won't go into details), but I'm stuck trying to figure out why this is not working.
In this code segment I'm getting the original/native Tcl_Filesystem, copying over all its contents (function pointers) to my_fs, and then calling Tcl_FSRegister on my_fs. Very simple, thought it should work.
// global scope
const Tcl_Filesystem *ori_fs;
Tcl_Filesystem *my_fs;
...
// in Init
// Get the original Tcl_Filesystem.
Tcl_Obj *root_obj = Tcl_NewStringObj("/", -1);
Tcl_IncrRefCount(root_obj);
ori_fs = Tcl_FSGetFileSystemForPath(root_obj);
Tcl_DecrRefCount(root_obj);
// create a duplicate of the original Tcl_Filesystem struct.
my_fs = malloc(sizeof(Tcl_Filesystem));
memmove(my_fs, ori_fs, ori_fs->structureLength);
int ret = Tcl_FSRegister((ClientData)1, my_fs);
if (ret == TCL_ERROR) {
...
When I ran
load <path to .so>/my_fs[info sharedlibextension]
# sanity check
puts [pwd]
set fp [open test.txt]
however, I get this
<my current directory>
while executing
"open test.txt"
invoked from within
"set fp [open test.txt]"
(file "test.tcl" line 3)
Notice how "puts [pwd]" works but not "open test.txt" ?
Replacing "my_fs" with "ori_fs" in the call to Tcl_FSRegister seems to work...
I've already spent far too much time trying to figure this out. I would appreciate if anyone could help me with this!
The native filesystem is special. In particular, there's some places where its identity is used directly: for example, it's the only FS that can have temporary files made on it, it's assumed to own the roots, and it is handled specially in path management. (Well, according to where in the source code there are direct references to the Tcl internal variable tclNativeFilesystem, which isn't something you can cheat at. It's also possibly in read-only memory, so you can't hack around this.)
For most sane uses of a Tcl virtual filesystem, this doesn't matter. Temp files have to be native because you may well be passing them to the OS (e.g., for loading libraries or running programs that were inside the VFS; with these, they have to be copied out or the OS will think “what are you talking about?!”) and you put the things that you are mounting somewhere other than the native root. So long as you're not trying to use a VFS as a security measure (not recommended; there are safe interpreters for that as they offer a stronger sandboxing solution) it shouldn't be a problem as you can just make your code know that it needs to work below a particular location to get things done. (FWIW, it's a bad idea to cd anyway, except in response to user requests, since it changes the meaning of user-supplied relative paths, so good code handles “make everything relative to a defined location” from the start.)

Matlab / M - pass a cell array into a function as input [duplicate]

This question already has answers here:
"Undefined function 'function_name' for input arguments of type 'double'."
(3 answers)
Closed 5 years ago.
I'm a new user of Matlab, can you please help:
I have the following code in an .M file:
function f = divrat(w, C)
S=sqrt(diag(diag(C)));
s=diag(S);
f=sqrt(w'*C*w)/(w'*s);
I have stored this file (divrat.M) in the normal Matlab path, and therefore I'm assuming that Matlab will read the function when it's starting and that this function therefore should be available to use.
However, when I type
>> divrat(w, C)
I get the following error
??? Undefined function or method 'divrat' for input arguments of type 'double'.
What is the error message telling me to do, I can't see any error in the code or the function call?
You get this error when the function isn't on the MATLAB path or in pwd.
First, make sure that you are able to find the function using:
>> which divrat
c:\work\divrat\divrat.m
If it returns:
>> which divrat
'divrat' not found.
It is not on the MATLAB path or in PWD.
Second, make sure that the directory that contains divrat is on the MATLAB path using the PATH command. It may be that a directory that you thought was on the path isn't actually on the path.
Finally, make sure you aren't using a "private" directory. If divrat is in a directory named private, it will be accessible by functions in the parent directory, but not from the MATLAB command line:
>> foo
ans =
1
>> divrat(1,1)
??? Undefined function or method 'divrat' for input arguments of type 'double'.
>> which -all divrat
c:\work\divrat\private\divrat.m % Private to divrat
As others have pointed out, this is very probably a problem with the path of the function file not being in Matlab's 'path'.
One easy way to verify this is to open your function in the Editor and press the F5 key. This would make the Editor try to run the file, and in case the file is not in path, it will prompt you with a message box. Choose Add to Path in that, and you must be fine to go.
One side note: at the end of the above process, Matlab command window will give an error saying arguments missing: obviously, we didn't provide any arguments when we tried to run from the editor. But from now on you can use the function from the command line giving the correct arguments.
The most common cause of this problem is that Matlab cannot find the file on it's search path. Basically, Matlab looks for files in:
The current directory (pwd);
Directly in a directory on the path (to see the path, type path at the command line)
In a directory named #(whatever the class of the first argument is) that is in any directory above.
As someone else suggested, you can use the command which, but that is often unhelpful in this case - it tells you Matlab can't find the file, which you knew already.
So the first thing to do is make sure the file is locatable on the path.
Next thing to do is make sure that the file that matlab is finding (use which) requires the same type as the first argument you are actually passing. I.el, If w is supposed to be different class, and there is a divrat function there, but w is actually empty, [], so matlab is looking for Double/divrat, when there is only a #(yourclass)/divrat. This is just speculation on my part, but this often bites me.
The function itself is valid matlab-code. The problem must be something else.
Try calling the function from within the directory it is located or add that directory to your searchpath using addpath('pathname').
The error code indicates the function definition cannot be found. Make sure you're calling the function from the same workspace as the divrat.m file is stored. And make sure divrat function is not a subfunction, it should be first function declaration in the file. You can also try to call the function from the same divrat.m file in order to see if the problem is with workspace selection or the function.
By the way, why didn't you simply say
s = sqrt(diag(C));
Wouldn't it be the same?
Also, name it divrat.m, not divrat.M. This shouldn't matter on most OSes, but who knows...
You can also test whether matlab can find a function by using the which command, i.e.
which divrat
I am pretty sure that the reason why this problem happened is because of the license of the toolbox (package) in which this function belongs in. Write which divrat and see what will be the result. If it returns path of the function and the comment Has no license available, then the problem is related to the license. That means, license of the package is not set correctly. Mostly it happens if the package (toolbox) of this function is added later, i.e., after installation of the original matlab. Please check and solve the license issue, then it will work fine.

Retrieving Global Variable Values from Command Line

In one particular project, we're trying to embed version information into shared object files. We'd like to be able to use some standard linux tool to parse the shared object to determine the version for automated testing.
Currently I have "const int plugin_version = 14;". I can use 'nm' and 'objdump' and verify that it's there:
00000000000dcfbc r plugin_version
I can't, however, seem to be able to get the value of that variable easily from command line. I figured there'd be a POSIX tool for showing the initialized values for globals. I have contemplated using a format for the variable as the information itself, ie, plugin_version_14, but that seems like a huge hack. Embedding the information in the filename unfortunately is NOT an option. Any other suggestions welcome.
You could embed it as a string
"MAGIC MARKER STRING VERSION: 4.56 END OF MAGIC" then just look for "MAGIC MARKER STRING" in the file and extract the version information that comes after it.
if you make it a standard, you could easily make command line tool to find these embeded strings on all your software.
if you require it also to be an int, a little macro magic will construct both the int and magic string to make sure they are never out of synch.
There's a couple of options I think.
My first instinct is to make sure the version information lives in its own section in the ELF file. You can use objdump -s -j name of section /bin/whatever.
This rather relies on objdump being available of course.
Alternatively you can do what Keith suggested, and just use 'strings', along with a magical marker string. This feels a little hackish, but should work quite well.
Finally, why don't you just add a --version command line option? You can then store the version information however you like, and trivially retrieve it using the one tool which is certain to be installed on any system which has your software.
A terrible hack that I've used in the past is to embed the version information in a variable name, so nm will show:
00000000000dcfbc r plugin_version_14
Why not writing your own tool to get that version in C/C++ ? You could Use dlopen, then dlsym to get the symbol and print its value to standard output. This way you also verify if the symbol is already there. It looks like 20 ~ 30 lines of code to me and about 20 minutes of your life :)
I know that the question is about command line, but writing such a tool yourself should be easy (especially if such a command line tool does not exist).
If the binary is not stripped, you could use gdb to print the variable. (I just tried to script gdb, but it seems to refuse work if stdin is not a tty, maybe expect will do the job ? )
If you can accept using python, this might help:
import struct
import sys
import subprocess
if __name__ == '__main__':
so = sys.argv[1]
sym = sys.argv[2]
addr = subprocess.check_output('nm %s | grep %s' % (so, sym), shell=True)
addr = int(addr.split()[0], 16)
so_file = open(so)
so_file.seek(addr)
data = so_file.read(4)
print struct.unpack('#i', data)[0]
Disclaimer: This script doesn't do any error checking (if you like it I'm sure you can come up with some ;)). It also assumes you're reading a 4-byte native int value.
$ cat global.c
const int plugin_version = 14;
$ python readsym.py global.so plugin_version
14

Any mature solutions to create a instant shell with ELF symbols and c grammar command parser?

I need to make a debug shell inside each c exe(linux enviroment), and my solution is as follows:
Read elf symbols from exe file, build a symbol->address table in
memory;
Run a thread calling readline to accept user input, some thing
like a c function call;
use Lex & yacc to parse the function name and arg list;
Find address of the function in the symbol table;
Call the function with args list;
Every function written can be input as shell command instantly.
I don't think this is a fresh idea, and my question is: Are there any mature codes implemented already?
Thanks for your help!
Sure. If you had working with VxWorks, you'll find WindShell is what you're looking for. I had port a similar shell to Linux. You can download the source from:
https://sourceforge.net/projects/zprj/
Note: don't use the source in commercial products, since they are ported from WindShell. If you do want a shell in commercial fields, then you shall develop one with LEX/YACC.

Resources