MVP Logo

Материализуя идеи

Не поднимешься в горы — не узнаешь высоты неба; не спустишься в бездну — не узнаешь толщи земли.

What would old tethered programmer want from its main working tool? Obviously, it must, no, MUST work as expected, and provide an expected, I emphasize, EXPECTED result(s). Recently, we tried to compile our middle-sized application project group for MAC OS. All components were separately ported beforehand, to be at least compileable under toolchains other than Win32|Win64. Needless to say, that after a few tries to compile entire project group, and getting a load of ‘unresolved externals’, I started to suspect that something is wrong there :) Test project for the article.

To be discreet, the group consists of a few packages, with package inter-dependency, like, core->comm->coregui->commgui, and an executable, which depends on packages. Putting aside question what package is, and how it’s different from dll, I must prepend the rest of article with rather verbose description of how packaging works in Emb., and why, in my knowing, using packages in Emb. projects is preferable.

Emb. package is actually two projects in one, namely, it prepares binary outputs for both, dynamic and static linkage. If one builds package in C++Builder, it is obvious, that each package build takes two iterations, one results in static, another in dynamic binary output. The static one produces *.lib (or *.a, or whatever is used in specific platform toolchain), dynamic provides *.bpi + *.bpl (or .dylib, or whatever is platform-specific), and that’s that.

The C++ package developer specifies shared stuff, it implements, to be useable externally, by providing some data|class|service declaration modifiers. Linker, in case of dynamic linkage, uses the latter, to create a table of exported symbols, which, in turn, is used, when it’s resolving external names in package inter-dependencies.

In static linkage case, linker marks these symbols as ‘weakly linked’, which postpones name resolution until final executable is being linked. To mark one package dependent on another one, it’s enough to add dependent one .bpi file to ‘Requires’ section, the rest is done by the linker, if dynamic, it resolves dependencies by using required *.bpi files, otherwise, ‘weak symbols’ resolution is postponed until final executable linking stage.

What is expected, when executable is depending on packages, and ‘link with runtime packages’ flag is set? Well, imported symbols got resolved using *.bpi files, corresponding *.bpl (or *.dylib) are marked as required by main executable, so dynamic library is loaded, and its symbols are bound implicitly, and, what is more important, automatically, at executable startup. Otherwise, in static case, all stuff, that is used, is compiled in main executable, and that’s that.

Now, Delphi developers are lucky guys here. To make services|data|classes exportable|externally usable, all they need, is to put these declaration in interface part of the package unit, add the package to the runtime packages list of an executable project, and add unit names to the uses list, where appropriate. The rest is done automatically for them, no matter the platform is, Win32|Win64|OSX, the dynamic linkage works seamlessly, automatically, and in the same way for all supported platforms, as expected.

Well, life with C++Builder is quite different. A lot more, hmm, interesting, so to say.

First, to make symbol exportable, you have to mark it by, say, EXPORTSPEC, in declaration. What is more, the same declaration is used in dependent binary, to make use of exported symbol. But, that time, the same declaration must mark symbol as, say, IMPORTSPEC, to let linker know the difference.

C++Builder code may use PACKAGE macro in both cases, that, according to docs, marks symbol as ‘residing in package’, and linker will do the rest. I prefer to use platform - specific EXPORTSPEC and IMPORTSPEC for symbols, which are not Embarcadero-dependent, i.e., for native C++ classes, services, and data. Otherwise, if class is derived from any Delphi class, the PACKAGE is used, that’s without alternative.

To augment compiler to use appropriate XXXSPEC, the specific define is used for each package, like PKGEXPORTS, for package PKG, then, in PKG – header file, the following is defined:

#ifdef PKGEXPORTS
# define PKG_CLASS     EXPORTSPEC()
# define PKG_FUNC(ret) EXPORTSPEC(ret) 
#else 
# define PKG_CLASS     IMPORTSPEC() 
# define PKG_FUNC(ret) IMPORSPEC(ret) 
#endif

It allows us to declare, for instance, exported PKG stuff as:

class PKG_CLASS PkgClass {…};

or

class PKG_CLASS PkgClass {…};

Next step is to add interdependency to the packages. Well, C++Builder uses the same ‘Requires’ section for that, as in Delphi projects.

Final executable project does not have ‘Requires’ section though. So, to specify that it needs to use external symbols from, say PKG, some manual operations on the cbproj file should be done.

