vcdll2.htm

来自「C++builder学习资料C++builder」· HTM 代码 · 共 463 行 · 第 1/2 页

HTM
463
字号
</P> 

<P> 

Inside the COM project, create a new COM object. MSVC will probably want you to create an COM interface. Since we are wrapping a class 

called <TT>CFoo</TT>, a good interface name would be <TT>IFoo</TT>. MSVC will also want you to name the implementation class for the 

COM obect. <TT>CFooImpl</TT> is a good candidate. 

</P> 

<P> 

The COM object should wrap the C++ DLL class using aggregation. In other words, the COM object contains a <TT>CFoo</TT> member variable. 

Don't try to inherit your COM class from <TT>CFoo</TT>. For each member function of the C++ DLL class (<TT>CFoo</TT>), create a similar 

function in your COM object. If possible, use the same name, pass the same arguments, and return the same type of value. You will need to 

tweak some things. For example, strings are usually passed as <TT>BSTR</TT>'s in COM. Also, return values are typically passed as out 

parameters, because the COM method should return an error code. When you are done, each member function of the C++ class should have a 

corresponding function in the COM wrapper. 

</P> 

<P> 

After you build the COM wrapper, register it with regsrv32.exe. Once you do that, you should be able to instantiate the COM object 

and call its wrapper member functions from your BCB code. 

</P> 

<P> 

Once again, I apologize for not having a working demo of this technique ready to go. 

</P> 

 

 

<H3> 

<A NAME="technique3">Technique 3: Use an abstract base class with virtual functions (pseudo-COM)</A> 

</H3> 

Technique 3 is a pseudo-COM approach. COM is a binary object specification. A COM object can be called from both 

BCB and MSVC, regardless of the compiler that the COM object was compiled with. So how does this binary magic work? 

The answer is the foundation for this technique. 

<P> 

COM function calls are dispatched via a function lookup table. Miraculously, this function lookup table works exactly the same way that 

virtual function tables work in C++. In fact, they are one in the same. COM is a glorified form of virtual functions and vtables. 

</P> 

<P> 

COM works because BCB and MSVC employ <I>exactly</I> the same virtual dispatching system. COM relies on the fact that C++ 

compilers all generate and use vtables the same way. Because the two compilers use the same virtual dispatching system, we can 

create a wrapper class with virtual functions in MSVC that can be called from BCB. This is exactly what COM does. 

</P> 

<P> 

Here is the DLL header file for the pseudo-COM wrapper class. It consists of an abstract base class, <TT>IFoo</TT>, that serves as the 

pseudo-COM interface. It also consists of two C functions for creating and deleting <TT>IFoo</TT> objects. This header file is shared between 

MSVC and BCB. 

</P> 

<pre>

<font color="green">#ifndef DLL_H</font>

<font color="green">#define DLL_H</font>



<font color="green">#ifdef BUILD_DLL</font>

<font color="green">#define DLLAPI __declspec(dllexport)</font>

<font color="green">#else</font>

<font color="green">#define DLLAPI __declspec(dllimport)</font>

<font color="green">#endif</font>



<font color="navy">// psuedo COM interface</font>

<b>class</b> IFoo

<b>{</b>

<b>public</b><b>:</b>

    <b>virtual</b> <b>int</b> <b>__stdcall</b> DoSomething<b>(</b><b>int</b> x<b>)</b> <b>=</b> <font color="blue">0</font><b>;</b>

    <b>virtual</b> <b>__stdcall</b> <b>~</b>IFoo<b>(</b><b>)</b> <b>=</b> <font color="blue">0</font><b>;</b>

<b>}</b><b>;</b>



<font color="green">#ifdef __cplusplus</font>

<b>extern</b> <font color="blue">&quot;C&quot;</font> <b>{</b>

<font color="green">#endif</font>



DLLAPI IFoo<b>*</b>  <b>__stdcall</b> new_IFoo<b>(</b><b>int</b> x<b>)</b><b>;</b>

DLLAPI <b>void</b>   <b>__stdcall</b> delete_IFoo<b>(</b>IFoo <b>*</b>f<b>)</b><b>;</b>



<font color="green">#ifdef __cplusplus</font>

<b>}</b>

<font color="green">#endif</font>



<font color="green">#endif</font>

</pre> 

<P> 

Notice that the two C functions resemble the functions from Technique 1, except that now they work with <TT>IFoo</TT> pointers 

