How to get contents of va_list in Python? - python

(I'm on Windows.)
I have a callback function in Python that is called from a DLL (I use the ctypes module). One parameter has the type va_list. Is there a way to get the contents of this? I don't get how to read the correct memory locations (here's a description).
The best would be, if there's a va_list parser for Python. Or a DLL that contains functions for getting arguments of a va_list.
Does anyone know of a solution?
I have found a solution:
You can use ctypes to call standard C functions like vsprintf which accepts a va_list parameter. (Formatted printing is want I intended to do).
This is my code to call it:
bufferString = ctypes.create_string_buffer(4096)
ctypes.cdll.msvcrt.vsprintf(bufferString, fmt.raw, ctypes.cast(ap, ctypes.c_void_p))
print ctypes.cast(bufferString, ctypes.c_char_p)
fmt.raw points to the format string, ap is the address of the va_list.

Related

Calling a Go string function from Python ctypes results in segfault

I have a module called test.go that contains two simple Go functions which accept string types:
package main
import (
"fmt"
"C"
)
//export TestConcat
func TestConcat(testArg string, testArg2 string) (string) {
retval := testArg + testArg2
return retval
}
//export TestHello
func TestHello(testArg string) {
fmt.Println("%v\n", testArg)
}
func main(){}
I compile it as a shared library with go build -o test.so -buildmode=c-shared test.go
Then I have a Python module called test.py
import ctypes
from ctypes import cdll
test_strings = [
"teststring1",
"teststring2"
]
if __name__ == '__main__':
lib = cdll.LoadLibrary("./test.so")
lib.TestConcat.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p]
lib.TestHello.argtypes = [ctypes.c_wchar_p]
for test_string in test_strings:
print(
lib.TestConcat("hello", test_string)
)
lib.TestHello(test_string)
Then I run test.py and get a nasty segfault
runtime: out of memory: cannot allocate 279362762964992-byte block (66781184 in use)
fatal error: out of memory
I've tried wrapping the arguments in ctypes.c_wchar_p to no avail.
What am I doing wrong here? And specifically, how does one interact with Go functions that accept string arguments in Python?
The Go's string type is actually something like
type string {
ptr *byte
size int
}
so that is what the Test{Hello|Concat} actually expect—not a pair of pointers but a pair of struct-typed values.
In other words, cgo performs just enough magic to gateway calls from Go to C and back, but it does not perform automatic conversions of values.
You have two options:
Explicitly work with this from your ctypes bindings, if possible.
When compiling your package, cgo generates a header file which contains a C definition for the struct representing a Go string; you could use it right away.
Make the functions exported to C compatible with the C's "type system".
For this, cgo offers helper functions C.CString and C.GoString.
Basically, you can define your API like this:
func TestHello(a, b *C.char) *C.char {
testArg1, testArg2 := C.GoString(a), C.GoString(b)
return C.CString(testArg + TestArg2)
}
Note few caveats here:
Both of these helpers copy the memory of their argument, so the silly example above would work just fine but it would first duplicate the memory blocks pointed to by a and b, then eat up twice as much memory to produce the concatenated string and then copy the memory of the resulting string once again to produce the returned pointer.
IOW, this approach is fine if you're trying to export to C some big chunk of Go code so that these allocations are dwarfed by whatever that chunk does.
Using *C.char is the same as *char in C, so the string is expected to be NUL-terminated; if it's not, use C.GoStringN.
Every memory block allocated by C.CString have to be freed by a call to C.free. And here's a twist: C.free is basically a thin shim to call free() from the linked in libc, so if you can guarantee the complete product (the code fully loaded into memory and (inter)linked using the dymanic linker) has only a single copy of libc linked in, you can call free() from the non-Go code on the memory blocks produced by calls to C.Cstring in the Go code.
A few more random pointers:
I'm not well-versed in Python's ctypes but I'd speculate using ctypes.c_wchar_p is not correct: in C (and C++, FWIW) wchar_t is a type to denote a single fixed-sized "wide character", which is usually a UCS-2/UTF-16 code point, and Go's strings are not composed of these—they may contain arbitrary bytes, and when they are used to contain Unicode text, they are encoded using UTF-8 which is a multi-byte ecoding (a single Unicode code point may be represented by 1 to 4 bytes in the string).
In either case, wchar_t cannot be used for UTF-8 (and actually many seasoned devs beleive it's an abomination).
Please read the docs on cmd/cgo completely before embarking on this project. Really, please do!

Returning a function python-c-api

I am creating Python bindings for a C library.
In C the code to use the functions would look like this:
Ihandle *foo;
foo = MethFunc();
SetArribute(foo, 's');
I am trying to get this into Python. Where I have MethFunc() and SetAttribute() functions that could be used in my Python code:
import mymodule
foo = mymodule.MethFunc()
mymodule.SetAttribute(foo)
So far my C code to return the function looks like this:
static PyObject * _MethFunc(PyObject *self, PyObject *args) {
return Py_BuildValue("O", MethFunc());
}
But that fails by crashing (no errors)
I have also tried return MethFunc(); but that failed.
How can I return the function foo (or if what I am trying to achieve is completely wrong, how should I go about passing MethFunc() to SetAttribute())?
The problem here is that MethFunc() returns an IHandle *, but you're telling Python to treat it as a PyObject *. Presumably those are completely unrelated types.
A PyObject * (or any struct you or Python defines that starts with an appropriate HEAD macro) begins with pointers to a refcount and a type, and the first thing Python is going to do with any object you hand it is deal with those pointers. So, if you give it an object that instead starts with, say, two ints, Python is going to end up trying to access a type at 0x00020001 or similar, which is almost certain to segfault.
If you need to pass around a pointer to some C object, you have to wrap it up in a Python object. There are three ways to do this, from hackiest to most solid.
First, you can just cast the IHandle * to a size_t, then PyLong_FromSize_t it.
This is dead simple to implement. But it means these objects are going to look exactly like numbers from the Python side, because that's all they are.
Obviously you can't attach a method to this number; instead, your API has to be a free function that takes a number, then casts that number back to an IHandle* and calls a method.
It's more like, e.g., C's stdio, where you have to keep passing stdin or f as an argument to fread, instead of Python's io, where you call methods on sys.stdin or f.
But even worse, because there's no type checking, static or dynamic, to protect you from some Python code accidentally passing you the number 42. Which you'll then cast to an IHandle * and try to dereference, leading to a segfault…
And if you were hoping Python's garbage collector would help you know when the object is still referenced, you're out of luck. You need to make your users manually keep track of the number and call some CloseHandle function when they're done with it.
Really, this isn't that much better than accessing your code from ctypes, so hopefully that inspires you to keep reading.
A better solution is to cast the IHandle * to a void *, then PyCapsule_New it.
If you haven't read about capsules, you need to at least skim the main chapter. But the basic idea is that it wraps up a void* as a Python object.
So, it's almost as simple as passing around numbers, but solves most of the problems. Capsules are opaque values which your Python users can't accidentally do arithmetic on; they can't send you 42 in place of a capsule; you can attach a function that gets called when the last reference to a capsule goes away; you can even give it a nice name to show up in the repr.
But you still can't attach any behavior to capsules.
So, your API will still have to be a MethSetAttribute(mymodule, foo) instead of mymeth.SetAttribute(foo) if mymodule is a capsule, just as if it's an int. (Except now it's type-safe.)
Finally, you can build a new Python extension type for a struct that contains an IHandle *.
This is a lot more work. And if you haven't read the tutorial on Defining Extension Types, you need to go thoroughly read through that whole chapter.
But it means that you have an actual Python type, with everything that goes with it.
You can give it a SetAttribute method, and Python code can just call that method. You can give it whatever __str__ and __repr__ you want. You can give it a __doc__. Python code can do isinstance(mymodule, MyMeth). And so on.
If you're willing to use C++, or D, or Rust instead of C, there are some great libraries (PyCxx, boost::python, Pyd, rust-python, etc.) that can do most of the boilerplate for you. You just declare that you want a Python class and how you want its attributes and methods bound to your C attributes and methods and you get something you can use like a C++ class, except that it's actually a PyObject * under the covers. (And it'll even takes care of all the refcounting cruft for you via RAII, which will save you endless weekends debugging segfaults and memory leaks…)
Or you can use Cython, which lets you write C extension modules in a language that's basically Python, but extended to interface with C code. So your wrapper class is just a class, but with a special private cdef attribute that holds the IHandle *, and your SetAttribute(self, s) can just call the C SetAttribute function with that private attribute.
Or, as suggested by user, you can also use SWIG to generate the C bindings for you. For simple cases, it's pretty trivial—just feed it your C API, and it gives you back the code to build your Python .so. For less simple cases, I personally find it a lot more painful than something like PyCxx, but it definitely has a lower learning curve if you don't already know C++.

Python ctype directly call a function with raw offset

Is it possible to call a RAW function pointer inside python?
for example:
ctypes.windll.kernel32.GetModuleHandleA(None)
will call the API, Python resolves the DLL + Function pointer to make it work. but if I have a raw function pointer, for example 0xDEADBEEF (which corresponds to the function header) how do I create a funciton instance passing this raw pointer?
I can't just export the function since its located on a compiled executable, but I want to be able to do something like this:
To make it clear... in C++ I can call RAW function with something like this:
#define myfunction ((void(*)(int a, int b, int c)) 0x00402383)
myfunction(1, 2, 3);
I python I wanted to make something similar, maybe using ctypes library?
I've digged inside the library but I couldn't find how they initialize the function instance.
Notes:
The python engine is embedded inside a DLL.
The DLL is injected into the process I want to call the function address.
If the function is located in a compiled executable, then you can't, neither in C++ because you'll get an access violation. You can't call functions from another process.
If you have C++ knowledge then create a DLL and inject it into the executable. You'll be able to call it.
Since you've already injected your DLL into the target process, it seems to be possible to achieve what you want using ctypes.
>>> functype = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_int)
>>> func = functype(raw_address)
>>> func
<CFunctionType object at 0x1006816d0>

