Using QPDF with C# - c#

I am attempting to translate this qpdf command:
qpdf --qdf --object-streams=disable input.pdf editable.pdf
into the equivalent method calls I would need when using the qpdf dll (available from here: https://sourceforge.net/projects/qpdf/).
I ran the qpdf dll through dumpbin to get the function names, and by looking at the header files that were included for use with a c++ project I can see the parameters for the functions.
For example the function needed to impart the --object-streams option above would (from what I can tell) be this function:
void setObjectStreamMode(qpdf_object_stream_e);
from the c++ header file which becomes:
[DllImport("qpdf21.dll")]
static extern void _ZN10QPDFWriter19setObjectStreamModeE20qpdf_object_stream_e(int stateEnum);
in the C# file.
The problem is when I use the above function I get an
AccessViolationException: Attempted to read or write protected memory
error, which makes me think I need to create a QPDF object somehow, but I have never used object oriented pinvokes, so I'm at a loss of how to make the object accessible in c#.
If anyone is already familiar with using the dll in C#, or even in C++, and could tell me the correct functions to call to replicate the command I would appreciate it!

I've managed to figure it out, the below code replicates the command, turns out I was looking into the wrong header file:
[DllImport("qpdf21.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
static extern IntPtr qpdf_init();
[DllImport("qpdf21.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
static extern void qpdf_cleanup(ref IntPtr qpdfData);
[DllImport("qpdf21.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
static extern int qpdf_read(IntPtr qpdfdata, [MarshalAs(UnmanagedType.LPStr)] string fileName, IntPtr password);
[DllImport("qpdf21.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
static extern void qpdf_set_object_stream_mode(IntPtr qpdf, int mode);
[DllImport("qpdf21.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
static extern void qpdf_set_qdf_mode(IntPtr qpdf, int value);
[DllImport("qpdf21.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
static extern int qpdf_init_write(IntPtr qpdf, [MarshalAs(UnmanagedType.LPStr)] string fileName);
[DllImport("qpdf21.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
static extern int qpdf_write(IntPtr qpdf);
static void Main(string[] args)
{
//call init
IntPtr qpdfData = qpdf_init();
//call read (which gets and processes the input file)
//0 result == good
int result = qpdf_read(qpdfData, #"scan.pdf", IntPtr.Zero);
//call init_write
result = qpdf_init_write(qpdfData, #"scanEditable.pdf");
//set write options
//disable object stream mode
qpdf_set_qdf_mode(qpdfData, 1);
qpdf_set_object_stream_mode(qpdfData, 0);
//call write
result = qpdf_write(qpdfData);
//call cleanup
qpdf_cleanup(ref qpdfData);
}

Looks like you've figured out a good answer here. You've discovered the C API, which is intended for helping to use QPDF from languages other than C++ through the DLL. The C API is primarily documented in the qpdf-c.h header file. You can find some information in the Using Other Languages section of the manual as well. The C API does not expose the full functionality of qpdf's C++ library. If you find missing pieces, please feel free to create an issue at github. I try to update the C API when I add new interfaces, but I don't do it for every interface, and some of the functionality in the CLI is implemented directly in the tool and doesn't map to a single library function.
If the C API is not rich enough for your use case, it's also possible to write your own C++ class with some functions declared extern "C", export them, and build an additional DLL that you can use in the manner you have found above. You can look at qpdf-c.cc as an example of how this works.

Related

How to implement a .dll in unity code

I wrote a code in unity to read some data from a measurement plate. Unfortunately the compiler gives the following error out:
Assets/Scribts/Initialize.cs(74,35): error CS0246: The type or namespace name `IDevice' could not be found. Are you missing an assembly reference?
After some research I found out that unity can't work with .dll's out of .NET languages. Thus you have to put it into a native plugin.
Do you know what to do or if there are some sample projects?
Thank you for your help.
There is an entire tutorial on this you can read here: http://www.alanzucconi.com/2015/10/11/how-to-write-native-plugins-for-unity/
The summary is:
1) Put the native plugin into the Assets/Plugins folder.
2) Define the api in a class, like:
[DllImport("TestDLL", EntryPoint = "TestSort")]
public static extern void TestSort(int [] a, int length);
There's an example of sqlite here to look at here: https://github.com/codecoding/SQLite4Unity3d
You'll see the C# code is along the lines of the above:
[DllImport("sqlite3", EntryPoint = "sqlite3_open", CallingConvention=CallingConvention.Cdecl)]
public static extern Result Open ([MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr db);
[DllImport("sqlite3", EntryPoint = "sqlite3_open_v2", CallingConvention=CallingConvention.Cdecl)]
public static extern Result Open ([MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr db, int flags, IntPtr zvfs);
[DllImport("sqlite3", EntryPoint = "sqlite3_open_v2", CallingConvention = CallingConvention.Cdecl)]
public static extern Result Open(byte[] filename, out IntPtr db, int flags, IntPtr zvfs);
[DllImport("sqlite3", EntryPoint = "sqlite3_open16", CallingConvention = CallingConvention.Cdecl)]
public static extern Result Open16([MarshalAs(UnmanagedType.LPWStr)] string filename, out IntPtr db);
[DllImport("sqlite3", EntryPoint = "sqlite3_enable_load_extension", CallingConvention=CallingConvention.Cdecl)]
public static extern Result EnableLoadExtension (IntPtr db, int onoff);
...but also notice how many #ifdef conditions there are.
Building cross platform native bindings is hard work.
Also note this only applies to C/C++ libraries; if you have an existing C# .Net library (eg. NHibernate), the answer is no, you just flat out can't use that DLL; you must get the C# source and recompile it specifically for unity.

AccessViolationException when calling code through DllImport

I'm working on building a wrapper for a win32 dll from the USPS. I've gotten the calls to work when the pointers are passed as parameters because I can allocate and free the memory before I send the value to the function. The problem is when the data is passed back in the return value as a char *.
Here is the call in the C dll I'm working with:
const char* z4LLkGetKeySTD(void);
Here is the C# code for the DLLImport call:
[DllImport("C:\\AMS\\zip4_w32.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall, EntryPoint = "z4SLNKGetVersionSTD",
ExactSpelling = false), ReliabilityContract(Consistency.WillNotCorruptState, Cer.None)]
private static extern IntPtr z4SLNKGetVersionSTD();
The public facing C# method for the above private call:
public static string z4SLNKGetVersion()
{
IntPtr versionPtr = IntPtr.Zero;
string version;
versionPtr = z4SLNKGetVersionSTD();
version = Marshal.PtrToStringAnsi(versionPtr);
versionPtr = IntPtr.Zero;
return version;
}
The code fails sometimes on the call to z4SLNKGetVersionSTD().
I know that returning pointers to strings in C isn't the best idea but I don't have access to the code for the dll.

libvlc_new always return null in vlc 2.1.3

libvlc_new always return null. I have copied libvlc.dll and libvlccore.dll in debug folder of my solution directory.
We have also tried calling libvlc_new(0,null) and set the environment variable "VLC_PLUGIN_PATH " to plugins directory, with same result.
Any pointer what is going wrong/ or what is the best way to access libVlc API programmitically in .net environment.
PLEASE FIND CODE SNIPPET BELOW deveopled in C#, VS2010.
IntPtr instance, player ;
string[] args = new string[] {
"-I", "dummy", "--ignore-config",
#"--plugin-path=D:\plugins",
"--vout-filter=deinterlace", "--deinterlace-mode=blend"
};
instance = LibVlc.libvlc_new(args.length, args);
IntPtr media = LibVlc.libvlc_media_new_location(instance, #"rtsp://username assword#IP_address/path");
player = LibVlc.libvlc_media_player_new_from_media(media);
LibVlc.libvlc_media_player_set_hwnd(player, panel1.Handle);
LibVlc.libvlc_media_player_play(player);
we have done P/Invoke for corresponding library calls as:
[DllImport("D:\\myvlc\\myvlc\\bin\\Debug\\libvlc", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr libvlc_new(int argc, [MarshalAs(UnmanagedType.LPArray,
ArraySubType = UnmanagedType.LPStr)] string[] argv);
[DllImport("D:\\myvlc\\myvlc\\bin\\Debug\\libvlc", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr libvlc_media_new_location(IntPtr p_instance,
[MarshalAs(UnmanagedType.LPStr)] string psz_mrl);
[DllImport("D:\\myvlc\\myvlc\\bin\\Debug\\libvlc", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr libvlc_media_player_new_from_media(IntPtr media);
[DllImport("D:\\myvlc\\myvlc\\bin\\Debug\\libvlc", CallingConvention = CallingConvention.Cdecl)]
public static extern void libvlc_media_player_set_hwnd(IntPtr player, IntPtr drawable);
[DllImport("D:\\myvlc\\myvlc\\bin\\Debug\\libvlc", CallingConvention = CallingConvention.Cdecl)]
public static extern void libvlc_media_player_play(IntPtr player);
The param --plugin-path" was ignored.
By copying the whole plugin directory works with 2.1.3.
Found solution, all you need to do is to copy plugins folder to the debug directory of your visual studio solution folder. Hope this would save some time for others trying the same thing. I have been trying this for almost 1 day.

How to get legacy COM path from Interop assembly

I have interop assembly from I wolud like to get path to orginal COM dll. How this can be done?
Edit:
Here is similar question with post marked as answer but it is so brief that I still don't know what to do.
I have created object from interop dll and used GetModuleHandle( "mycomserver.dll" ) and 0 results returned.
Code looks like that:
class Program
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr GetModuleHandle(string libname);
static void Main(string[] args)
{
IntPtr result = GetModuleHandle(typeof(InteropClass).Module.Name);
Console.WriteLine(result);
}
}
Regards,
jotbek

How to import void * C API into C#?

Given this C API declaration how would it be imported to C#?
int _stdcall z4ctyget(CITY_REC *, void *);
I've been able to get this far:
[DllImport(#"zip4_w32.dll",
CallingConvention = CallingConvention.StdCall,
EntryPoint = "z4ctygetSTD",
ExactSpelling = false)]
private extern static int z4ctygetSTD(ref CITY_REC args, void * ptr);
Naturally in C# the "void *" doesn't compile.
Some Googling indicates that it should be translated as "object." Which seems like it should work. But others indicate that "Void * is called a function pointer in C/C++ terms which in C# terms is a delegate". That doesn't make a whole lot of sense here as what would it delegate to? Some similar calls for other APIs found through Googling use other functions in the respective API. But in this API no other call would make sense.
The documentation for the call shows an example:
z4ctyget(&city, “00000”);
Which seems to show that even a static value could be passed.
It will compile with object in place of the void *. I don't know whether this is right and I haven't had an opportunity to test it (licensing issue).
For the void* parameter you can just use an IntPtr
[DllImport(#"zip4_w32.dll",
CallingConvention = CallingConvention.StdCall,
EntryPoint = "z4ctygetSTD",
ExactSpelling = false)]
private extern static int z4ctygetSTD(ref CITY_REC args, IntPtr ptr);
You can also use void* if you mark your class as unsafe.
It really depends on what the API is looking for in that parameter.
You can add IntPtr or Object* to get past compiler, but you will still need to pass it the correct data when you call it.
As far as I can tell the C declaration of z4ctyget is:
int z4ctyget(CITY_REC *cityrec, char *zipcode);
The second parameter is a 5 character ANSI string representing the zip code at which you want to start your search or "00000" to start at the beginning of the file. So your declaration should be:
[DllImport(#"zip4_w32.dll", CharSet = CharSet.Ansi)]
private extern static int z4ctygetSTD(ref CITY_REC args, string zipcode);

Categories