How do I solve an unresolved external when using C

2020-06-03 08:55发布

问题:

I'm experimenting with reconfiguring my application to make heaving use of packages. Both I and another developer running a similar experiment are running into a bit of trouble when linking using several different packages. We're probably both doing something wrong, but goodness knows what :)

The situation is this:

  • The first package, PackageA.bpl, contains C++ class FooA. The class is declared with the PACKAGE directive.
  • The second package, PackageB.bpl, contains a class inheriting from FooA, called FooB. It includes FooB.h, and the package is built using runtime packages, and links to PackageA by adding a reference to PackageA.bpi.

  • When building PackageB, it compiles fine but linking fails with a number of unresolved externals, the first few of which are:

    • [ILINK32 Error] Error: Unresolved external '__tpdsc__ FooA' referenced from C:\blah\FooB.OBJ
    • [ILINK32 Error] Error: Unresolved external 'FooA::' referenced from C:\blah\FooB.OBJ
    • [ILINK32 Error] Error: Unresolved external '__fastcall FooA::~FooA()' referenced from blah\FooB.OBJ

    etc.

Running TDump on PackageA.bpl shows:

Exports from PackageA.bpl
  14 exported name(s), 14 export addresse(s).  Ordinal base is 1.
  Sorted by Name:
    RVA      Ord. Hint Name
    -------- ---- ---- ----
    00002A0C    8 0000 __tpdsc__ FooA
    00002AD8   10 0001 __linkproc__ FooA::Finalize
    00002AC8    9 0002 __linkproc__ FooA::Initialize
    00002E4C   12 0003 __linkproc__ PackageA::Finalize
    00002E3C   11 0004 __linkproc__ PackageA::Initialize
    00006510   14 0007 FooA::
    00002860    5 0008 FooA::FooA(FooA&)
    000027E4    4 0009 FooA::FooA()
    00002770    3 000A __fastcall FooA::~FooA()
    000028DC    6 000B __fastcall FooA::Method1() const
    000028F4    7 000C __fastcall FooA::Method2() const
    00001375    2 000D Finalize
    00001368    1 000E Initialize
    0000610C   13 000F ___CPPdebugHook

So the class definitely seems to be exported and available to link. I can see entries for the specific things ILink32 says it's looking for and not finding. Running TDump on the BPI file shows similar entries.

Other info

The class does descend from TObject, though originally before refactoring into packages it was a normal C++ class. (More detail below. It seems "safer" using VCL-style classes when trying to solve problems with a very Delphi-ish thing like this anyway. Changing this only changes the order of unresolved externals to first not find Method1 and Method2, then others.)

Declaration for FooA:

class PACKAGE FooA: public TObject {
public:
   FooA();
   virtual __fastcall ~FooA();
   FooA(const FooA&);
   virtual __fastcall long Method1() const;
   virtual __fastcall long Method2() const;
};

and FooB:

class FooB: public FooA {
public:
   FooB();
   virtual __fastcall ~FooB();
   ... other methods...
};

All methods definitely are implemented in the .cpp files, so it's not not finding them because they don't exist! The .cpp files also contain #pragma package(smart_init) near the top, under the includes.

Questions that might help...

  • Are packages reliable using C++, or are they only useable with Delphi code?
  • Is linking to the first package by adding a reference to its BPI correct - is that how you're supposed to do it? I could use a LIB but it seems to make the second package much larger, and I suspect it's statically linking in the contents of the first.
  • Can we use the PACKAGE directive only on TObject-derived classes? There is no compiler warning using it on standard C++ classes.
  • Is splitting code into packages the best way to achieve the goal of isolating code and communicating through defined layers / interfaces? I've been investigating this path because it seems to be the C++Builder / Delphi Way, and if it worked it looks attractive. But are there better alternatives?
  • I'm very new to using packages and have only known about them through using components before. Any general words of advice would be great!

We're using C++Builder 2010. I've fabricated the class and method names in the above code examples, but other than that the details are exactly what we're seeing.

回答1:

Unresolved external

The unresolved external in your case, seems to be because the compiler is unable to find the path to the package data. You should find out if:

  • The path exists in the compiler search path list.
  • The package exists in the default package directory.

If one of them is true then the path isn't the problem. However as Riho also mentions this is the most likely cause for the problem. The Embarcadero documentation wiki states the following about the unresolved external error:

The named symbol is referenced in the given module but is not defined anywhere in the set of object files and libraries included in the link. Check to make sure the symbol is spelled correctly.

You will usually see this error from the linker for C or C++ symbols if any of the following occur:

  • You did not properly match a symbol’s declarations of __pascal and __cdecl types in different source files.
  • You have omitted the name of an object file your program needs. You need to manually add all required packages to the Requires list.
  • You did not link in the emulation library.