instead of unsafe void pointers. This technique provides a little more type safety than the first. 

</P> 

<P> 

Here is the source code for the MSVC wrapper. It contains a class call <TT>CFooImpl</TT> that inherits from <TT>IFoo</TT>. 

<TT>CFooImpl</TT> is an implementation of the <TT>IFoo</TT> interface. 

</P> 

<pre>

<font color="green">#define BUILD_DLL</font>



<font color="green">#include &quot;dll.h&quot;</font>



IFoo<b>:</b><b>:</b><b>~</b>IFoo<b>(</b><b>)</b>

<b>{</b>

	<font color="navy">// must implement base class destructor</font>

	<font color="navy">// even if its abstract</font>

<b>}</b>



<font color="navy">// Note: we declare the class here because no one outside needs to be concerned</font>

<font color="navy">//       with it.</font>

<b>class</b> CFooImpl <b>:</b> <b>public</b> IFoo

<b>{</b>

<b>private</b><b>:</b>

    CFoo  m_Foo<b>;</b> <font color="navy">// the real C++ class from the existing MSVC C++ DLL</font>

<b>public</b><b>:</b>

    CFooImpl<b>(</b><b>int</b> x<b>)</b><b>;</b>

    <b>virtual</b> <b>~</b>CFooImpl<b>(</b><b>)</b><b>;</b>

    <b>virtual</b> <b>int</b> <b>__stdcall</b> DoSomething<b>(</b><b>int</b> x<b>)</b><b>;</b>

<b>}</b><b>;</b>



CFooImpl<b>:</b><b>:</b>CFooImpl<b>(</b><b>int</b> x<b>)</b>

    <b>:</b> m_Foo<b>(</b>x<b>)</b>

<b>{</b>

<b>}</b>



<b>int</b> <b>__stdcall</b> CFooImpl<b>:</b><b>:</b>DoSomething<b>(</b><b>int</b> x<b>)</b>

<b>{</b>

    <b>return</b> m_Foo<b>.</b>DoSomething<b>(</b>x<b>)</b><b>;</b>

<b>}</b>



CFooImpl<b>:</b><b>:</b><b>~</b>CFooImpl<b>(</b><b>)</b>

<b>{</b>

<b>}</b>



IFoo <b>*</b> <b>__stdcall</b> new_IFoo<b>(</b><b>int</b> x<b>)</b>

<b>{</b>

    <b>return</b> <b>new</b> CFooImpl<b>(</b>x<b>)</b><b>;</b>

<b>}</b>



<b>void</b> <b>__stdcall</b> delete_IFoo<b>(</b>IFoo <b>*</b>f<b>)</b>

<b>{</b>

    <b>delete</b> f<b>;</b>

<b>}</b>

</pre> 

<p> 

There is lots of good stuff going on here. First of all, notice that now we have a class in the header file being shared between BCB 

and MSVC. That <I>seems</I> like it ought to be a good thing. More important than that, notice that the BCB project will only interact 

with the <TT>IFoo</TT> class. The actual implementation of <TT>IFoo</TT> is provided by a derived class call <TT>CFooImpl</TT>, 

which is internal to the MSVC wrapper project. 

</P> 

<P> 

The BCB client code will work with <TT>IFoo</TT> objects polymorphically. To obtain a wrapper instance, the BCB code will call the 

<TT>new_IFoo</TT> function. <TT>new_IFoo</TT> works like a factory function, serving up new <TT>IFoo</TT> instances. <TT>new_Foo</TT> 

returns a pointer to an <TT>IFoo</TT> instance. However, that pointer is polymorphic. The static type of the pointer is <TT>IFoo</TT>, 

but its actaul dynamic type will be a pointer to a <TT>CFooImpl</TT> (a fact that is unbeknownst to the BCB code). 

</P> 

<P> 

Here is the code for the BCB client. 

</P> 

 

<pre>

<font color="green">#include &quot;dll.h&quot;</font>



<b>void</b> bar<b>(</b><b>)</b>

<b>{</b>

    <b>int</b> x <b>=</b> <font color="blue">10</font><b>;</b>

    <b>int</b> y <b>=</b> <font color="blue">20</font><b>;</b>

    <b>int</b> z<b>;</b>





    IFoo <b>*</b>foo <b>=</b> new_IFoo<b>(</b>x<b>)</b><b>;</b>

    z <b>=</b> foo<b>-&gt;</b>DoSomething<b>(</b>y<b>)</b><b>;</b>

    delete_IFoo<b>(</b>foo<b>)</b><b>;</b>

<b>}</b>