How to specify a non default type when calling a FFmpeg library using ctypes

I am calling an FFMPEG library called avformat_alloc_context() that returns a pointer to type AVFormatContext. The structure AVFormatContext is defined in the avformat library.
Obviously; this is not one ctypes default types and results in an error when I try to pass a reference to it to another ffmpeg library further down in my code.
Is there a way to add in the the classes defined inside of the library ? I read through what documentation I could find online and was not able to find a good answer to this question.
You need to first of all translate the AVFormatContext struct into a ctypes declaration. You do this by deriving from the Structure class.
Then you need to specify that the avformat_alloc_context function returns a pointer to that struct. You assign restype to do so:
avformat_alloc_context.restype = POINTER(AVFormatContext)
Then you can call the function like this:
contextPtr = avformat_alloc_context()
To get at the contents of the pointer, read the contents attribute:
context = contextPtr.contents
And don't forget to call avformat_free_context() when you have finished with the pointer.
This information is all covered in more detail in the ctypes documentation.

Python ctypes argument errors

I wrote a test dll in C++ to make sure things work before I start using a more important dll that I need. Basically it takes two doubles and adds them, then returns the result. I've been playing around and with other test functions I've gotten returns to work, I just can't pass an argument due to errors.
My code is:
import ctypes
import string
nDLL = ctypes.WinDLL('test.dll')
func = nDLL['haloshg_add']
func.restype = ctypes.c_double
func.argtypes = (ctypes.c_double,ctypes.c_double)
print(func(5.0,5.0))
It returns the error for the line that called "func":
ValueError: Procedure probably called with too many arguments (8 bytes in excess)
What am I doing wrong? Thanks.
You probably got the calling conventions mixed up. I'm guessing you have a C function declared something like this:
double haloshg_add(double d1, double s2)
{
return d1+d2;
}
This will use the C calling convention by default. The simplest approach would be to change the calling convention in your ctypes code:
nDLL = ctypes.CDLL('test.dll')
If you wanted to change the calling convention in the C code to stdcall (to match ctypes.WinDLL) then you would do this:
double __stdcall haloshg_add(double d1, double s2)
Whatever you do, only do one of these changes. If you do both you'll have the reverse failure!
If it were me, I'd just change the Python code to use C calling convention (use CDLL). That change has the least impact.

Categories