COM in Linux
The core of SIL FieldWorks was written in C++. New development in FieldWorks is done in C#, and ties into the C++ core through COM.
After much looking all over the Internet in January 2005, I basically concluded that not only does no one use COM in Linux, but no one wanted to (outside of big enterprise application ports to UNIX).
When both porting FieldWorks to Linux and making it cross-platform, we lose access to Microsoft's COM implementation in Windows. We were originally going to use SWIG to generate C# wrappers around the C++ core libraries. All C++ methods would then be P/Invoked, and COM would be bypassed.
There are a few ways to have an ole32.dll in Linux, including
- Create our own ole32.dll
- Use the native Windows ole32.dll using Wine (perhaps not freely distributable?)
- Use something massive like Software AG EntireX or Mainsoft MainWin (not freely distributable)
- Use the Wine ole32.dll.so (as-is, or modified)
The Wine ole32.dll.so was segfaulting when Mono was calling its CoInitialize. I wrote a C program to dlopen Wine 0.9.25's ole32.dll.so's CoInitialize, and it segfaulted in the same way. Wine's CoInitialize may need to be accessed in a different way. Or maybe I should install the whole of Wine, rather than just trying to compile and call into a specific DLL.
A few years ago we had developed a small COM implementation for C++ COM clients to connect to C++ COM servers in Mac and Linux. This wasn't really polished up, and it's taken a while for us to get to the point where we need C++ COM support in Linux, though we are now starting to get some of the C++ core library code to compile.
Using our own COM implementation, and for now some Wine headers, I have been able to make a C# COM client connect to a C++ COM server in Linux. I did this by doing the following:
- Create an ole32.dll that symlinks to our COM implementation .so
- Create a C++ COM server, libBaz.so that ties into our COM implementation
- Create an .idl
- Copy the IDL file to Windows to use midl and tlbimp
- C:\> midl /mktyplib203 /env win32 /tlb "IBaz.tlb" /h "IBaz_idl.h" IBaz.idl
- C:\> tlbimp IBaz.tlb /out:IBazMetadata.dll
- Create a C# program, Client.exe, that references IBazMetadata.dll
- $ g++ -I...wine/include/ -DINITGUID -shared -fpic Baz.cpp .../ole32dir/ole32.dll -o libBaz.so
- $ gmcs Client.cs -r:IBazMetadata.dll
- $ LD_PRELOAD=...ole32dir/ole32.dll:./libBaz.so LD_LIBRARY_PATH=...ole32dir mono Client.exe
The preloaded libBaz.so calls our ole32.dll's RegisterServer(), registering itself so it can be found by its GUIDs. Mono runs Client.exe. Client.exe creates a Baz, which it knows about because of the definitions in IBazMetadata.dll. IBazMetadata.dll just defines the class and has GUID information. Mono sees that it has GUIDs and creates a Runtime Callable Wrapper (RCW) to bridge to the actual Baz implementation. Mono DllImports ole32.dll and calls CoInitialize() and CoCreateInstance(). Our ole32.dll accepts the correct GUID and gives access to the corresponding code in libBaz.so. Client.exe now works with Baz objects across these boundaries, with them appearing to Client.exe as though they were managed .NET objects.
Still to do is to make our COM implementation work in the way the FieldWorks code is expecting, and to do a test involving actual FieldWorks C# and C++. I'd also like to isolate the needed Wine headers, or not use them at all.
While learning about COM, I found that it was difficult to find an introduction to COM programming and IDL on the Internet, and the library didn't have any COM books. Eventually I found two good articles, complete with sample code, at The Code Project: Introduction to COM Part II and Understanding Classic COM Interoperability with .NET Applications. Other useful links include Wikipedia's COM article, MSDN's IDL file information, and the currently out of date but very relevant Mono COM Interop page.