views:

712

answers:

4

Hi,

What is the best way to design a C API for dlls which deals with the problem of passing "objects" which are C runtime dependent (FILE*, pointer returned by malloc, etc...). For example, if two dlls are linked with a different version of the runtime, my understanding is that you cannot pass a FILE* from one dll to the other safely.

Is the only solution to use windows-dependent API (which are guaranteed to work across dlls) ? The C API already exists and is mature, but was designed from a unix POV, mostly (and still has to work on unix, of course).

A: 

The problem with the different runtimes isn't solvable because the FILE* struct belongs to one runtime on a windows system.

But if you write a small wrapper Interface your done and it does not really hurt.

stdcall IFile* IFileFactory(const char* filename, const char* mode);

class IFile {

  virtual fwrite(...) = 0;
  virtual fread(...) = 0;

  virtual delete() = 0; 
}

This is save to be passed accross dll boundaries everywhere and does not really hurt.

P.S.: Be careful if you start throwing exceptions across dll boundaries. This will work quiet well if you fulfill some design creterions on windows OS but will fail on some others.

Totonga
+1  A: 

Neither existing answer is correct: Given the following on Windows: you have two DLLs, each is statically linked with two different versions of the C/C++ standard libraries.

In this case, you should not pass pointers to structures created by the C/C++ standard library in one DLL to the other. The reason is that these structures may be different between the two C/C++ standard library implementations.

The other thing you should not do is free a pointer allocated by new or malloc from one DLL that was allocated in the other. The heap manger may be differently implemented as well.

Note, you can use the pointers between the DLLs - they just point to memory. It is the free that is the issue.

Now, you may find that this works, but if it does, then you are just luck. This is likely to cause you problems in the future.

One potential solution to your problem is dynamically linking to the CRT. For example,you could dynamically link to MSVCRT.DLL. That way your DLL's will always use the same CRT.

Note, I suggest that it is not a best practice to pass CRT data structures between DLLs. You might want to see if you can factor things better.

Note, I am not a Linux/Unix expert - but you will have the same issues on those OSes as well.

Foredecker
I understand the problems - I am asking for answers to this problem :) I was hoping for a solution which does not assume changing existing C API (with FILE* in the signature and so on), but it seems that there isn't if I cannot guarantee everything to link against the same C runtime ?Although the problem is theoretically the same on Unix, it is rarely an issue because there is only one C runtime. I never had a single problem passing FILE*, file descriptors on unix between libraries - a lot of C APIS are designed this way and work flawlessly on those OS.
David Cournapeau
If having FILE* in the signature of the API is not good practice, how do you deal with file streams at all ? Do you need to export the functions to open and close the files if the dll you call has some functions which internally call fprintf and other functions expecting FILE* ?
David Cournapeau
I did suggestion a solution :) Don't link to the CRT statically. link to MSVCRT.DLL. I'd be quite surprised if all CRT libraries on Linux, Unix or the MAC were guaranteed to work flawless. I think you've been lucky there as well.
Foredecker
Ah, sorry, I don't link the CRT statically, so I ignored that part of the message unconsciously :) Since the main program (outside my control) links against msvcrt90.dll, linking against msvcrt.dll would mean 2 different CRT, right ?On unix, you are almost guaranteed that your C objects always refer to the same runtime. It is not so much a question of being lucky, but rather a consequence of the flat namespace on most unices (every malloc in your .so will resolve to the same malloc), and other more cultural reasons (availability of sources, one C compiler, etc...)
David Cournapeau
Dunno about Unix, but when using VC your not guaranteed to be using the same CRT even if your DLL and the EXE using it are built using the exact same version. VC has a different C runtimes for release and debug builds, and they don't mix well. If the DLL is built with the release CRT, and the EXE is being developed with the debug build, they won't be using the same CRT even when linked dynamically.
eran
A: 

If the C API exists and is mature, bypassing the CRT internally by using pure Win32 API stuff gets you half the way. The other half is making sure the DLL's user uses the corresponding Win32 API functions. This will make your API less portable, in both use and documentation. Also, even if you go this way with memory allocation, where both the CRT functions and the Win32 ones deal with void*, you're still in trouble with the file stuff - Win32 API uses handles, and knows nothing about the FILE structure.

I'm not quite sure what are the limitations of the FILE*, but I assume the problem is the same as with CRT allocations across modules. MSVCRT uses Win32 internally to handle the file operations, and the underlying file handle can be used from every module within the same process. What might not work is closing a file that was opened by another module, which involves freeing the FILE structure on a possibly different CRT.

What I would do, if changing the API is still an option, is export cleanup functions for any possible "object" created within the DLL. These cleanup functions will handle the disposal of the given object in the way that corresponds to the way it was created within that DLL. This will also make the DLL absolutely portable in terms of usage. The only worry you'll have then is making sure the DLL's user does indeed use your cleanup functions rather than the regular CRT ones. This can be done using several tricks, which deserve another question...

eran
A: 

You asked for a C, not a C++ solution.

The usual method(s) for doing this kind of thing in C are:

  • Design the modules API to simply not require CRT objects. Get stuff passed accross in raw C types - i.e. get the consumer to load the file and simply pass you the pointer. Or, get the consumer to pass a fully qualified file name, that is opened , read, and closed, internally.

  • An approach used by other c modules, the MS cabinet SD and parts of the OpenSSL library iirc come to mind, get the consuming application to pass in pointers to functions to the initialization function. So, any API you pass a FILE* to would at some point during initialization have taken a pointer to a struct with function pointers matching the signatures of fread, fopen etc. When dealing with the external FILE*s the dll always uses the passed in functions rather than the CRT functions.

With some simple tricks like this you can make your C DLLs interface entirely independent of the hosts CRT - or in fact require the host to be written in C or C++ at all.

Chris Becke