全國最多中醫師線上諮詢網站-台灣中醫網
發文 回覆 瀏覽次數:3698
推到 Plurk!
推到 Facebook!

[轉貼]使用 Visual C++ DLLs in a C++Builder Project

 
axsoft
版主


發表:681
回覆:1056
積分:969
註冊:2002-03-13

發送簡訊給我
#1 引用回覆 回覆 發表時間:2002-06-13 09:16:10 IP:61.220.xxx.xxx 未訂閱
Using Visual C++ DLLs in a C++Builder Projec =============================================== 來源  :http://www.bcbdev.com/articles/vcdll.htm 作者  :Harold Howe. e-mail:hhowe@bcbdev.com    It is likely that one day your boss will ask you if you can create a GUI with C++Builder that interfaces to an existing 32 bit DLL compiled with Microsoft Visual C++. Often times, the original DLL source code won't be available to you, either because the DLL comes from a third party vendor, or because the 22 year old intern just deleted the \DLL\SRC directory from the network. Given a DLL and a header file, this article shows you how to call the DLL from your C++Builder project.     Calling DLL functions from a C++Builder project  The problem with Visual C++ DLLs  Step 1: Identify calling conventions used by the Visual C++ DLL  Step 2: Examine the linker names in the DLL  Step 3: Generate an import library for the Visual C++ DLL  Step 4: Add the import library to your project  Conclusion         Calling DLL functions from a C++Builder project  Calling a DLL that was created with Visual C++ presents C++Builder programmers with some unique challenges. Before we attempt to tackle DLLs generated by Visual C++, it may be beneficial to review how you call a DLL that was created with C++Builder. A DLL that was created with C++Builder presents fewer roadblocks than one that was made by Visual C++.     You need to gather three ingredients in order to call a DLL function from your C++Builder program: the DLL itself, a header file with function prototypes, and an import library (you could load the library at runtime instead of using an import library, but we will stick to the import library method for simplicity). To call a DLL function, add the import library to your C++Builder project by selecting the Project | Add To Project menu option in C++Builder. Next, insert a #include statement for the DLL header file in the C++ source file that needs to call one of the DLL functions. The last step is to add the code that calls the DLL function.     Listings A and B contain source code for a DLL that can serve as a test DLL. Notice that the test code implements two different calling conventions (__stdcall and __cdecl). This is for a very good reason. When you try to call a DLL that was compiled with Visual C++, most of your headaches will result from disagreements due to calling conventions. Also notice that one function does not explicitly list the calling convention that it uses. This unknown function will act as a measuring stick for DLL functions that don't list their calling convention.     //------------------------------------------ // Listing A: DLL.H    #ifdef __cplusplus extern "C" { #endif    #ifdef _BUILD_DLL_ #define FUNCTION __declspec(dllexport) #else #define FUNCTION __declspec(dllimport) #endif    FUNCTION int __stdcall   StdCallFunction(int Value); FUNCTION int __cdecl     CdeclFunction  (int Value); FUNCTION int             UnknownFunction(int Value);    #ifdef __cplusplus } #endif    //------------------------------------------ //Listing B: DLL.C    #define _BUILD_DLL_ #include "dll.h"    FUNCTION int __stdcall StdCallFunction(int Value) {     return Value + 1; }    FUNCTION int __cdecl   CdeclFunction(int Value) {     return Value + 2; }    FUNCTION int UnknownFunction(int Value) {     return Value; }    To create a test DLL from Listing A and Listing B, open up C++Builder and bring up the Object Repository by selecting the File | New menu option. Select the DLL icon and click the OK button. C++Builder responds by creating a new project with a single source file. That file will contain a DLL entry point function and some include statements. Now select File | New Unit. Save the new unit as DLL.CPP. Cut and paste the text from Listing A and insert it into the header file DLL.H. Then copy the code from Listing B and insert it into DLL.CPP. Make sure that the #define for _BUILD_DLL_ is placed above the include statement for DLL.H.     Save the project as BCBDLL.BPR. Next, compile the project and take a look at the files produced. C++Builder generates both a DLL and an import library with a .LIB extension.     At this point, you have the three ingredients needed to call a DLL from a C++Builder project: the DLL itself, a header file for function prototypes, and an import library to link with. Now we need a C++Builder project that will try to call the DLL functions. Create a new project in C++Builder and save it to your hard drive. Copy the DLL, the import library, and the DLL.H header file from DLL project to this new project. Select the Project | Add To Project menu option and add the LIB file to the project. Next, add a #include statement in the main unit that includes DLL.H. Finally, add code that calls the DLL functions. Listing C shows code that calls each the DLL functions from Listing A and B.     //------------------------------------------ // Listing C: MAINFORM.CPP - DLLTest program #include  #pragma hdrstop #include "MAINFORM.h" #include "dll.h" //--------------------------------------------------------- #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { int Value = StrToInt(Edit1->Text); int Result= StdCallFunction(Value); ResultLabel->Caption = IntToStr(Result); } //--------------------------------------------------------- void __fastcall TForm1::Button2Click(TObject *Sender) { int Value = StrToInt(Edit1->Text); int Result= CdeclFunction(Value); ResultLabel->Caption = IntToStr(Result); } //--------------------------------------------------------- void __fastcall TForm1::Button3Click(TObject *Sender) { int Value = StrToInt(Edit1->Text); int Result= UnknownFunction(Value); ResultLabel->Caption = IntToStr(Result); } The problem with Visual C DLLs In an ideal world, calling a DLL created with Visual C would be no more difficult than calling a DLL built with C Builder. Unfortunately, Borland and Microsoft disagree on several points. For starters, Borland and Microsoft disagree on file formats for OBJs and import library files (Visual C uses the COFF library format while Borland uses OMF). This means that you can't add a Microsoft generated import library to a C Builder project. Thanks to the Borland IMPLIB utility, the file format differences are surmountable. The two products also disagree on linker naming conventions. This turns out to be the primary hurdle when trying to call a Visual C DLL from C Builder. Every function in a DLL or OBJ has a linker name. The linker uses the linker name to resolve functions that were protoyped during compile time. The linker will generate an unresolved external error if it can't find a function with a linker name that it thinks is needed by the program. With regard to linker function names, Borland and Microsoft disagree on these points: 1- Visual C sometimes decorates exported __stdcall functions. 2- Borland C Builder expects imported __cdecl functions to be decorated. So why is this such a big deal? Take disagreement #1 regarding the __stdcall calling convention. If you create a DLL with Visual C that contains a __stdcall function called MyFunction(), Visual C will give the function a linker name that looks like _MyFunction@4. When the Borland linker tries to resolve calls made to this function, it expects to find a function with the name MyFunction. Since the import library for the Visual C DLL doesn't contain a function called MyFunction, the Borland linker reports an unresolved external, which means it couldn't find the function. Your attack strategy for overcoming these three problems will depend on how the Visual C DLL was compiled. I have broken the process into four steps. Step 1: Identify calling conventions used by the Visual C DLL In order to combat the naming convention entanglements, you must first determine what calling conventions are used by functions in the DLL. You can determine this by investigating the header file for the DLL. The function prototypes in the DLL header should look something like this: __declspec(dllimport) void CALLING_CONVENTION MyFunction(int nArg); CALLING_CONVENTION should be __stdcall or __cdecl (see Listing A for concrete examples). In many cases, the calling convention won't be specified, in which case it defaults to __cdecl. Step 2: Examine the linker names in the DLL If step 1 reveals that the DLL utilizes the __stdcall calling convention, you will need to examine the DLL to determine the naming convention that Visual C followed when it created the DLL. Visual C decorates __stdcall functions by default, but the DLL programmer can prohibit name decorations if they add a DEF file to their project. Your work will be slightly more tedious if the DLL supplier did not use a DEF file. The command line TDUMP utility allows you to examine the linker names of functions exported by the DLL. The following command invokes TDUMP on a DLL. TDUMP -ee MYDLL.DLL > MYDLL.LST TDUMP can report a ton of information about the DLL. We're only interested in functions exported by the DLL. The -ee command option instructs TDUMP to list only export information. If the DLL is large, you may want to redirect the output of TDUMP to a file (via the > MYDLL.LST appendage). The TDUMP output for the test DLL in Listing A and B looks like this: Turbo Dump Version 5.0.16.4 Copyright (c) 1988, 1998 Borland International Display of File DLL.DLL EXPORT ord:0000='CdeclFunction' EXPORT ord:0002='UnknownFunction' EXPORT ord:0001='_StdCallFunction@4' Notice the leading underscore and the trailing @4 on the __stdcall function. The __cdecl and the unknown function don't contain any decorations. If the Visual C DLL had been compiled with a DEF file, the decorations on the __stdcall function would not be present. Step 3: Generate an import library for the Visual C DLL Here comes the hard part. Due to the library file format differences between C Builder and Visual C , you cannot add an import library created with Visual C to your C Builder project. You must create an OMF import library using the command line tools that come with C Builder. Depending out what you found in the first two steps, this step will either go smoothly, or it could take some time. As stated earlier, C Builder and Visual C don't agree on how functions should be named in a DLL. Due to naming convention differences, you will need to create an aliased import library if the DLL implements calling conventions where C Builder and Visual C disagree. Table A lists the areas of disagreement. Table A: Visual C and C Builder naming conventions Calling convention VC name VC (DEF used) C Builder Name ----------------------------------------------------------------------- __stdcall _MyFunction@4 MyFunction MyFunction __cdecl MyFunction MyFunction _MyFunction The C Builder column lists function names that the Borland linker expects to see. The first Visual C column lists the linker names that Visual C generates when the Visual C project does not utilize a DEF file. The second Visual C column contains linker names that Visual C creates when a DEF file is used. For things to go smoothly, the C Builder name should agree with the Visual C name. Notice that the two products agree in only one place: __stdcall functions where the Visual C project contained a DEF file. For the remaining scenarios, you will need to create an import library that aliases the Visual C name to a C Builder compatible name. Table A shows that there are several combinations that you may need to deal with when creating the import library. I have separated the combinations into two cases. Case 1: The DLL contains only __stdcall functions and the DLL vendor utilized a DEF file. Table A reveals that VC and C Builder agree only when the DLL uses __stdcall functions. Furthermore, the DLL must be compiled with a DEF file to prevent VC from decorating the linker names. The header file will tell you if the __stdcall calling convention was used (Step 1), and TDUMP will reveal whether or not the functions are decorated (Step 2). If the DLL contains __stdcall functions that are not decorated, then Visual C and C Builder agree on how the functions should be named. You can create an import library by running IMPLIB on the DLL. No aliases are needed. IMPLIB works like this: IMPLIB (destination lib name) (source dll) For example, IMPLIB mydll.lib mydll.dll Create the import library and move on to step 4. Case 2: The DLL contains __cdecl functions or decorated __stdcall functions. If your DLL vendor is adamant about creating DLLs that are compiler independent, then you have a good chance of falling into the Case 1 category. Unfortunately, odds are you won't fall into the Case 1 group for several reasons. For one, the calling convention defaults to __cdecl if the DLL vendor omits a calling convention when prototyping the functions, and __cdecl forces you into Case 2. Secondly, even if your vendor has utilized the __stdcall calling convention, they probably neglected to utilize a DEF file to strip the Visual C decorartions. However you got here, Good Day, and welcome to Case 2. You're stuck with a DLL whose function names don't agree with C Builder. Your only way out of this mess is to create an import library that aliases the Visual C function names into a format compatible with C Builder. Fortunately, the C Builder command line tools allow you to create an aliased import library. The first step is to create a DEF file from the Visual C DLL by using the IMPDEF program that comes with C Builder. IMPDEF creates a DEF file that lists all of the functions exported by the DLL. You invoke IMPDEF like this: IMPDEF (Destination DEF file) (source DLL file). For example IMPDEF mydll.def mydll.dll After running IMPDEF, open the resulting DEF file using the editor of your choice. When the DLL source in Listing A and B is compiled with Visual C , the DEF file created by IMPDEF looks like this: LIBRARY DLL.DLL EXPORTS CdeclFunction @1 UnknownFunction @3 _StdCallFunction@4 =_StdCallFunction @2 The next step is to alter the DEF file so it aliases the DLL functions into names that C Builder will like. You can alias a function by listing a C Builder compatible name followed by the original Visual C linker name. For the test DLL in Listing A and B, the aliased DEF looks like this: EXPORTS ; use this type of aliasing ; (Borland name) = (Name exported by Visual C ) _CdeclFunction = CdeclFunction _UnknownFunction = UnknownFunction StdCallFunction = _StdCallFunction@4 Notice that the function names on the left match the Borland compatible names from Table A. The function names on the right are the actual linker names of the functions in the Visual C DLL. The final step is to create an aliased import library from the aliased DEF file. Once again, you rely on the IMPLIB utility, except that this time, you pass IMPLIB the aliased DEF file as its source file instead of the original DLL. The format is IMPLIB (dest lib file) (source def file) For example IMPLIB mydll.lib mydll.def Create the import library and move on to step 4. You may want to examine the import library first to ensure that each DLL function appears in a naming format that C Builder agrees with. You can use the TLIB utility to inspect the import library. TLIB mydll.lib, mydll.lst The list file for the test DLL looks like this: Publics by module StdCallFunction size = 0 StdCallFunction _CdeclFunction size = 0 _CdeclFunction _UnknownFunction size = 0 _UnknownFunction Step 4: Add the import library to your project Once you create an import library for the Visual C DLL, you can add the import library to your C Builder project using the Project | Add to Project menu option. You use the import library without regard to whether the import library contains aliases or not. After adding the import library to your project, build your project and see if you can successfully link. Conclusion: This article demonstrated how you can call functions in a Visual C DLL from a C Builder project. The techniques work with C Builder 1 and C Builder 3, and DLLs built with Visual C 4.X or Visual C 5 (I haven't tested Visual C 6 yet). You may have noticed that this article only discusses how to call C style functions in a DLL. No attempt is made to call methods of an object where the code for the class resides in a Visual C DLL. C DLLs present an even greater array of problems because linker names for member functions are mangled. The compiler employs a name mangling scheme in order to support function overloading. Unfortunately, the C standard does not specify how a compiler should mangle class methods. Without a strict standard in place, Borland and Microsoft have each developed their own techniques for name mangling, and the two conventions are not compatible. In theory, you could use the same aliasing technique to call member functions of a class that resides in a DLL. However, you may want to consider creating a COM object instead. COM introduces many of its own problems, but it does enforce a standard way of calling methods of an object. A COM object created by Visual C can be called from any development environment, including both Delphi and C Builder. C Builder 3.0 introduced a new command line utility called COFFtoOMF.EXE. This utility can convert a Visual C import library to a C Builder import library. Furthermore, the program will automatically alias __cdecl functions from the Visual C format to the C Builder format. The automatic aliasing can simplify Step 3 if the DLL exclusively uses the __cdecl calling convention. -------------------------------------------------------------------------------- Copyright © 1997-2000 by Harold Howe. All rights reserved.
axsoft
版主