If you are linking C++ code with C modules, you might have forgotten to wrap C external declarations in extern “C”.

You could also have a case mismatch between two symbols.

Source: Unresolved external 'symbol' referenced from 'module'.

Since it seems from the - although altered class names - it is not the case of a misspelling. You also state that you have added the package to the requires list so we rule out this as well. Since you are not linking to C modules we can omit that part as well. So it points to problems with the directory.

About the other questions

Your questions are all really interesting and many of the questions are questions I my self has been looking for answers for when I started developing packages and components for C++ Builder.

Are packages reliable using C++?

Packages are a fine solution to use for C++ Builder, Both C++ Builder is built to support packages and the Pascal written VCL framework. This means that certain implementations are different in C++ Builder than other compilers. This is a necessity to keep the language compatible with its Delphi sibling. For this reason you can use packages in C++ Builder almost as easily as if using Delphi.

Is linking to the first package by adding a reference to its BPI correct?

To start with the second part of your question here, using a lib file makes your package larger simply because it is using static linking - so your guess is correct. Now back to the first part of the question, linking to a package is fine by adding a reference to its BPI. But you do need to make sure that the path variable has been set correctly as Riho suggests in his answer.

Personally I always make sure to my packages to the proper directories in your users folder, the location of this is depending on your Delphi version and operating system version. As far as I recall it is under the Document and Settings\all users\shared documents\Rad studio(version number)\Packages but I could be mistaken about that.

Can we use the PACKAGE directive only on TObject-derived classes?

The PACKAGE macro is resolved into __declspec(package), you can compare it to __declspec(dllexport). The difference between these is that package is used when declared in a package, and dllexport is used when declared in a DLL. There is a topic about this on the official embarcadero forums titled __declspec(package) vs __declspec(dllexport). The author of the original post, also asks your exact question about this, but unfortunately that part of the question is unanswered.

I have a theory however, and I must emphasize that it is nothing more than a theory. Remy Lebeau writes as a response to the question in the forum post:

__declspec(dllexport) can be used for plain functions, data variables, and non-VCL classes, and can be used in plain DLLs. __declspec(package) is used for VCL components, and can only be used with packages.

So from reading his response it seems to me that package is simply exporting the class, just like dllexport does. And since dllexport as far as I can read from his response is to be used in plain DLLs only you have to use the package for exporting (even) non VCL classes from a package.

What is interesting about all this, is that a package is essentially a DLL as far as I recall, but I must admit I can't find or remember the source of that information so take with a grain of salt.

Is splitting code into packages the best way to achieve the goal of isolating code?

Packages have some very prominent strengths when creating reusable components for the VCL. Obviously using packages limits the user to use either C++Builder or Delphi, but for components written to take advantage of the VCL framework it's an excellent choice. Properly written packages can ease the reusability of components, and I believe it is the preferred method of distributing components for the VCL.

However if your code does not take advantage of the VCL framework in any way, I would consider using an ordinary library, either static or dynamic, simply to create a more cross compiler friendly approach.

Whether there are any better approach to isolating your code, really depends on the project you are working on. I like to keep code that communicates through use of VCL classes in packages, but code that does not require use of any VCL classes in regular libraries. Keep in mind though that you can easily use VCL classes in a DLL but you need to handle special cases if you choose to export functions with VCL String classes as parameters or return values.

Any general words of advice?

I'm not the most experienced developer of packages myself, but I have found that disabling runtime linking often solves a lot of my problems, while it is somewhat trivial to fix any problems for your own code, you can often run into 3rd party components that have trouble dealing with this. Having said that, I'm not a fan of distributing my packages along with my application as is required in this case. But to be honest that is a matter of taste.

Personally I found it difficult to find some proper answers to many of my questions, when I started creating components and packages. The official helpfile is not the most informative on the matter, but looking through the VCL source code, will often give you the best answer to your question. Besides there is a few other websites that can provide help, many of the sites are targeting Delphi though, but this you have to get used to though.

Delphi Wikia has some good articles about creating components, in particular Creating Components and Creating Packages There is also BCB Journal which is one of the few C++ Builder specific sites, it has some fine articles and an acceptable forum. The Delphi pages at About.com is also a good source of information, I've found lots of good hints and nice to knows there, in particular: Creating Custom Delphi Components - Inside and Out.



回答2:

Maybe stupid question, but are your BPI/BPL files in correct path to be found by linker? I created once an app in BCB5 that used several linked packages, but don't remember if there was anything special in making them.



回答3:

For me #pragma package(smart_init,weak) in the cpp file solved the problem. See also http://flylib.com/books/en/3.264.1.27/1/ The cpp->obj file gets staticly linked without influencing anything else.