В настоящее время я использую small3dlib с SDL, чтобы попытаться создать простую 3D-игру.
Я включил библиотеку в свой проект C, просто поместив заголовочный файл в исходный каталог, и создал два файла, gfx.c и gfx.h, которые служат для управления графикой для всего проекта.
Моя текущая структура проекта выглядит так:
tsa-3d/
| bin/
| build/
| src/
|-- gfx.c
|-- main.c
|-- include/
|---- gfx.h
|---- logger.h
|---- loggerconf.h
|---- models.h
|---- small3dlib.h
|---- tgc.h
|-- lib/
|---- logger.c
|---- loggerconf.c
|---- tgc.c
|-- models/
|---- carModel.c
|---- cityModel.c
|---- cityTexture.c
Из них только файлы, включающие small3dlib (косвенно), это main.c, gfx.c/.h, models.h и все модели.
Библиотека требует, чтобы несколько макросов были определены перед включением, поэтому мне нужно каждый раз вызывать заголовок моего графического интерфейса gfx.h, а не small3dlib.h.
#ifndef GFX_H
#define GFX_H
#define S3L_FLAT 0
#define S3L_NEAR_CROSS_STRATEGY 3
#define S3L_PERSPECTIVE_CORRECTION 2
#define S3L_SORT 0
#define S3L_STENCIL_BUFFER 0
#define S3L_Z_BUFFER 2
#define S3L_PIXEL_FUNCTION draw_pixel
#define S3L_RESOLUTION_X 640
#define S3L_RESOLUTION_Y 480
#define TEXTURE_W 256
#define TEXTURE_H 256
#define WINDOW_TITLE "hello, world"
#include <SDL2/SDL.h>
#include <stdint.h>
#include "small3dlib.h"
#include "models.h"
...
В результате этого макета у меня есть следующая ошибка связывания для каждой функции, определенной в small3dlib.h:
/usr/bin/ld: src/main.o:(.bss+0x0): multiple definition of `S3L_zBuffer'; src/gfx.o:(.bss+0x0): first defined here
/usr/bin/ld: src/main.o: in function `S3L_zBufferRead':
main.c:(.text+0x7d): multiple definition of `S3L_zBufferRead'; src/gfx.o:gfx.c:(.text+0x7d): first defined here
/usr/bin/ld: src/main.o: in function `S3L_zBufferWrite':
main.c:(.text+0xb9): multiple definition of `S3L_zBufferWrite'; src/gfx.o:gfx.c:(.text+0xb9): first defined here
/usr/bin/ld: src/main.o: in function `S3L_mat4Copy':
...
У меня была такая же проблема и для всех моделей (все они были получены из проекта и изначально имели определения переменных/функций в заголовках, пока я не разделил их только на файлы .c и один общий заголовок.
Я огляделся столько, сколько мог, и я не уверен, что мне нужно сделать, чтобы исправить это. Я знаю, что определения переменных и функций не должны помещаться в файлы заголовков, но вся эта библиотека построена на том, что все определения находятся в одном заголовке. Все примеры программ для этой библиотеки имеют small3dlib.h, включенный только в один файл .c, так что в таком случае, как эта библиотека предназначена для использования, когда она включена в несколько файлов?
Полный код проекта на данный момент можно найти здесь . Это попытка адаптации примера города в репозитории библиотеки.
Я попытался разделить библиотеку на заголовочный файл и исходный файл, чтобы все функции теперь просто прототипировались в заголовке. Этот вариант не удался, потому что сам заголовок невероятно велик, а преобразование каждого определения функции в объявление и знание того, какие функции нужно преобразовать, а какие нет, утомительно и утомительно для 2800 строк C. После попытки сделать это около получаса , я предположил, что, скорее всего, просто делаю что-то не так, пытаясь связать библиотеку, и что библиотека, скорее всего, не виновата в этой проблеме.
Обновлено: это минимальный воспроизводимый пример этого:
Вот пример того, что здесь происходит:
Файловая структура:
project/
| Makefile
| src/
|-- main.c
|-- foo.c
|-- include/
|---- something.h
|---- lib.h
Makefile
# Global
SRC := $(wildcard src/*.c) \
$(wildcard src/**/*.c) \
$(wildcard src/**/**/*.c) \
$(wildcard src/**/**/**/*.c)
OUT := program
# Default Build
EXE := ./build/${OUT}
CFLAGS := -I./src/include
LIBFLAGS := -lSDL2 -ldl
OBJ := ${SRC:.c=.o}
build: clean-obj dir ${OBJ}
gcc ${OBJ} -o ${EXE} ${CFLAGS} ${LIBFLAGS}
${call clean-obj}
%.o: %.c
gcc -c $< -o $@ ${CFLAGS}
run: build
./bin/${OUT}
# Utility
dir:
mkdir -p ./bin ./build/
clean-obj:
rm -f ${OBJ} ./bin/*
clean: clean-obj
rm -f ${EXE}
Lib.h -> представление small3dlib.h
#ifndef LIB_H
#define LIB_H
#include <stdint.h>
#include <stdio.h>
#ifndef SOME_MACRO
#error SOME_MACRO NOT DEFINED!
#endif
#define ARRAY_LENGTH 3
const uint8_t array[ARRAY_LENGTH] = { 1, 2, 3 };
void hello(uint8_t x) {
for (uint8_t i = 0; i < x; i++)
printf("%d", i);
}
#endif
Что-то.ч
#ifndef SOMETHING_H
#define SOMETHING_H
#define SOME_MACRO 3
#include "lib.h"
void bar();
#endif
Main.c
#include "include/something.h"
int main(int argc, char* argv[]) {
hello(array[2]);
bar();
}
Foo.c
#include "include/something.h"
void bar() {
hello(8);
}
Это приводит к:
...
/usr/bin/ld: src/main.o:(.rodata+0x0): multiple definition of `array'; src/foo.o:(.rodata+0x0): first defined here
/usr/bin/ld: src/main.o: in function `hello':
main.c:(.text+0x0): multiple definition of `hello'; src/foo.o:foo.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
make: *** [Makefile:18: build] Error 1
Не удается получить доступ ко всему коду. Пожалуйста, предоставьте минимальный воспроизводимый пример.
Что ж, тогда нет другого решения, кроме того, которое мы предлагаем: не включайте этот заголовок в несколько единиц перевода.
Хорошо. Спасибо за совет. Я новичок в C, поэтому любая помощь, которую я могу получить, полезна! Я собираюсь прочитать о единицах перевода и о том, как ими манипулировать, и посмотреть, смогу ли я двигаться дальше. Если вы хотите, могу ли я получить простой пример того, как это реализовать? Спасибо!
Библиотеки только для файлов заголовков... хм... с ними не так просто работать. Вы не можете включать их более чем в одну единицу перевода (источник vulgo). В противном случае у вас будет столько же определений, сколько единиц перевода, которые его включили, и, как следствие, ошибки сборки. Кроме того, существует потенциальное несоответствие в их контроле #define.
Поэтому, как и в вашем примере, вам нужно написать реализацию, обеспечивающую единую единицу перевода. Я назвал его "lib_impl.c". Чтобы централизовать необходимые определения, я написал отдельный заголовочный файл с именем «lib_defs.h»:
#ifndef LIB_DEFS_H
#define LIB_DEFS_H
#define SOME_MACRO 3
#endif
К сожалению, вам нужно написать отдельный заголовочный файл, содержащий все объявления объектов, которые вы используете в своем приложении. Вам не нужно объявлять все возможные объекты библиотеки. Я назвал его «lib_decl.h»:
#ifndef LIB_DECL_H
#define LIB_DECL_H
#include <stdint.h>
extern const uint8_t array[];
void hello(uint8_t x);
#endif
Единственный файл реализации:
// lib_impl.c
#include "lib_defs.h"
#include "lib_decl.h"
// Actually, this is a source file, not a header file!
#include "lib.h"
Вы можете позволить компилятору проверить, правильно ли вы написали этот заголовочный файл объявления, если включите его в файл реализации.
Теперь вы можете включить заголовочный файл объявления, где вам нужно получить доступ к объектам библиотеки.
В вашем примере вы, кажется, используете какой-то «глобальный» включаемый файл «something.h». По-видимому, он собирает объявления нескольких модулей:
#ifndef SOMETHING_H
#define SOMETHING_H
#include "lib_decl.h"
void bar();
#endif
Все остальные файлы без изменений, и приложение собирается без проблем.
Примечание 1. Поскольку вы предоставляете компилятору путь к включаемым файлам, вам не нужно использовать «включать» в директивах #include.
Примечание 2. При удалении всех объектных файлов, как правило, промежуточных файлов, вы отказываетесь от основной функции make: запускайте только необходимые команды для создания зависимых целевых файлов.
Последнее замечание: я понимаю, почему авторам нравятся библиотеки только для заголовочных файлов. Это всего лишь один файл для обслуживания. Однако это сопряжено с рядом недостатков:
#define
все функции, которые он хочет использовать.Это фактически устраняет разовые усилия автора библиотеки и передает их каждому пользователю, умножая усилия на количество использований. :-( Судите сами.
Большое спасибо за помощь. Это было очень полезно и информативно, но я просто не могу понять, как применить этот совет к моему проекту. (Я все еще продолжаю сталкиваться с ошибками и т. д. и т. д.) Скорее всего, я просто попытаюсь не использовать эту библиотеку, так как, судя по ее звукам, это пустая трата времени и результат безответственного дизайна библиотеки.
Ну, если у вас такой уровень опыта, было бы хорошим решением отказаться от этой библиотеки. Тем не менее, не стесняйтесь публиковать еще один вопрос с новыми проблемами, если вы стремитесь подняться по крутой кривой обучения. Просто убедитесь, что вы приложили к этому достаточно усилий, так как это сайт QnA, а не обучающий форум. ;-) - Возможно, вы захотите пометить этот ответ как «принятый», чтобы будущие посетители могли его использовать.
Добро пожаловать в StackOverflow! Пожалуйста, посетите тур , чтобы узнать, как работает этот сайт. Это не форум, поэтому, пожалуйста, добавьте пояснения, отредактировав свой вопрос. Вы также можете прочитать « Как спросить ». -- По-видимому, эта библиотека только для заголовков не может быть включена в несколько единиц перевода приложения. Вот что показывает ошибка. Самое простое решение — включить его только в одну единицу перевода и предоставить функции «обертки» для всех остальных модулей. Вы рассматривали и пробовали это? -- Обычно мы ожидаем, что минимальный воспроизводимый пример сможет воспроизвести вашу проблему. Пожалуйста, рассмотрите возможность добавления одного.