First, you should add PKG to the runtime project list (that may be added in IDE though), that’s what is placed in <PackageImports> </PackageImports> element. Second, you have to open cbproj in text editor, and find <AllPackageLibs></AllPackageLibs> element. It would contain all packages, with lib extensions, which are used in executable compilation. While most of that list is filled-in implicitly by the IDE, when resolving dependencies upon component addition to the form, etc, the rtl.lib (System runtime) and fmx.lib (I use Firemonkey mostly) are there from the start. The list from that element is used to auto link dependencies in either static or dynamic case, in latter, these names, but with .bpi extensions will be used, to resolve symbols, and prepare implicit load-time dynamic libraries binding. So, in addition to auto-filled-in items, your runtime package names should be added there manually, with .lib extensions.

Now, back to the IMPORTSPEC and EXPORTSPEC defines. As I wrote before, under Embarcadero tools, the PACKAGE specifier may be used for both. So, we should define EXPORTSPEC(x) as PACKAGE x. The same is OK for IMPORTSPEC(x). That way, interdependent packages link fine, both in static and dynamic modes, but for executable.

Executable static linkage is working fine, but dynamic one results in unresolved externals, which are all symbols from packages, which I tried to use in executable code.

Following are the xlink command lines for the same executable in dynamic and static linkage modes. They differ in last parts only, which are shown for comparison:

Dynamic linkage:

-C -c -v -Gn –GA "D:\Temp\vfsEA6F.tmp"="D:\Users\Vsevolod\Documents\Embarcadero\Studio\Projects\Package_Test\frmMain.fmx
-V5.0 -Tme start_fmx.o rtl.bpi fmx.bpi pkg.bpi pkg_using_pkg.bpi delphi.o .\OSX32\Debug\frmMain.o .\OSX32\Debug\pkgTest.o ,
 .\OSX32\Debug\pkgTest ,
 .\OSX32\Debug\pkgTest.map ,
 libcgcrtl.dylib libcgcrtl_nonshared.a libcgstl.dylib libcgunwind.1.0.dylib
 libSystem.B.dylib libiconv.dylib libmathcommon.A.dylib libobjc.A.dylib 
libpcre.dylib libz.dylib , , pkgTest.res

Static linkage:

-C -c -v -Gn -GA "D:\Temp\vfsB7C6.tmp"="D:\Users\Vsevolod\Documents\Embarcadero\Studio\Projects\Package_Test\frmMain.fmx"
 -V5.0 -Tme start_fmx.o delphi.o .\OSX32\Debug\frmMain.o .\OSX32\Debug\pkgTest.o ,
 .\OSX32\Debug\pkgTest ,
 .\OSX32\Debug\pkgTest.map ,
 rtl.a fmx.a pkg.a pkg_using_pkg.a libcgcrtl.dylib
 libcgcrtl_nonshared.a libcgstl.dylib libcgunwind.1.0.dylib libSystem.B.dylib libiconv.dylib
 libmathcommon.A.dylib libobjc.A.dylib libpcre.dylib libz.dylib , , pkgTest.res

Apparently, static linkage uses .a files, and dynamic one uses .bpi for symbols resolution. Obviously, bpis do not help, so xlink still is unable to resolve external names for dynamic C++ packages under OSX platform, unfortunately.

As soon as our packages are, in terms of MAC OS, standalone dynamic libraries, the last thing I tried, was to explicitly specify dylib-s to the xlink tool. There were two ways to do that, both seems to work.

First one – use:

#pragma comment(lib, “bplpkg.dylib”) 
#pragma comment(lib, “bplpkg_using_pkg.dylib”)

in one of the main executable cpp files. Other option is to explicitly add dylib files to the main project. One note for the first alternative – as soon as we do not want to specify these lines, when building statically, we may want to wrap these pragmas in #ifdef USEPACKAGES … #endif, USEPACKAGES symbol is automatically defined by development environment, when dynamic package linkage is used in project. That way, name resolution finally works in dynamic linkage under OSX as well.

The last thing is running a field test. I’ve added C++ test project for this article, it demonstrates package interdependency, dynamic and static linkage for a final executable, as well as a few use cases.

Function exported from pkg, another function, exported from pkg_using_pkg, which internally uses function from pkg. Another use-case is: C++ classes, base defined in pkg, derived defined in pkg_using_pkg, and derived one is created in main executable, both, on stack and on heap, its virtual method is called once. The last use – case is C++ class, derived from Delphi TText, created dynamically on main executable’s form. That’s where I gave up, really. Somehow, when dynamic linkage is used, its creation gives rise to an AV. As opposed to that, statically linked executable runs just fine.

Яндекс.Метрика

Сейчас 47 гостей и ни одного зарегистрированного пользователя на сайте

14.12.2019  ©2019 - ExactSoft - All rights reserved