Difference between revisions of "Libcom usage example"

From LSDevLinux
Jump to: navigation, search
m (Reverted edits by Okopacare (Talk); changed back to last version by Mstrobert)
 
Line 1: Line 1:
=[http://amofuryqimu.co.cc UNDER COSTRUCTION, PLEASE SEE THIS POST IN RESERVE COPY]=
 
 
[[libcom]] provides an implementation of connecting Component Object Model (COM) [http://msdn2.microsoft.com/en-us/library/ms680573.aspx][http://www.microsoft.com/com/default.mspx]  clients and servers on Linux, similar to ole32.dll on Windows.
 
[[libcom]] provides an implementation of connecting Component Object Model (COM) [http://msdn2.microsoft.com/en-us/library/ms680573.aspx][http://www.microsoft.com/com/default.mspx]  clients and servers on Linux, similar to ole32.dll on Windows.
  
Line 22: Line 21:
 
# Build the C# test program, referencing Jar.dll and IBakeryMetadata.dll.
 
# Build the C# test program, referencing Jar.dll and IBakeryMetadata.dll.
 
# Windows$ tlbexp Jar.dll /out:Jar.tlb
 
# Windows$ tlbexp Jar.dll /out:Jar.tlb
# Create a one-line file, temp.cpp, with contents: #import "Jar.tlb"
+
# Create a one-line file, temp.cpp, with contents: #import "Jar.tlb"
 
# Windows$ cl /LD temp.cpp
 
# Windows$ cl /LD temp.cpp
 
# Copy the resulting jar.tlh,jar.tli to Linux.
 
# Copy the resulting jar.tlh,jar.tli to Linux.
Line 40: Line 39:
 
  // IBakery.idl
 
  // IBakery.idl
 
    
 
    
  import "oaidl.idl";
+
  import "oaidl.idl";
  import "ocidl.idl";
+
  import "ocidl.idl";
 
    
 
    
 
  [uuid(af4e32b4-93ce-41fc-9ed7-c3b442313152), version(1.0)]
 
  [uuid(af4e32b4-93ce-41fc-9ed7-c3b442313152), version(1.0)]
 
  library IBakeryLib
 
  library IBakeryLib
 
  {
 
  {
   importlib("stdole32.tlb");
+
   importlib("stdole32.tlb");
   importlib("stdole2.tlb");
+
   importlib("stdole2.tlb");
 
    
 
    
 
   [object, uuid(cbdb8382-a142-489a-8831-2b60069d2841)]
 
   [object, uuid(cbdb8382-a142-489a-8831-2b60069d2841)]
Line 79: Line 78:
 
     s//#define \2_\3 __uuidof(\3)/
 
     s//#define \2_\3 __uuidof(\3)/
 
     }
 
     }
   ' IBakery_idl-original.h > IBakery_idl.h
+
   ' IBakery_idl-original.h > IBakery_idl.h
 
This changes things like
 
This changes things like
 
  EXTERN_C const IID IID_IBakery;
 
  EXTERN_C const IID IID_IBakery;
Line 85: Line 84:
 
  #define IID_IBakery __uuidof(IBakery)
 
  #define IID_IBakery __uuidof(IBakery)
 
Generate GUIDs that will work without Microsoft's compiler:
 
Generate GUIDs that will work without Microsoft's compiler:
  echo "#include \"IBakery_idl.h\"" > IBakery_GUIDs.cpp
+
  echo "#include \"IBakery_idl.h\"" > IBakery_GUIDs.cpp
 
  sed -n -e '
 
  sed -n -e '
   /MIDL_INTERFACE("/{
+
   /MIDL_INTERFACE("/{
 
   h
 
   h
 
   n
 
   n
 
   G
 
   G
 
   s/[ \t]*:.*\n//
 
   s/[ \t]*:.*\n//
   s/^[ \t]*/template<> const GUID __uuidof(/
+
   s/^[ \t]*/template<> const GUID __uuidof(/
 
   s/[ \t]*MIDL_INTERFACE/)/
 
   s/[ \t]*MIDL_INTERFACE/)/
 
   s/$/;/
 
   s/$/;/
 
   p
 
   p
 
   }
 
   }
   /DECLSPEC_UUID(&quot;/{
+
   /DECLSPEC_UUID("/{
 
   h
 
   h
 
   n
 
   n
 
   G
 
   G
   s/^/template&lt;&gt; const GUID __uuidof(/
+
   s/^/template<> const GUID __uuidof(/
 
   s/class DECLSPEC_UUID/)/
 
   s/class DECLSPEC_UUID/)/
 
   s/[ \t]*;.*\n//
 
   s/[ \t]*;.*\n//
Line 107: Line 106:
 
   p
 
   p
 
   }
 
   }
   ' IBakery_idl.h &gt;&gt; IBakery_GUIDs.cpp
+
   ' IBakery_idl.h >> IBakery_GUIDs.cpp
 
This produces a short file such as
 
This produces a short file such as
  #include &quot;IBakery_idl.h&quot;
+
  #include "IBakery_idl.h"
  template&lt;&gt; const GUID __uuidof(IBakery)(&quot;cbdb8382-a142-489a-8831-2b60069d2841&quot;);
+
  template<> const GUID __uuidof(IBakery)("cbdb8382-a142-489a-8831-2b60069d2841");
  template&lt;&gt; const GUID __uuidof(Bakery)(&quot;01fe1db4-5cfc-482b-9aae-266d94f5ece8&quot;);
+
  template<> const GUID __uuidof(Bakery)("01fe1db4-5cfc-482b-9aae-266d94f5ece8");
 
Processing the _idl.h can be done automatically in the build process using scripts in our source repository.
 
Processing the _idl.h can be done automatically in the build process using scripts in our source repository.
  
Line 118: Line 117:
 
  // Bakery.h
 
  // Bakery.h
 
   
 
   
  #include &quot;IBakery_idl.h&quot;
+
  #include "IBakery_idl.h"
  #include &quot;COMLibrary.h&quot;
+
  #include "COMLibrary.h"
 
   
 
   
 
  class Bakery : public IBakery {
 
  class Bakery : public IBakery {
Line 127: Line 126:
 
    
 
    
 
   // IUnknown functions
 
   // IUnknown functions
   virtual HRESULT __stdcall QueryInterface(const IID&amp; interfaceid, void** objectInterface);
+
   virtual HRESULT __stdcall QueryInterface(const IID& interfaceid, void** objectInterface);
 
   virtual ULONG __stdcall AddRef();
 
   virtual ULONG __stdcall AddRef();
 
   virtual ULONG __stdcall Release();
 
   virtual ULONG __stdcall Release();
Line 145: Line 144:
 
  public:
 
  public:
 
   // IUnknown functions
 
   // IUnknown functions
   virtual HRESULT __stdcall QueryInterface(const IID&amp; interfaceid, void** objectInterface);
+
   virtual HRESULT __stdcall QueryInterface(const IID& interfaceid, void** objectInterface);
 
   virtual ULONG __stdcall AddRef();
 
   virtual ULONG __stdcall AddRef();
 
   virtual ULONG __stdcall Release();
 
   virtual ULONG __stdcall Release();
 
   
 
   
 
   // IClassFactory functions
 
   // IClassFactory functions
   virtual HRESULT __stdcall CreateInstance(IUnknown* outerAggregateIUnknown, const IID&amp; interfaceid, void** objectInterface);
+
   virtual HRESULT __stdcall CreateInstance(IUnknown* outerAggregateIUnknown, const IID& interfaceid, void** objectInterface);
 
   virtual HRESULT __stdcall LockServer(BOOL shouldLock);
 
   virtual HRESULT __stdcall LockServer(BOOL shouldLock);
 
   
 
   
Line 163: Line 162:
 
  // Bakery.cpp
 
  // Bakery.cpp
 
   
 
   
  #include &quot;Bakery.h&quot;
+
  #include "Bakery.h"
  #include &quot;jar.tlh&quot;
+
  #include "jar.tlh"
  #include &lt;stdexcept&gt;
+
  #include <stdexcept>
 
    
 
    
 
  static volatile LONG g_serverLockCount = 0;
 
  static volatile LONG g_serverLockCount = 0;
Line 175: Line 174:
 
   GUID IID_IJar = __uuidof(Jar::IJar);
 
   GUID IID_IJar = __uuidof(Jar::IJar);
 
   Jar::IJar * jarinstance;
 
   Jar::IJar * jarinstance;
   long hr = jar-&gt;QueryInterface(IID_IJar, (void**)&amp;jarinstance);
+
   long hr = jar->QueryInterface(IID_IJar, (void**)&jarinstance);
 
   if (FAILED(hr)) return hr;
 
   if (FAILED(hr)) return hr;
 
   
 
   
   &lt;b&gt;// C++ calling a method on a C# object
+
   <b>// C++ calling a method on a C# object
   hr = jarinstance-&gt;AddCookies(amount);&lt;/b&gt;
+
   hr = jarinstance->AddCookies(amount);</b>
 
   if (FAILED(hr)) return hr;
 
   if (FAILED(hr)) return hr;
 
   cookiesBaked += amount;
 
   cookiesBaked += amount;
Line 201: Line 200:
 
  {
 
  {
 
   [ComVisible(true)]
 
   [ComVisible(true)]
   [Guid(&quot;5e139206-aca8-4530-b2b9-308e16f81bda&quot;)]
+
   [Guid("5e139206-aca8-4530-b2b9-308e16f81bda")]
 
   [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
 
   [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
 
   public interface IJar
 
   public interface IJar
Line 212: Line 211:
 
   
 
   
 
   [ComVisible(true)]
 
   [ComVisible(true)]
   [Guid(&quot;b1de8884-c20c-4743-a1b3-851daad6d188&quot;)]
+
   [Guid("b1de8884-c20c-4743-a1b3-851daad6d188")]
 
   [ClassInterface(ClassInterfaceType.None)]
 
   [ClassInterface(ClassInterfaceType.None)]
   [ProgId(&quot;Kitchen.Jar&quot;)]
+
   [ProgId("Kitchen.Jar")]
 
   public class Jar : IJar
 
   public class Jar : IJar
 
   {
 
   {
Line 229: Line 228:
 
  Windows$ tlbexp Jar.dll /out:Jar.tlb
 
  Windows$ tlbexp Jar.dll /out:Jar.tlb
 
Create a one-line file, temp.cpp, with contents:
 
Create a one-line file, temp.cpp, with contents:
  #import &quot;Jar.tlb&quot;
+
  #import "Jar.tlb"
 
Create C++ headers from Jar.tlb:
 
Create C++ headers from Jar.tlb:
 
  Windows$ cl /LD temp.cpp
 
  Windows$ cl /LD temp.cpp
Line 250: Line 249:
 
   public static void Main(string[] args) {
 
   public static void Main(string[] args) {
 
   
 
   
   &lt;b&gt;// Create C++ object
+
   <b>// Create C++ object
 
   Bakery bakery = new Bakery();
 
   Bakery bakery = new Bakery();
 
   // Create C# object
 
   // Create C# object
Line 256: Line 255:
 
   
 
   
 
   // Pass C# object to C++ object
 
   // Pass C# object to C++ object
   bakery.bakeCookies(8, jar);&lt;/b&gt;
+
   bakery.bakeCookies(8, jar);</b>
 
   
 
   
   System.Console.WriteLine(&quot;Number of cookies in jar: &quot; + jar.GetNumberCookies());
+
   System.Console.WriteLine("Number of cookies in jar: " + jar.GetNumberCookies());
   System.Console.WriteLine(&quot;Number of cookies baked: &quot; + bakery.getNumberCookiesBaked());
+
   System.Console.WriteLine("Number of cookies baked: " + bakery.getNumberCookiesBaked());
 
   }
 
   }
 
  }
 
  }

Latest revision as of 10:36, 24 November 2010

libcom provides an implementation of connecting Component Object Model (COM) [1][2] clients and servers on Linux, similar to ole32.dll on Windows.

.NET objects and COM components interact using Runtime Callable Wrappers (RCWs) and COM Callable Wrappers (CCWs). RCWs and CCWs are implemented in Mono.

This is an example of using libcom and Mono to:

  • Access a C++ COM server from C#
  • Access a C# COM server from a C++ COM server


Abstract

  1. Create a C# test program.
  2. Create a C# interface IJar and class Jar, with some COM-related attributes.
  3. Create a C++ COM server, Bakery.
  4. Create an IBakery.idl file.
  5. Windows$ midl /Oicf /env win32 /tlb IBakery.tlb /h IBakery_idl.h IBakery.idl
  6. Windows$ tlbimp IBakery.tlb /out:IBakeryMetadata.dll
  7. Tweak IBakery_idl.h with a script to make it compatible with libcom.
  8. Build the C# Jar to Jar.dll.
  9. Build the C# test program, referencing Jar.dll and IBakeryMetadata.dll.
  10. Windows$ tlbexp Jar.dll /out:Jar.tlb
  11. Create a one-line file, temp.cpp, with contents: #import "Jar.tlb"
  12. Windows$ cl /LD temp.cpp
  13. Copy the resulting jar.tlh,jar.tli to Linux.
  14. Tweak jar.tlh,jar.tli to make them compile.
  15. Bakery #includes jar.tlh.
  16. Build the C++ Bakery COM server to an .so.
  17. Run the C# test program.

Access C++ COM server from C#

To access a C++ COM server from C#, you will need an interop assembly file. Mono will use this when it creates an RCW to allow the C# code to transparently access the COM server.

To do this, do the following:

Create a C++ COM server

Creating IBakeryMetadata.dll, IBakery_idl.h, and IBakery_GUIDs.cpp

Create an IDL file:

// IBakery.idl
 
import "oaidl.idl";
import "ocidl.idl";
 
[uuid(af4e32b4-93ce-41fc-9ed7-c3b442313152), version(1.0)]
library IBakeryLib
{
 importlib("stdole32.tlb");
 importlib("stdole2.tlb");
 
 [object, uuid(cbdb8382-a142-489a-8831-2b60069d2841)]
 interface IBakery : IUnknown
 {

  [id(1)] 
  HRESULT getNumberCookiesBaked(
   [out, retval] long * number);

  [id(2)]
  HRESULT bakeCookies( 
   [in] long amount,
   [in] IUnknown * jar);
 }
 
 [uuid(01fe1db4-5cfc-482b-9aae-266d94f5ece8)]
 coclass Bakery
 {
  [default] interface IBakery;
 }
}

In Windows, process the IDL file into _idl.h and TLB files:

Windows$ midl /Oicf /env win32 /tlb IBakery.tlb /h IBakery_idl.h IBakery.idl

Also in Windows, process the TLB into an interop assembly:

Windows$ tlbimp IBakery.tlb /out:IBakeryMetadata.dll

Process the _idl.h file to make it compatible with libcom:

$ sed -e '
   /EXTERN_C const \(IID\|CLSID\|LIBID\|DIID\) \(IID\|CLSID\|LIBID\|DIID\)_\(..*\);/{
    s//#define \2_\3 __uuidof(\3)/
   }
  ' IBakery_idl-original.h > IBakery_idl.h

This changes things like

EXTERN_C const IID IID_IBakery;

to

#define IID_IBakery __uuidof(IBakery)

Generate GUIDs that will work without Microsoft's compiler:

echo "#include \"IBakery_idl.h\"" > IBakery_GUIDs.cpp
sed -n -e '
 /MIDL_INTERFACE("/{
  h
  n
  G
  s/[ \t]*:.*\n//
  s/^[ \t]*/template<> const GUID __uuidof(/
  s/[ \t]*MIDL_INTERFACE/)/
  s/$/;/
  p
 }
 /DECLSPEC_UUID("/{
  h
  n
  G
  s/^/template<> const GUID __uuidof(/
  s/class DECLSPEC_UUID/)/
  s/[ \t]*;.*\n//
  s/$/;/
  p
 }
 ' IBakery_idl.h >> IBakery_GUIDs.cpp

This produces a short file such as

#include "IBakery_idl.h"
template<> const GUID __uuidof(IBakery)("cbdb8382-a142-489a-8831-2b60069d2841");
template<> const GUID __uuidof(Bakery)("01fe1db4-5cfc-482b-9aae-266d94f5ece8");

Processing the _idl.h can be done automatically in the build process using scripts in our source repository.

Creating Bakery.h, Bakery.cpp

IBakery is defined in IBakery_idl.h. Make a C++ header file to define Bakery.

// Bakery.h

#include "IBakery_idl.h"
#include "COMLibrary.h"

class Bakery : public IBakery {
public:
 Bakery(void);
 ~Bakery(void);
 
 // IUnknown functions
 virtual HRESULT __stdcall QueryInterface(const IID& interfaceid, void** objectInterface);
 virtual ULONG __stdcall AddRef();
 virtual ULONG __stdcall Release();

 virtual HRESULT getNumberCookiesBaked(long * number);
 virtual HRESULT bakeCookies(long amount, IUnknown * jar);

private:
 /** Number of cookies this Bakery has baked */
 long cookiesBaked;
 /** Bakery reference count */
 volatile LONG m_referenceCount; 
};

class CFactory : public IClassFactory
{
public:
 // IUnknown functions
 virtual HRESULT __stdcall QueryInterface(const IID& interfaceid, void** objectInterface);
 virtual ULONG __stdcall AddRef();
 virtual ULONG __stdcall Release();

 // IClassFactory functions
 virtual HRESULT __stdcall CreateInstance(IUnknown* outerAggregateIUnknown, const IID& interfaceid, void** objectInterface);
 virtual HRESULT __stdcall LockServer(BOOL shouldLock);

 CFactory() : m_referenceCount((LONG)1) {}
 ~CFactory() {}

private:
 volatile LONG m_referenceCount;
};

Make a C++ file to implement the IBakery.idl interface.

// Bakery.cpp

#include "Bakery.h"
#include "jar.tlh"
#include <stdexcept>
 
static volatile LONG g_serverLockCount = 0;
static CFactory classFactory;

EXTERN_C HRESULT DllGetClassObject(REFCLSID requestedClassID, REFIID requestedInterfaceID, LPVOID * objectInterface) ...

HRESULT Bakery::bakeCookies(long amount, IUnknown * jar) {
 GUID IID_IJar = __uuidof(Jar::IJar);
 Jar::IJar * jarinstance;
 long hr = jar->QueryInterface(IID_IJar, (void**)&jarinstance);
 if (FAILED(hr)) return hr;

 // C++ calling a method on a C# object
 hr = jarinstance->AddCookies(amount);
 if (FAILED(hr)) return hr;
 cookiesBaked += amount;

 return S_OK;
}

... definiton of Bakery and CFactory methods in Bakery.h ...

Create components.map

In Windows, COM servers are registered in the Windows Registry. libcom instead uses GUID to DLL mapping information recorded in a text file, components.map.

Access C# COM server from C++

Create a C# COM server

Put attributes on the interface, interface methods, and class.

// Jar.cs

using System;
using System.Runtime.InteropServices;
namespace Kitchen
{
 [ComVisible(true)]
 [Guid("5e139206-aca8-4530-b2b9-308e16f81bda")]
 [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
 public interface IJar
 {
  [DispId(1)]
  int GetNumberCookies();
  [DispId(2)]
  void AddCookies(int number);
 }

 [ComVisible(true)]
 [Guid("b1de8884-c20c-4743-a1b3-851daad6d188")]
 [ClassInterface(ClassInterfaceType.None)]
 [ProgId("Kitchen.Jar")]
 public class Jar : IJar
 {
  public Jar() {}
  public int GetNumberCookies() { return m_numberCookies; }
  public void AddCookies(int number) { m_numberCookies += number; }
  private int m_numberCookies;
 }
}

Create C++ headers for the C# class

Build and copy the Jar.dll assembly to Windows. Create a TLB from Jar.dll:

Windows$ tlbexp Jar.dll /out:Jar.tlb

Create a one-line file, temp.cpp, with contents:

#import "Jar.tlb"

Create C++ headers from Jar.tlb:

Windows$ cl /LD temp.cpp

Copy the resulting jar.tlh,jar.tli to Linux.

Tweak jar.tlh,jar.tli to make them compile.

Bakery.cpp #includes jar.tlh.

Process jar.tlh to produce Jar_GUIDs.cpp.

Create test program

// TestProgram.cs
using System;
using System.Runtime.InteropServices;
using System.Reflection;
using IBakeryMetadata; // C# stubs to C++ class

public class TestProgram {
 public static void Main(string[] args) {

  // Create C++ object
  Bakery bakery = new Bakery();
  // Create C# object
  Kitchen.Jar jar = new Kitchen.Jar();

  // Pass C# object to C++ object
  bakery.bakeCookies(8, jar);

  System.Console.WriteLine("Number of cookies in jar: " + jar.GetNumberCookies());
  System.Console.WriteLine("Number of cookies baked: " + bakery.getNumberCookiesBaked());
 }
}

Compiling

$ g++ -I.../libcom-headers -c Bakery.cpp
$ g++ -I.../libcom-headers -c IBakery_GUIDs.cpp
$ g++ -lcom IBakery_GUIDs.o Bakery.o -shared -o libbakery.so
$ gmcs -out:TestProgram.exe TestProgram.cs -r:IBakeryMetadata.dll -r:Jar.dll

Explanation of what's happening

In C#, the test program creates a C# Jar and also requests the creation of a Bakery (which it sees as C#). Mono loads libcom (via symlink from ole32.dll) and calls CoCreateInstance. libcom finds the Bakery .so, loads it, creates a Bakery, and gives it to Mono. Mono makes an RCW to present the Bakery C++ COM server to the C# test program as though it were a C# object.

The C# test program then calls a method on Bakery and passes the C# Jar to it as an argument. Mono creates a CCW for Jar so that C++ can access Jar through a COM interface and call methods on Jar as though it were C++.

In C++, a C# Jar method (AddCookies) is called to increment a counter in the Jar, and then the C++ Bakery method returns.

Back in C#, the C# Jar is examined, and the counter is seen to have been incremented.



Code examples are Copyright (C) 2007 SIL International and licensed under the LGPL. Details are in the real files in the source repository.