vcdll2.htm
来自「C++builder学习资料C++builder」· HTM 代码 · 共 463 行 · 第 1/2 页
HTM
463 行
<HTML>
<HEAD>
<TITLE> Using Visual C++ DLLs in a C++Builder Project Part 2: C++ classes.</TITLE>
<META NAME="Author" CONTENT="Harold Howe">
</HEAD>
<BODY>
<CENTER>
<TABLE BORDER=0 CELLPADDING=0 CELLSPACING=0 WIDTH="640">
<TR>
<TD>
<H2>
Using Visual C++ DLLs in a C++Builder Project<BR>Part 2: C++ classes.
</H2>
<P>
<B>Note: </B> This article describes how to import C++ class from a Visual C++ DLL into an BCB project.
Before we get started, I feel compelled to provide a small warning. This article is not really ready
for mass distribution. I apologize if the 'article' seems choppy, difficult to read, or contains mistakes. I haven't have much
time to polish it up. The only reason I decided to go ahead and publish this is because lots of developers ask how to do this.
I figure that a poorly written article is better than nothing at all. I hope that this incoherent collection of ideas will prove
helpful to you.
</P>
<P>
In a previous article on how to <A HREF="vcdll.htm">use VC++ DLLs from MSVC</A>, I describe how to create a Borland compatible
import library for an MSVC DLL. The main difficulty in using the VC++ DLL is that MSVC and Borland use different formats for
function names. For example, Borland expects <TT>__cdecl</TT> functions to have a leading underscore in front of them, whereas MSVC does
not. Fortunately, you can surmount these naming problems using some of Borland's command line utilites, namely <TT>TDUMP</TT>,
<TT>IMPLIB</TT>, <TT>IMPDEF</TT> and <TT>COFF2OMF</TT>. The idea is that you use the command line tools to create a Borland compatible
import library with Borland compatible function names in it. Once you have a Borland compatible import library, you're set. You can
simply link with the import library to use the MSVC DLL.
</P>
<P>
Unfortunately, this strategy doesn't completely get you out of the woods. At the end of the <A HREF="vcdll.htm">DLL article</A>, I drop a
small bombshell. You can only call functions in the MSVC DLL if they are plain C functions. You can't import classes or class member
functions. Doh!
</P>
<P>
So what do you do if you need to import C++ class from an MSVC DLL? Uh...well, in that case, you are boxed into a corner, and your
choices are limited (and usually when your backed into a corner, your options are not that pleasant). This articles describes three
ways to get yourself out of that corner.
</P>
<P>
<B>Bad news</B>: Before you waste too much time reading this diatribe, I feel, once again, compelled to issue a warning. All three of
my techniques require that you have Microsoft Visual C++. You don't need to have the source code for the DLL that you need to call, but
you do need to have the tool that can call it. Each of the three techniques are more or less wrapping techniques where we use MSVC to
wrap the DLL in something we can use from BCB.
</P>
<P>
<UL>
<LI><A HREF="#summary" >Summary of the three techniques</A>
<LI><A HREF="#technique1" >Technique 1: Flattening the C++ class into a C library</A>
<LI><A HREF="#technique2" >Technique 2: Create a COM wrapper</A>
<LI><A HREF="#technique3" >Technique 3: Use an abstract base class with virtual functions (pseudo-COM)</A>
<LI><A HREF="#conclusion" >Conclusion</A>
<LI><A HREF="#downloads" >Downloads</A>
</UL>
<BR>
<H3>
<A NAME="summary">Summary of the three techniques</A>
</H3>
<P>
Ok, let the ugliness begin. Here are the three techniques.
</P>
<OL>
<LI>Create a DLL with MSVC that flattens the C++ classes into plain C functions. The plain C functions will be imported from BCB.
<LI>Create a COM object with MSVC that wraps the C++ classes via containment. BCB will be a COM client of the VC++ COM object.
<LI>Wrap the C++ classes using an abstract class with nothing but virtual functions in it. This is essentially COM without the ugly parts.
</OL>
<P>
Each technique is described in more detail below. In each example, we will assume that the MSVC DLL exports a class that looks
like this:
</P>
<pre>
<b>class</b> CFoo
<b>{</b>
<b>public</b><b>:</b>
CFoo<b>(</b><b>int</b> x<b>)</b><b>;</b>
<b>~</b>CFoo<b>(</b><b>)</b><b>;</b>
<b>int</b> DoSomething<b>(</b><b>int</b> y<b>)</b><b>;</b>
<b>}</b><b>;</b>
</pre>
<H3>
<A NAME="technique1">Technique 1: Flattening the C++ class into a C library</A>
</H3>
<P>
From the previous article on VC++ DLLs, we know that it is possible for a Borland project to call plain C functions that are
exported from an MSVC DLL. Using this information, we can create a DLL project in MSVC that exports plain C functions for use in
BCB. This MSVC wrapper DLL will be a client of the C++ DLL. The wrapper DLL will export plain C functions for creating <TT>CFoo</TT>
objects, for calling <TT>CFoo</TT> member functions, and for deleting the <TT>CFoo</TT> object.
</P>
<P>
The <TT>CFoo</TT> class contains three functions that we care about: the constructor, the destructor, and the all important
<TT>DoSomething</TT> function. We need to flatten each of these into an equivalent C function.
</P>
<pre>
<font color="navy">// original class</font>
<b>class</b> CFoo
<b>{</b>
<b>public</b><b>:</b>
CFoo<b>(</b><b>int</b> x<b>)</b><b>;</b>
<b>~</b>CFoo<b>(</b><b>)</b><b>;</b>
<b>int</b> DoSomething<b>(</b><b>int</b> y<b>)</b><b>;</b>
<b>}</b><b>;</b>
<font color="navy">// flattened C code</font>
<b>void</b><b>*</b> <b>__stdcall</b> new_CFoo<b>(</b><b>int</b> x<b>)</b>
<b>{</b>
<b>return</b> <b>new</b> CFoo<b>(</b>x<b>)</b><b>;</b>
<b>}</b>
<b>int</b> <b>__stdcall</b> CFoo_DoSomething<b>(</b><b>void</b><b>*</b> handle<b>,</b> <b>int</b> y<b>)</b>
<b>{</b>
CFoo <b>*</b>foo <b>=</b> <b>reinterpret_cast</b><CFoo <b>*</b><b>></b><b>(</b>handle<b>)</b><b>;</b>
<b>return</b> foo<b>-></b>DoSomething<b>(</b>y<b>)</b><b>;</b>
<b>}</b>
<b>void</b> <b>__stdcall</b> delete_CFoo<b>(</b><b>void</b> <b>*</b>handle<b>)</b>
<b>{</b>
CFoo <b>*</b>foo <b>=</b> <b>reinterpret_cast</b><CFoo <b>*</b><b>></b><b>(</b>handle<b>)</b><b>;</b>
<b>delete</b> foo<b>;</b>
<b>}</b>
</pre>
<P>
There are lots of important things to notice here. First, note that each C++ member function has been mapped to a plain C
function. Second, observe that we explicitly use the <TT>__stdcall</TT> calling convention for the C functions. From the previous DLL
article, we know that simply calling plain C functions in an MSVC DLL can be a real chore. If we put our past sufferings to use,
we can make this endeavor a little bit easier. The easiest way to for Borland to call a Microsoft DLL is if that DLL exports plain,
undecorated, C functions that use the <TT>__stdcall</TT> calling convention. Borland and Microsoft don't agree on <TT>__cdecl</TT>
functions. Normally, they don't agree on <TT>__stdcall</TT> functions either because MSVC decorates <TT>__stdcall</TT> functions,
but we can suppress that behavior by adding a <TT>DEF</TT> file to the MSVC project. See the examples from the download section for
an example of the <TT>DEF</TT> file.
</P>
<P>
Another thing to note about the code is that the <TT>new_CFoo</TT> function returns a pointer to the <TT>CFoo</TT> object. The BCB caller
must store this pointer in some location. This may seem contradictory to the gist of this article. After all, I thought that BCB couldn't
use the C++ objects from an MSVC DLL? If that's true, then why are we returning a pointer to a <TT>CFoo</TT> object?
</P>
<P>
The answer is that BCB cannot call the member functions of an class exported by an MSVC DLL. However, that doesn't mean that it can store
the address of such an object. That's what <TT>new_CFoo</TT> returns, a pointer to a <TT>CFoo</TT> object. The BCB client can store that
pointer, but there isn't much that it can do with it. It can't dereference it (and should not attempt to do so). To make this concept
easier to understand, <TT>new_CFoo</TT> returns a void pointer (it can't really return anything else anyway). On the BCB side, there is
not much you can safely do with this <TT>void</TT> pointer, other than storing it and passing it back into the DLL.
</P>
<P>
Ok, two more quick notes before we move on. First, notice that <TT>CFoo_DoSomething</TT> takes a void pointer argument as its first
parameter. This void pointer is the same void pointer that is returned by <TT>new_CFoo</TT>. The void pointer is cast back into a
<TT>CFoo</TT> object using <TT>reinterpret_cast</TT> (you know you're dealing ugly code when you see an <TT>reinterpret_cast</TT>).
The <TT>DoSomething</TT> member function is called after the cast. Lastly, note that the void pointer is also an argument to the
<TT>delete_CFoo</TT> function. It is crucial that the wrapper DLL delete the object. You should not call delete on the void pointer from
BCB. That will certainly not do what you want it to.
</P>
<P>
The listing below shows a DLL header file for the C functions. This header file can be shared between MSVC and BCB.
</P>
<pre>
<font color="navy">// DLL header file</font>
<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">#else</font>
<font color="green">#ifdef __cplusplus</font>
<b>extern</b> <font color="blue">"C"</font> <b>{</b>
<font color="green">#endif</font>
DLLAPI <b>void</b><b>*</b> <b>__stdcall</b> new_CFoo<b>(</b><b>int</b> x<b>)</b><b>;</b>
DLLAPI <b>int</b> <b>__stdcall</b> CFoo_DoSomething<b>(</b><b>void</b><b>*</b> handle<b>,</b> <b>int</b> y<b>)</b><b>;</b>
DLLAPI <b>void</b> <b>__stdcall</b> delete_CFoo<b>(</b><b>void</b> <b>*</b>handle<b>)</b><b>;</b>
<font color="green">#ifdef __cplusplus</font>
<b>}</b>
<font color="green">#endif</font>
<font color="green">#endif</font>
</pre>
<P>
This is a typical DLL header file. One interesting thing to notice is that you
don't see the word <TT>CFoo</TT> anywhere in the header file.
</P>
<P>
The next listing shows how to call the DLL from BCB
</P>
<pre>
<font color="green">#include "dll.h"</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>
<b>void</b> <b>*</b> foo <b>=</b> new_CFoo<b>(</b>x<b>)</b><b>;</b>
z <b>=</b> CFoo_DoSomething<b>(</b>foo<b>,</b> y<b>)</b><b>;</b>
delete_CFoo<b>(</b>foo<b>)</b><b>;</b>
<b>}</b>
</pre>
<P>
That's it. Pretty it isn't, but it does work. In fact, despite the grotesqueness of this technique, it is amazingly handy in other
situations that don't even involve DLLs. For instance, Delphi programmers employ this same technique because Delphi cannot call C++
member funtions. Delphi programmers must flatten the C++ class into C code, and then link with the C OBJ files. The
open source SWIG (<A HREF="http://www.swig.org">swig.org</A>) tool is designed to generate wrapper functions like this, which allows you
to call C++ objects from scripting languages such as python.
</P>
<H3>
<A NAME="technique2">Technique 2: Create a COM wrapper</A>
</H3>
<P>
Unfortunately, I don't have an example for this technique yet (hey, I said the article wasn't ready for prime time). But the idea works
like this. You create a COM object in MSVC. There is probably a wizard that you can run. Create an inprocess server (ie a DLL, not an
EXE). Also, make sure you create a COM object, and not an automation object. Automation just makes everything more difficult. Unless you
also need to use the C++ class from VB or an ASP page, use plain COM and not automation.
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?