Символ не найден при использовании шаблона, определенного в библиотеке

Я пытаюсь использовать библиотеку Adobe XMP в приложении для iOS, но получаю ошибки ссылок. У меня есть соответствующие заголовки и библиотеки на моем пути, но я получаю ошибки ссылки. Я дважды проверил, чтобы убедиться, что заголовки и библиотека находятся на моем пути. Я проверил искаженные имена методов, но их нет в библиотеке (я проверил, используя nm команда). Что я делаю неправильно?

Заголовок библиотеки:

#if defined ( TXMP_STRING_TYPE )

#include "TXMPMeta.hpp"#include "TXMPIterator.hpp"#include "TXMPUtils.hpp"typedef class TXMPMeta <TXMP_STRING_TYPE>     SXMPMeta;       // For client convenience.
typedef class TXMPIterator <TXMP_STRING_TYPE> SXMPIterator;
typedef class TXMPUtils <TXMP_STRING_TYPE>    SXMPUtils;

.мм файл:

#include <string>
using namespace std;
#define IOS_ENV
#define TXMP_STRING_TYPE string
#import "XMP.hpp"
void DoStuff()
{
SXMPMeta meta;
string returnValue;
meta.SetProperty ( kXMP_NS_PDF, "test", "{ formId: {guid} }" );
meta.DumpObject(DumpToString, &returnValue);
}

Ошибки ссылки:

(null): "TXMPMeta<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >::DumpObject(int (*)(void*, char const*, unsigned int), void*) const", referenced from:
(null): "TXMPMeta<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >::TXMPMeta()", referenced from:
(null): "TXMPMeta<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >::SetProperty(char const*, char const*, char const*, unsigned int)", referenced from:
(null): "TXMPMeta<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >::~TXMPMeta()", referenced from:
(null): Linker command failed with exit code 1 (use -v to see invocation)

1

Решение

В основном, произошло то, что у вас есть только определения в заголовках
Если я говорю

template<class T> T something(T); где-то, что говорит компилятору «поверь мне, братан, он существует, оставь это компоновщику»

и он добавляет символ в объектный файл, как будто он существует. Поскольку он может видеть прототип, он знает, сколько стекового пространства, какой тип он возвращает и тому подобное, поэтому он просто настраивает его, чтобы компоновщик мог просто прийти и вставить адрес функции.

НО в вашем случае нет адреса. Вы / ДОЛЖНЫ / иметь определение шаблона (а не просто объявление) в одном и том же файле, чтобы компилятор мог создать его (со слабой связью), поэтому здесь предполагается, что они существуют, но не где он фактически исключает этот класс из шаблона, поэтому компоновщик не находит его, следовательно, ошибка.

Буду распушать мой ответ сейчас, надеюсь, это поможет.

Приложение 1:

template<class T> void output(T&);

int main(int,char**) {
int x = 5;
output(x);
return 0;
}

Это скомпилирует но НЕ ссылка на сайт.

Выход:

if ! g++ -Isrc -Wall -Wextra -O3 -std=c++11 -g -gdwarf-2 -Wno-write-strings  -MM src/main.cpp >> build/main.o.d ; then rm build/main.o.d ; exit 1 ; fi
g++ -Wall -Wextra -O3 -std=c++11 -g -gdwarf-2 -Wno-write-strings  -Isrc -c src/main.cpp -o build/main.o
g++  build/main.o  -o a.out
build/main.o: In function `main':
(my home)/src/main.cpp:13: undefined reference to `void output<int>(int&)'
collect2: error: ld returned 1 exit status
make: *** [a.out] Error 1

(Я угнал открытый проект для этого, следовательно, имена)

Как вы можете видеть, команда compile работает нормально (она заканчивается на -o build / main.o), потому что мы говорим: «посмотрите, эта функция существует»

Таким образом, в объектном файле он говорит компоновщику (в некоторой «форме, управляемой именем», чтобы сохранить шаблоны) «поместить местоположение в память вывода void (int&); здесь «компоновщик не может найти его.

Компиляции и ссылки

#include <iostream>
template<class T> void output(T&);

int main(int,char**) {
int x = 5;
output(x);
return 0;
}

template<class T> void output(T& what) {
std::cout<<what<<"\n";
std::cout.flush();
}

Обратите внимание на строку 2, мы говорим ей «существует функция, шаблон в T, называемый output, которая ничего не возвращает и берет ссылку T», что означает, что она может использовать ее в основной функции (помните, когда она анализирует основную функцию, которую она имеет пока не видел определения выходных данных, только что сказали, что он существует), компоновщик исправляет это. «Хотя современные компиляторы намного умнее (потому что у нас больше оперативной памяти :)) и изнасилуют структуру вашего кода, оптимизация по ссылочному времени делает это еще больше, но это то, как это раньше работало, и как это можно рассматривать работать в эти дни.

Выход:

make all
if ! g++ -Isrc -Wall -Wextra -O3 -std=c++11 -g -gdwarf-2 -Wno-write-strings  -MM src/main.cpp >> build/main.o.d ; then rm build/main.o.d ; exit 1 ; fi
g++ -Wall -Wextra -O3 -std=c++11 -g -gdwarf-2 -Wno-write-strings  -Isrc -c src/main.cpp -o build/main.o
g++  build/main.o  -o a.out

Как вы можете видеть, он хорошо скомпилирован и отлично связан.

Несколько файлов без включения в качестве доказательства этого

main.cpp

#include <iostream>

int TrustMeCompilerIExist();

int main(int,char**) {
std::cout<<TrustMeCompilerIExist();
std::cout.flush();
return 0;
}

proof.cpp

int TrustMeCompilerIExist() {
return 5;
}

Компиляция и ссылка

make all
if ! g++ -Isrc -Wall -Wextra -O3 -std=c++11 -g -gdwarf-2 -Wno-write-strings  -MM src/main.cpp >> build/main.o.d ; then rm build/main.o.d ; exit 1 ; fi
g++ -Wall -Wextra -O3 -std=c++11 -g -gdwarf-2 -Wno-write-strings  -Isrc -c src/main.cpp -o build/main.o
if ! g++ -Isrc -Wall -Wextra -O3 -std=c++11 -g -gdwarf-2 -Wno-write-strings  -MM src/proof.cpp >> build/proof.o.d ; then rm build/proof.o.d ; exit 1 ; fi
g++ -Wall -Wextra -O3 -std=c++11 -g -gdwarf-2 -Wno-write-strings  -Isrc -c src/proof.cpp -o build/proof.o
g++  build/main.o build/proof.o  -o a.out

(Выходы 5)

Помните, что #include LITERALLY выдает файл, в котором говорится «#include» (+ некоторые другие макросы, которые корректируют номера строк), это называется единицей перевода. Вместо того, чтобы использовать заголовочный файл, содержащий «int TrustMeCompilerIExist ();» который объявляет, что функция существует (но компилятор снова не знает, где она находится, код внутри нее, просто она существует), — повторил я.

Давайте посмотрим на proof.o

команда

objdump proof.o -t

выход

proof.o:     file format elf64-x86-64

SYMBOL TABLE:
0000000000000000 l    df *ABS*  0000000000000000 proof.cpp
0000000000000000 l    d  .text  0000000000000000 .text
0000000000000000 l    d  .data  0000000000000000 .data
0000000000000000 l    d  .bss   0000000000000000 .bss
0000000000000000 l    d  .debug_info    0000000000000000 .debug_info
0000000000000000 l    d  .debug_abbrev  0000000000000000 .debug_abbrev
0000000000000000 l    d  .debug_aranges 0000000000000000 .debug_aranges
0000000000000000 l    d  .debug_line    0000000000000000 .debug_line
0000000000000000 l    d  .debug_str 0000000000000000 .debug_str
0000000000000000 l    d  .note.GNU-stack    0000000000000000 .note.GNU-stack
0000000000000000 l    d  .eh_frame  0000000000000000 .eh_frame
0000000000000000 l    d  .comment   0000000000000000 .comment
0000000000000000 g     F .text  0000000000000006 _Z21TrustMeCompilerIExistv

Справа внизу есть функция, по смещению 6 в файл, с отладочной информацией (хотя g является глобальной), вы можете видеть, что она называется _Z (вот почему _ зарезервировано для некоторых вещей, я забыл, что именно. … но это связано с этим) и Z «integer», 21 — длина имени, а после имени v — «void» — тип возвращаемого значения.

Нули в начале — это номер раздела, помните, что двоичные файлы могут быть ОГРОМНЫМИ.

разборка
Бег:

objdump proof.o -S дает

proof.o:     file format elf64-x86-64Disassembly of section .text:

0000000000000000 <_Z21TrustMeCompilerIExistv>:int TrustMeCompilerIExist() {
return 5;
}
0:   b8 05 00 00 00          mov    $0x5,%eax
5:   c3                      retq

Поскольку у меня есть -g, вы можете видеть, как он помещает код, к которому относится сборка (это имеет смысл с большими функциями, он показывает вам, что на самом деле делают следующие инструкции до следующего блока кода), которого обычно не было бы.

main.o

Вот таблица символов, полученная так же, как указано выше:

objdump main.o -t

main.o:     file format elf64-x86-64

SYMBOL TABLE:
0000000000000000 l    df *ABS*  0000000000000000 main.cpp
0000000000000000 l    d  .text  0000000000000000 .text
0000000000000000 l    d  .data  0000000000000000 .data
0000000000000000 l    d  .bss   0000000000000000 .bss
0000000000000000 l    d  .text.startup  0000000000000000 .text.startup
0000000000000030 l     F .text.startup  0000000000000026 _GLOBAL__sub_I_main
0000000000000000 l     O .bss   0000000000000001 _ZStL8__ioinit
0000000000000000 l    d  .init_array    0000000000000000 .init_array
0000000000000000 l    d  .debug_info    0000000000000000 .debug_info
0000000000000000 l    d  .debug_abbrev  0000000000000000 .debug_abbrev
0000000000000000 l    d  .debug_loc 0000000000000000 .debug_loc
0000000000000000 l    d  .debug_aranges 0000000000000000 .debug_aranges
0000000000000000 l    d  .debug_ranges  0000000000000000 .debug_ranges
0000000000000000 l    d  .debug_line    0000000000000000 .debug_line
0000000000000000 l    d  .debug_str 0000000000000000 .debug_str
0000000000000000 l    d  .note.GNU-stack    0000000000000000 .note.GNU-stack
0000000000000000 l    d  .eh_frame  0000000000000000 .eh_frame
0000000000000000 l    d  .comment   0000000000000000 .comment
0000000000000000 g     F .text.startup  0000000000000026 main
0000000000000000         *UND*  0000000000000000 _Z21TrustMeCompilerIExistv
0000000000000000         *UND*  0000000000000000 _ZSt4cout
0000000000000000         *UND*  0000000000000000 _ZNSolsEi
0000000000000000         *UND*  0000000000000000 _ZNSo5flushEv
0000000000000000         *UND*  0000000000000000 _ZNSt8ios_base4InitC1Ev
0000000000000000         *UND*  0000000000000000 .hidden __dso_handle
0000000000000000         *UND*  0000000000000000 _ZNSt8ios_base4InitD1Ev
0000000000000000         *UND*  0000000000000000 __cxa_atexit

Посмотрите, как написано undefined, потому что он не знает, где он находится, он просто знает, что он существует (вместе со стандартными библиотеками, которые найдет компоновщик)

В заключение
ИСПОЛЬЗУЙТЕ ЗАГОЛОВОК ЗАГОЛОВОК и с шаблонами поместите файл #include file.cpp внизу, перед ЗАКРЫТИЕМ заголовка. таким образом, вы можете включить заголовочные файлы как обычно 🙂

8

Другие решения

Ответ на ваш вопрос присутствует в каждом образце, который идет с Набор инструментов XMP SDK.Клиенты должны скомпилировать XMP.incl_cpp, чтобы обеспечить генерацию всего кода на стороне клиента. Сделайте это, включив его точно в один из ваших исходных файлов.

Для вашего удобства я приведу ниже более подробное объяснение, представленное в разделе Шаблонные классы и доступ к API XMPProgrammersGuide.pdf, который поставляется с Набор инструментов XMP SDK

Шаблонные классы и доступ к API

Полный клиентский API определяется и документируется в заголовочных файлах TXMP * .hpp. Классы TXMP * являются классами шаблонов C ++, для которых должен быть создан экземпляр строкового класса, такого как std :: string, который используется для возврата текстовых строк для значений свойств, сериализованного XMP и т. Д. Чтобы разрешить вашему коду доступ ко всему API XMP, вы должны:

Предоставьте строковый класс, такой как std :: string, для создания экземпляров шаблонных классов.

Предоставьте доступ к XMPCore и XMPFiles, включив необходимые определения и заголовки. Для этого добавьте необходимое определение и включите директивы в ваш исходный код, чтобы весь необходимый код был включен в сборку:

#include <string>
#define XMP_INCLUDE_XMPFILES 1 //if using XMPFiles
#define TXMP_STRING_TYPE std::string
#include "XMP.hpp"

SDK предоставляет полную справочную документацию для классов шаблонов, но шаблоны должны быть созданы для использования. Вы можете прочитать заголовочные файлы (TXMPMeta.hpp и т. Д.) Для информации, но не включать их непосредственно в свой код. Существует один общий заголовочный файл, XMP.hpp, который является единственным, который клиенты C ++ должны включать с помощью директивы #include. Прочитайте инструкции в этом файле для создания экземпляров классов шаблонов. Когда вы это сделаете, API станет доступен через конкретные классы с именем SXMP *; то есть SXMPMeta, SXMPUtils, SXMPIterator и SXMPFiles. Этот документ относится к классам SXMP *, которые вы можете создавать и которые предоставляют статические функции.

Клиенты должны скомпилировать XMP.incl_cpp, чтобы обеспечить генерацию всего кода на стороне клиента. Сделайте это, включив его точно в один из ваших исходных файлов.
Прочитайте XMP_Const.h для получения подробной информации о типах и константах для URI пространства имен и флагов опций.

2

По вопросам рекламы [email protected]