發表:681
回覆:1056
積分:969
註冊:2002-03-13

發送簡訊給我
#2 引用回覆 回覆 發表時間:2002-07-16 08:41:30 IP:61.218.xxx.xxx 未訂閱

Using Visual C DLLs in a C Builder Project

Part 2: C classes. 資料來源:http://www.bcbdev.com/articles/vcdll2.htm Note: 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. In a previous article on how to use VC DLLs from MSVC, 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 __cdecl 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 TDUMP, IMPLIB, IMPDEF and COFF2OMF. 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. Unfortunately, this strategy doesn't completely get you out of the woods. At the end of the DLL article, 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! 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. Bad news: 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. * Summary of the three techniques * Technique 1: Flattening the C class into a C library * Technique 2: Create a COM wrapper * Technique 3: Use an abstract base class with virtual functions (pseudo-COM) * Conclusion * Downloads Summary of the three techniques Ok, let the ugliness begin. Here are the three techniques. 1. Create a DLL with MSVC that flattens the C classes into plain C functions. The plain C functions will be imported from BCB. 2. Create a COM object with MSVC that wraps the C classes via containment. BCB will be a COM client of the VC COM object. 3. Wrap the C classes using an abstract class with nothing but virtual functions in it. This is essentially COM without the ugly parts. 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: class CFoo { public: CFoo(int x); ~CFoo(); int DoSomething(int y); }; Technique 1: Flattening the C class into a C library 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 CFoo objects, for calling CFoo member functions, and for deleting the CFoo object. The CFoo class contains three functions that we care about: the constructor, the destructor, and the all important DoSomething function. We need to flatten each of these into an equivalent C function. // original class class CFoo { public: CFoo(int x); ~CFoo(); int DoSomething(int y); }; // flattened C code void* __stdcall new_CFoo(int x) { return new CFoo(x); } int __stdcall CFoo_DoSomething(void* handle, int y) { CFoo *foo = reinterpret_cast(handle); return foo->DoSomething(y); } void __stdcall delete_CFoo(void *handle) { CFoo *foo = reinterpret_cast(handle); delete foo; } 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 __stdcall 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 __stdcall calling convention. Borland and Microsoft don't agree on __cdecl functions. Normally, they don't agree on __stdcall functions either because MSVC decorates __stdcall functions, but we can suppress that behavior by adding a DEF file to the MSVC project. See the examples from the download section for an example of the DEF file. Another thing to note about the code is that the new_CFoo function returns a pointer to the CFoo 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 CFoo object? 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 new_CFoo returns, a pointer to a CFoo 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, new_CFoo 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 void pointer, other than storing it and passing it back into the DLL. Ok, two more quick notes before we move on. First, notice that CFoo_DoSomething takes a void pointer argument as its first parameter. This void pointer is the same void pointer that is returned by new_CFoo. The void pointer is cast back into a CFoo object using reinterpret_cast (you know you're dealing ugly code when you see a reinterpret_cast). The DoSomething member function is called after the cast. Lastly, note that the void pointer is also an argument to the delete_CFoo 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. The listing below shows a DLL header file for the C functions. This header file can be shared between MSVC and BCB. // DLL header file #ifndef DLL_H #define DLL_H #ifdef BUILD_DLL #define DLLAPI __declspec(dllexport) #else #define DLLAPI __declspec(dllimport) #else #ifdef __cplusplus extern "C" { #endif DLLAPI void* __stdcall new_CFoo(int x); DLLAPI int __stdcall CFoo_DoSomething(void* handle, int y); DLLAPI void __stdcall delete_CFoo(void *handle); #ifdef __cplusplus } #endif #endif This is a typical DLL header file. One interesting thing to notice is that you don't see the CFoo class anywhere in the header file. The header file contaisn only plain C functions that will wrap CFoo. The next listing shows how to call the DLL from BCB #include "dll.h" void bar() { int x = 10; int y = 20; int z; void * foo = new_CFoo(x); z = CFoo_DoSomething(foo, y); delete_CFoo(foo); } 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 (swig.org) tool is designed to generate wrapper functions like this, which allows you to call C objects from scripting languages such as Python. Technique 2: Create a COM wrapper 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. 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 CFoo, a good interface name would be IFoo. MSVC will also want you to name the implementation class for the COM obect. CFooImpl is a good candidate. The COM object should wrap the C DLL class using aggregation. In other words, the COM object contains a CFoo member variable. Don't try to inherit your COM class from CFoo. For each member function of the C DLL class (CFoo), 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 BSTR'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. 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. Once again, I apologize for not having a working demo of this technique ready to go. Technique 3: Use an abstract base class with virtual functions (pseudo-COM) 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. 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. COM works because BCB and MSVC employ exactly the same virtual dispatching system. COM relies on the fact that most Win32 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. Here is the DLL header file for the pseudo-COM wrapper class. It consists of an abstract base class, IFoo, that serves as the pseudo-COM interface. It also consists of two C functions for creating and deleting IFoo objects. This header file is shared between MSVC and BCB. #ifndef DLL_H #define DLL_H #ifdef BUILD_DLL #define DLLAPI __declspec(dllexport) #else #define DLLAPI __declspec(dllimport) #endif // psuedo COM interface class IFoo { public: virtual int __stdcall DoSomething(int x) = 0; virtual __stdcall ~IFoo() = 0; }; #ifdef __cplusplus extern "C" { #endif DLLAPI IFoo* __stdcall new_IFoo(int x); DLLAPI void __stdcall delete_IFoo(IFoo *f); #ifdef __cplusplus } #endif #endif Notice that the two C functions resemble the functions from Technique 1, except that now they work with IFoo pointers instead of unsafe void pointers. This technique provides a little more type safety than the first. Here is the source code for the MSVC wrapper. It contains a class call CFooImpl that inherits from IFoo. CFooImpl is an implementation of the IFoo interface. #define BUILD_DLL #include "dll.h" IFoo::~IFoo() { // must implement base class destructor // even if its abstract } // Note: we declare the class here because no one outside needs to be concerned // with it. class CFooImpl : public IFoo { private: CFoo m_Foo; // the real C class from the existing MSVC C DLL public: CFooImpl(int x); virtual ~CFooImpl(); virtual int __stdcall DoSomething(int x); }; CFooImpl::CFooImpl(int x) : m_Foo(x) { } int __stdcall CFooImpl::DoSomething(int x) { return m_Foo.DoSomething(x); } CFooImpl::~CFooImpl() { } IFoo * __stdcall new_IFoo(int x) { return new CFooImpl(x); } void __stdcall delete_IFoo(IFoo *f) { delete f; } 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 seems like it ought to be a good thing. More important than that, notice that the BCB project will only interact with the IFoo class. The actual implementation of IFoo is provided by a derived class call CFooImpl, which is internal to the MSVC wrapper project. The BCB client code will work with IFoo objects polymorphically. To obtain a wrapper instance, the BCB code will call the new_IFoo function. new_IFoo works like a factory function, serving up new IFoo instances. new_Foo returns a pointer to an IFoo instance. However, that pointer is polymorphic. The static type of the pointer is IFoo, but its actual dynamic type will be a pointer to a CFooImpl (a fact that is unbeknownst to the BCB code). Here is the code for the BCB client. #include "dll.h" void bar() { int x = 10; int y = 20; int z; IFoo *foo = new_IFoo(x); z = foo->DoSomething(y); delete_IFoo(foo); } Now some parting comments on technique 3. First, it is crucial that you delete the IFoo pointer from the MSVC DLL. This is done by passing the IFoo pointer to the delete_IFoo function. Don't attempt to delete the object from BCB. void bar() { IFoo *foo = new_IFoo(x); delete foo; // BOOM!!! } This code will surely die an agonizing death. The problem is that IFoo was created from within the new_IFoo function in the MSVC wrapper DLL. As such, the memory for the IFoo 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. Another item of note is that the BCB code works entirely in terms of the IFoo abstract interface. You don't see any occurrence of the class CFooImpl on the BCB side. CFooImpl is internal to the MSVC wrapper project. When you call DoSomething from the BCB side, the call is dispatched to CFooImpl via the virtual table. 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. Tip Note: If you employ this pseudo-com technique, make sure that you do not attempt to overload virtual functions. In other words, don't create an interface that looks like this: class IFoo { public: virtual int __stdcall DoSomething(int x) = 0; virtual int __stdcall DoSomething(float x) = 0; virtual int __stdcall DoSomething(const char *x) = 0; }; The reason you don't want to overload the virtual interface functions is that MSVC and BCB may not (and probably won't) order the functions the same way in the vtable. When I tested overloading, calling DoSomething(int) on the BCB side seemed to get dispacted to DoSoemthing(float) on the MSVC side. Borland and Microsoft appear to agree on vtable formats as long as you don't overload. This may explain why you don't seem COM objects with overloaded functions. If you need to wrap a C class with overloaded functions, then you should create a distinct function name for each one. class IFoo { public: virtual int __stdcall DoSomething_int (int x) = 0; virtual int __stdcall DoSomething_float(float x) = 0; virtual int __stdcall DoSomething_str (const char *x) = 0; }; Conclusion: 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. 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. Downloads Downloads for this article http://www.bcbdev.com/ftp/source/cppdll.zip VC 5 DLL project with exported C CFoo class http://www.bcbdev.com/ftp/source/vcdll2tech1.zip Code for technique #1, flattening a class into C functions http://www.bcbdev.com/ftp/source/vcdll2tech3.zip Code for technique #3, virtual function/abstract base class wrapper opyright ?1997-2002 by Harold Howe. All rights reserved 時間就是金錢 << 發問前請先找找舊文章 >> 發表人 - axsoft 於 2002/07/16 08:44:14
系統時間:2024-04-20 23:57:01
聯絡我們 | Delphi K.Top討論版
本站聲明
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。
2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。
3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇!