Skocz do zawartości

Błędy podczas kolejnego dołączania pliku "h"


Pomocna odpowiedź

Napisano

Witam,

mam wiele rozterek i wątpliwości podczas pracy z biblioteką, wszystko jest dla mnie jak czarna magia:

 

1. Przede wszystkim biblioteka jest napisana w języku C, ale wszystko jest od razu w pliku H bez rozdziału na definicje w pliku H a funkcje w pliku C (!!!)

Nie za bardzo a raczej wcale tego nie rozumiem, dlaczego ktoś burzy ustalony porządek tych plików C i H, z tego co się dowiedziałem, to że pisanie całości programu w pliku H "bo tak jest lepiej".
Ja zakładam że tak naprawdę jest lepiej, bo cała ta biblioteka ma ponad 5000 linii i nie została napisana przez kogoś tak słabo znającego C jak ja, tylko przez fachowców i dodam że dzięki tej bibliotece LVGL zaczęło obsługiwać fonty TrueType.

Moim problemem jest to, że nie wiem dlaczego tak jest lepiej - czy ktoś mógłby mi wyjaśnić ten cały trick z pisaniem kodu w pliku H?

2. Mam kolejny plik H, w którym wywołuję funkcje z biblioteki i rysuję fonty. Każda funkcja jest zadeklarowana jako extern. W zasadzie nie ma to znaczenia, bo ten plik przerobiłem też na C i H, ale błędy generują się tak samo. OK przykładowa funkcja drukująca znak:

 

#include "fonty.h"

//===================================================================================

extern int lcd_mono_text_boxed(int pozx, int pozy, const char *text, const unsigned char *font_data, float pixel_height)
{
	return pozy + render_text_monospace(pozx, pozy, text, font_data, pixel_height);
}
//===================================================================================
//===================================================================================

extern int render_text_monospace(int pozx, int pozy, const char *text, const unsigned char *font_data, float pixel_height)
{

	if (!stbtt_InitFont(&font, font_data, stbtt_GetFontOffsetForIndex(font_data, 0)))
	{
		return 0;
	}

	float scale = stbtt_ScaleForPixelHeight(&font, pixel_height);

	int ascent, descent, line_gap;
	stbtt_GetFontVMetrics(&font, &ascent, &descent, &line_gap);
	int baseline = (int) (ascent * scale);

	//  Ustalamy rozmiar glifu (monospace)
	int adv_width, lsb;
	stbtt_GetCodepointHMetrics(&font, 'M', &adv_width, &lsb);  //
	int mono_w = (int) (adv_width * scale);
	int text_len = strlen(text);

	int x0, y0, x1, y1;
	stbtt_GetCodepointBitmapBox(&font, 'M', scale, scale, &x0, &y0, &x1, &y1);
	int total_h = baseline + y1;

	//  Renderowanie znaeden po drugim
	int pen_x = 0;
	for (int i = 0; i < text_len; i++)
	{
		int cp = text[i];
		int x0, y0, x1, y1;
		stbtt_GetCodepointBitmapBox(&font, cp, scale, scale, &x0, &y0, &x1, &y1);

		int gw = x1 - x0;
		int gh = y1 - y0;
		unsigned char *g_bitmap = stbtt_GetCodepointBitmap(&font, scale, scale, cp, &gw, &gh, 0, 0);





		uint16_t center_x =  (pozx+pen_x);  // X center in monospace fields
		uint16_t center_x_end = (center_x+mono_w-1);

		uint16_t center_y = ((total_h - gh) >> 1) + pozy; // Y center in monospace fields

		uint16_t buforek[320];


		LCD_OpenWin( center_x, center_y, center_x_end , center_y+gh-1); // open window big as TTF fonts
		for (int row = 0; row < gh; row++)
		{
			for(uint16_t it = 0; it< (mono_w) ; it++) { buforek[it] = lcd_background_color  ; }
			for (int col = 0; col < gw; col++)
			{
				unsigned char val = g_bitmap[row * gw + col];
				int px = col + (mono_w - gw)  ;  // centrowanie w polu monospace
				if (val > 0) {	buforek[px] = lcd_text_color; }
			}
			for(uint16_t it = 0 ; it < mono_w ; it++) { LCD_RAM = buforek[it];}
		}
		stbtt_FreeBitmap(g_bitmap, NULL);
		pen_x += mono_w;
	}

	return total_h;
}
//===================================================================================

Plik fonty.h wygląda tak:

/* Generated by bin2c, do not edit manually */
//#ifndef __FONTY_H__
//#define __FONTY_H__
/* Contents of file digital-7.ttf */
const long int digital_7_ttf_size = 34360;
const unsigned char digital_7_ttf[34360] = {
    0x00, 0x01,
  ...
    ...
    ...

 

Program zachowuje się dziwnie, oczywiście nie wiem dlaczego, ale cóż się dzieje:

Nieważne czy plik fonty.h ma włączoną protekcję zapętlenia  #include #ifndef __FONTY_H__ czy nie - błędy są cały czas takie same. Więc jeżeli drukuję na ekran wszelakie komunikaty z tego pliku że tak powiem, pierwszego do komunikacji z biblioteką - tego co ma funkcję :

extern int render_text_monospace(int pozx, int pozy, const char *text, const unsigned char *font_data, float pixel_height)

wszystko jest OK nie ma żadnych problemów - przykładowo drukuję takie coś:

render_biggestFont_portait(0, 0, tekst, digital_7_ttf, 230);

I wszystko gra, ALE... jeżeli spróbuję jakikolwiek inny komunikat wydrukować z innego pliku to pojawiają się błędy - a więc daję na przykład takie coś w innym pliku:

lcd_mono_text_boxed(x_tab[n], y_tab[n], (char*)&idx[0], digital_7_ttf, 20);

I wywala mi błędy:

Description    Resource    Path    Location    Type
'digital_7_ttf' undeclared (first use in this function)    TP_operations.c    /PITER_CLOCK/USER    line 111    C/C++ Problem
 

Zatem na początku pliku piszę taką linię:

#include "fonty.h"

 

i... nie nie nie... nie ma tak dobrze dostaję takie błędy:

Description    Resource    Path    Location    Type
./USER/TP_operations.o:X:/PITER_CLOCK/Debug/../USER/fonty.h:5: multiple definition of `digital_7_ttf_size'; ./USER/RTC.o:X:/PITER_CLOCK/Debug/../USER/fonty.h:5: first defined here    PITER_CLOCK             C/C++ Problem
Description    Resource    Path    Location    Type
./USER/TP_operations.o:X:/PITER_CLOCK/Debug/../USER/fonty.h:6: multiple definition of `digital_7_ttf'; ./USER/RTC.o:X:/PITER_CLOCK/Debug/../USER/fonty.h:6: first defined here    PITER_CLOCK             C/C++ Problem
 

Czyli albo nie mam zmiennej zadeklarowanej, albo jest zbyt wiele razy zadeklarowana (???)

 

Problem obszedłem w taki sposób że w tym pliku gdzie mogę drukować dodałem dwie funkcje wrappery:

//=====================================================================================
int lcd_mono_t_box(int pozx, int pozy, const char *text, int data_font, float pixel_height)
{
	if(data_font == 0) {	return lcd_mono_text_boxed(pozx, pozy, text, digital_7_ttf, pixel_height);}
	if(data_font == 1) {	return lcd_mono_text_boxed(pozx, pozy, text, dum1_ttf, pixel_height);}
    return 0;
}
//=====================================================================================
int lcd_t_box(int pozx, int pozy, const char *text, int data_font, float pixel_height)
{
	if(data_font == 0) {	return lcd_text_boxed(pozx, pozy, text, digital_7_ttf, pixel_height);}
	if(data_font == 1) {	return lcd_text_boxed(pozx, pozy, text, dum1_ttf, pixel_height);}
    return 0;
}
//=====================================================================================

 

a w pliku z którego chcę drukować dodałem takie wpisy:

#define digital_7_ttf     0
#define dum1_ttf          1
#define lcd_mono_text_boxed       lcd_mono_t_box
#define lcd_text_boxed            lcd_t_box

extern int lcd_mono_t_box(int pozx, int pozy, const char *text, int data_font, float pixel_height);
extern int lcd_t_box(int pozx, int pozy, const char *text, int data_font, float pixel_height);

I wówczas mogę drukować wszystko z innego pliku. Polecenie na przykład:

lcd_mono_text_boxed(35, 75,"MATRIX ADDRESS", digital_7_ttf, 20);

Działa bezproblemowo.

Natomiast ja nie wiem czemu tak się dzieje. Pewnie znawcy C którzy to przeczytają mogą mieć ze mnie ubaw, ale byłbym wdzięczny za wyjaśnienie - co tutaj się dzieje.

 

Cały projekt jest dostępny:

https://github.com/wegi1/STM32F407VET6-BLACK-DEVBOARD-AS-RTC/tree/main

można go sklonować i od razu kompilować.

Efekt jego działania przykładowo:

 

 

Liczę na pomoc kolegi @ethanak, który zadeklarował że jest taka możliwość - z góry dziękuję...

 

Aha - ta genialna biblioteka do TTFów to stb_truetype

 

Jeszcze wymyśliłem takie rozwiązanie że zamiast tych wrapperów:

#define digital_7_ttf     0
#define dum1_ttf          1
#define lcd_mono_text_boxed       lcd_mono_t_box
#define lcd_text_boxed            lcd_t_box

extern int lcd_mono_t_box(int pozx, int pozy, const char *text, int data_font, float pixel_height);
extern int lcd_t_box(int pozx, int pozy, const char *text, int data_font, float pixel_height);

 

podałem externy:

extern const unsigned char digital_7_ttf[] ;
extern const unsigned char dum1_ttf[];

extern int lcd_text_boxed(int pozx, int pozy, const char *text, const unsigned char *font_data, float pixel_height);
extern int lcd_mono_text_boxed(int pozx, int pozy, const char *text, const unsigned char *font_data, float pixel_height);

 

Trochę mniej kombinowania bez dodatkowych funkcji, a więc lepiej i chyba to by było prawidłowe rozwiązanie. Natomiast sztuczki z biblioteką zawierającą cały kod w pliku H są dla mnie enigmą...

12 godzin temu, virtualny napisał:

pisanie całości programu w pliku H "bo tak jest lepiej".

A butem można gwoździa wbić bo tak jest lepiej (oszczędzasz na młotku).

Nie wypowiem się na temat takiego podejścia bo mnie zbanują na resztę życia... w każdym razie (pośrednio i w nieprzyjemny aczkolwiek wielce skuteczny sposób) wymusza to całkiem porządny styl pisania programów - mianowicie taki, że wszelkie operacje związane z displayem siedzą sobie w jednym pliku, a reszty programu to nie interesuje. Przykładowo (z czapy): jeśli mamy stację pogodową, to  moduł pomiaru temperatury nie musi nic wiedzieć o jakichś fontach, wyświetlaczach czy opozycjach na ekranie. Z drugiej strony modułu wyświetlania nie interesuje skąd się wzięło np. 18.5, jego zadaniem jest to wyświetlić.

Nie zaglądając do Twojego kodu (niestety, wydawało mi się że będę miał trochę wolnego przed południem ale trafiłem w dziesiątkę - wydawało mi się):

Częściowo zrobiłeś dobrze. Czyli masz te funkcje we właściwym pliku, możesz zrobić sobie dodatkowy plik .h zawierający deklaracje tych funkcji i włączać sobie gdzie chcesz. Ale przemyśl jedno: czy naprawdę w każdym miejscu w programie potrzebna jest funkcja operująca na jakichś fontach czy pikselach?

Analogicznie z przykładu z czapy:

Plik numer 1 (c):

static float stara_temperatura = -274.0; // zgadnij czemu
float temperatura = odczytajTermometr();
if (temperatura != stara_temperatura) {
  stara_temperatura = temperatura;
  wyswietl_temperature(temperatura);
}

Plik numer 2 (c, ten się zna na fontach i srontach ale nie termometrze):

void wyswietl_temperature(float temperatura)
{
   //tu jakieś instrukcje pozycjonujące kursor, ustalające fonta i takie tam pierdoły
  ...
    // a tu funkcja wyświetlania, oczywiście w rzeczywistości wygląda zupełnie inaczej
    wyswietl_na_displayu_na_ustawionej pozycji(temperatura);
}

Plik numer 3 (h, włączany do różnych innych plików):

#ifndef MOJEFUNKCJE_H
#define MOJEFUNKCJE_H
  
extern void wyswietl_temperature(float temperatura);
// no i cała reszta funkcji

#endif
  

Takl przy okazji: słówko 'extern' pełni tu rolę wyłącznie dekoracyjną, program będzie prawidłowo skompilowany i bez tego, ale lepiej go uzyć bo w innych przypadkach może być niezbędne.

No i krótka odpowiedź dlaczego tak się dzieje że coś jest źle:

W C deklaracja funkcji (czyli opis jak ją się wywołuje) może wystąpić dowolną ilość razy (łącznie z zerem, gdy definicja pełni rolę deklaracji) pod warunkiem oczywiście, że są zgodne. Natomiast definicja (czyli ciało funkcji) musi wystąpić dokładnie raz. Kompilator za każdym razem kiedy napotyka definicję radośnie tworzy kod wynikowy tej funkcji nie sprawdzając, czy już w innym pliku taka wystąpiła. Natomiast linker (którego zadaniem jest zebranie wszystkiego w jedną całość) dostaje informację, że istnieją dwie definicje funkcji o tej samej nazwie i uznaje to za oczywisty błąd. Nieważne, że kod funkcji jest identyczny: linkera to nie interesuje.

No a jeśli masz definicję (nie deklarację) funkcji w pliku .h, to każde włączenie polega po prostu na skopiowaniu zawartości tego pliku w miejsce, gdzie masz #include. Czyli jeśli zrobisz to w dwóch plikach - dostaniesz dwie definicje.

Jeśli coś niejasne pisz, tyle że mogę odpowiadać z opóźnieniem.

  • Lubię! 2
17 minut temu, ethanak napisał:

A butem można gwoździa wbić bo tak jest lepiej (oszczędzasz na młotku).

🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣

 

18 minut temu, ethanak napisał:

Jeśli coś niejasne pisz, tyle że mogę odpowiadać z opóźnieniem.

 

No tak jest dla mnie niejasne, bo problem dotyczył tych binarek fontów w pliku H - dla przypomnienia wygląda on tak:

 

/* Generated by bin2c, do not edit manually */

/* Contents of file digital-7.ttf */
const long int digital_7_ttf_size = 34360;
const unsigned char digital_7_ttf[34360] = {
    0x00, 0x01,
  ...

 

No i kompilator kompleinował najpierw że brakuje mu definicji digital_7_ttf

ALE... !!!

Po wykonaniu include pliku który tą definicję zawierał (fonty.h):

#include "fonty.h"

Kompilator twierdził że wystąpiła wielokrotna definicja digital_7_ttf !!!

Tym samy stawiając mnie pod ścianą, że tak jest źle, a tak niedobrze !!!

Najpierw obszedłem to w sposób jak podałem, że napisałem dwie funkcje wrappery, czyli nic nie robiące poza przekazywaniem podanych parametrów do innej funkcji, z wyjątkiem tego, że parametr wyboru fontów był przekazywany jako wartość, a owe wrappery zamieniały ten parametr na realny wskaźnik do fontów.

Oprócz tego musiałem dodać kilka (re)definicji. Pomimo że to działało, zaciemniało kod, a co najgorsze nie przybliżało mnie do dokładnego zrozumienia w czym tkwi istota problemu.

Dzisiaj wpadłem na to żeby zastosować taką sztuczkę: 

extern const unsigned char digital_7_ttf[] ;

i to zadziałało - natomiast nadal nie rozumiem niekonsekwencji kompilatora, który najpierw mówi:

BRAKUJE MI ZMIENNEJ - WEŹ MI JĄ DAJ !

 

ale gdy mu tą zmienną dam to ów kompilator twierdzi:

 

ZIOMUŚ DAŁEŚ MI TĄ ZMIENNĄ ZBYT WIELE RAZY NICZEGO CI NIE SKOMPILUJE MASZ 35 ERRORÓW!

 

No i właśnie tej kołomyi nie do końca rozumiem.

 

No i dzięki za odpowiedź.

 

 

 

 

 

 

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • Utwórz nowe...