</pre> 

<P> 

Now some parting comments on technique 3. First, it is crucial that you delete the <TT>IFoo</TT> pointer from the MSVC DLL. 

This is done by passing the <TT>IFoo</TT> pointer to the <TT>delete_IFoo</TT> function. Don't attempt to delete the object from BCB. 

</P> 

<pre>

<b>void</b> bar<b>(</b><b>)</b>

<b>{</b>

    IFoo <b>*</b>foo <b>=</b> new_IFoo<b>(</b>x<b>)</b><b>;</b>

    <b>delete</b> foo<b>;</b>               <font color="navy">// BOOM!!!</font>

<b>}</b>

</pre> 

<P> 

This code will surely die an agonizing death. The problem is that <TT>IFoo</TT> was created from within the <TT>new_IFoo</TT> 

function in the MSVC wrapper DLL. As such, the memory for the <TT>IFoo</TT> object is allocated by the MSVC memory manager. When you 

delete an object, it is important that you delete it with the same memory manager that was used to create it. If you call delete on the 

pointer from the BCB side, then you will be deleting it with Borland memory manager. Now, I could be wrong, but I would bet my house and 

a reproductive organ or two that the Microsoft memory manager and the Borland memory manager don't conspire to work together. When you 

delete the pointer with the Borland memory manager, it is doubtful that it will attempt to contact the Microsoft memory manager to let it 

know that it should free some memory. 

</p> 

<P> 

Another item of note is that the BCB code works entirely in terms of the <TT>IFoo</TT> abstract interface. You don't see any occurrenct 

of the class <TT>CFooImpl</TT> on the BCB side. <TT>CFooImpl</TT> is internal to the MSVC wrapper project. When you call 

<TT>DoSomething</TT> from the BCB side, the call is dispatched to <TT>CFooImpl</TT> via the virtual table. 

</P> 

<P> 

If you are having troubling understanding this concept, don't worry. I am probably not describing it very well. To help understand what 

is going on, you might want to step through the code on the BCB side using the CPU viewer. This will allow you to step through each 

assembly instruction and see how the vtable lookup works. 

</P> 

 

<BR> 

<H3> 

<A NAME="conclusion">Conclusion:</A> 

</H3> 

<P> 

Ok, so where are we at? Well, at the start of the article, we talked about why BCB can't call C++ member functions in a DLL if that 

DLL was compiled with MSVC. The reason is that the two compilers don't agree on how those member functions should be named. We then 

discussed three (rather unpleasant) work arounds. Each workaround consisted of an MSVC wrapper DLL for the C++ DLL. The wrapper DLL 

exposed the C++ class using some format that BCB would understand. The first technique was to flatten each member function of the C++ 

class into a plain C function. The second technique mapped each member function to a member of a COM object. The last technique relied on 

the fact that virtual functions are dispatched via a lookup table instead of by name. In this strategy, each C++ member function is mapped 

to a virtual member function of an abstract class. 

</P> 

<P> 

The downloads section contains the example code for this article. The first download contains the original MSVC C++ dll that we were 

trying to work with. This same DLL is used by each of the three techniques. The example for Technique 2 is not ready yet. 

</P> 

 

<BR> 

<H3> 

<A NAME="downloads">Downloads</A> 

</H3> 

 

<BR> 

<TABLE  BORDER=1 CELLPADDING=10 CELLSPACING=0 WIDTH="100%"> 

<TR> <TD colspan = 2><B>Downloads for this article</B> </TD> </TR> 

<TR> <TD><TT><a HREF="download/cppdll.zip">cppdll.zip</a></TT></TD><TD>VC++ 5 DLL project with exported C++ CFoo class</TD> </TR>

<TR> <TD><TT><a HREF="download/vcdll2tech1.zip">vcdll2tech1.zip</a></TT></TD><TD>Code for technique #1, flattening a class into C functions</TD> </TR>

<TR> <TD><TT><a HREF="download/vcdll2tech3.zip">vcdll2tech3.zip</a></TT></TD><TD>Code for technique #3, virtual fucntion/abstract base class wrapper</TD> </TR>

</TABLE>







</TD> </TR>



</TABLE>

</CENTER>

</BODY>

</HTML>

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?