Linker error when trying to use Azure-iot-sdk-c serializer model in multiple files - c

I try to use a serializer model with the azure-iot-sdk-c in multiple files. As long as I use it only in the main file, everything compiles.
But when I declare the model in another header file, use it in the corresponding c file and in the main file, I get a linker error:
CMakeFiles/simplesample_amqp.dir/otherfile.c.o: In function `FromAGENT_DATA_TYPE_ContosoAnemometer':
otherfile.c:(.text+0x1165): multiple definition of `FromAGENT_DATA_TYPE_ContosoAnemometer'
CMakeFiles/simplesample_amqp.dir/simplesample_amqp.c.o:simplesample_amqp.c:(.text+0x1165): first defined here
collect2: error: ld returned 1 exit status
serializer/samples/simplesample_amqp/CMakeFiles/simplesample_amqp.dir/build.make:160: recipe for target 'serializer/samples/simplesample_amqp/simplesample_amqp' failed
make[2]: *** [serializer/samples/simplesample_amqp/simplesample_amqp] Error 1
CMakeFiles/Makefile2:3947: recipe for target 'serializer/samples/simplesample_amqp/CMakeFiles/simplesample_amqp.dir/all' failed
make[1]: *** [serializer/samples/simplesample_amqp/CMakeFiles/simplesample_amqp.dir/all] Error 2
I tried this by adapting the serializer/samples/simplesample_amqp example:
diff --git a/serializer/samples/simplesample_amqp/CMakeLists.txt b/serializer/samples/simplesample_amqp/CMakeLists.txt
index 4f26060..52d55d7 100644
--- a/serializer/samples/simplesample_amqp/CMakeLists.txt
+++ b/serializer/samples/simplesample_amqp/CMakeLists.txt
## -11,6 +11,7 ## endif()
set(simplesample_amqp_c_files
simplesample_amqp.c
+otherfile.c
)
if(WIN32)
## -21,6 +22,7 ## endif()
set(simplesample_amqp_h_files
simplesample_amqp.h
+otherfile.h
)
IF(WIN32)
diff --git a/serializer/samples/simplesample_amqp/simplesample_amqp.c b/serializer/samples/simplesample_amqp/simplesample_amqp.c
index 0a9c9a8..b312c9c 100644
--- a/serializer/samples/simplesample_amqp/simplesample_amqp.c
+++ b/serializer/samples/simplesample_amqp/simplesample_amqp.c
## -26,24 +26,11 ##
#include "certs.h"
#endif // SET_TRUSTED_CERT_IN_SAMPLES
+#include "otherfile.h"
+
/*String containing Hostname, Device Id & Device Key in the format: */
/* "HostName=<host_name>;DeviceId=<device_id>;SharedAccessKey=<device_key>" */
-static const char* connectionString = "[device connection string]";
-
-// Define the Model
-BEGIN_NAMESPACE(WeatherStation);
-
-DECLARE_MODEL(ContosoAnemometer,
-WITH_DATA(ascii_char_ptr, DeviceId),
-WITH_DATA(int, WindSpeed),
-WITH_DATA(float, Temperature),
-WITH_DATA(float, Humidity),
-WITH_ACTION(TurnFanOn),
-WITH_ACTION(TurnFanOff),
-WITH_ACTION(SetAirResistance, int, Position)
-);
-
-END_NAMESPACE(WeatherStation);
+static const char* connectionString = "";
static char propText[1024];
## -199,10 +186,8 ## void simplesample_amqp_run(void)
}
else
{
- myWeather->DeviceId = "myFirstDevice";
- myWeather->WindSpeed = avgWindSpeed + (rand() % 4 + 2);
- myWeather->Temperature = minTemperature + (rand() % 10);
- myWeather->Humidity = minHumidity + (rand() % 20);
+ myWeather->DeviceId = "myFirstDevice";
+ process_model(myWeather);
if (SERIALIZE(&destination, &destinationSize, myWeather->DeviceId, myWeather->WindSpeed, myWeather->Temperature, myWeather->Humidity) != CODEFIRST_OK)
{
otherfile.h:
// Define the Model
#include "serializer.h"
BEGIN_NAMESPACE(WeatherStation);
DECLARE_MODEL(ContosoAnemometer,
WITH_DATA(ascii_char_ptr, DeviceId),
WITH_DATA(int, WindSpeed),
WITH_DATA(float, Temperature),
WITH_DATA(float, Humidity),
WITH_ACTION(TurnFanOn),
WITH_ACTION(TurnFanOff),
WITH_ACTION(SetAirResistance, int, Position)
);
END_NAMESPACE(WeatherStation);
void process_model(ContosoAnemometer *myWeather);
otherfile.c:
#include "otherfile.h"
void process_model(ContosoAnemometer *myWeather) {
int avgWindSpeed = 10;
float minTemperature = 20.0;
float minHumidity = 60.0;
myWeather->WindSpeed = avgWindSpeed + (rand() % 4 + 2);
myWeather->Temperature = minTemperature + (rand() % 10);
myWeather->Humidity = minHumidity + (rand() % 20);
}
Is there any way to work with the serializer model in multiple files?
Feel free to ask me for more information, please. Right now I'm not so sure what information is relevant for this post. I tried rigorously with the mqtt serializer example some time ago but got stuck with the same error as far as I can remember. It is really messy if you can not modularize your code and just have one monolithic file containing almost everything.
GCC:
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609
OS:
4.4.0-97-generic #120-Ubuntu SMP Tue Sep 19 17:28:18 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

You can't reuse models but you can reuse datastructures. This can help you reuse up to 80% of your definitions.
You can design JSON structure as a set of DECLARE_STRUCT like this in separate file:
DECLARE_STRUCT(MyCommon,
EDM_DATE_TIME_OFFSET, datetime,
ascii_char_ptr, deviceid,
ascii_char_ptr, vin,
int32_t, message_id,
int32_t, request_messageid
);
After that you can use #include to reuse DECLARE_STRUCT for different models in С files:
BEGIN_NAMESPACE(cctcp);
#include "MyCommon_json.inc"
DECLARE_MODEL(Message,
WITH_DATA(EDM_GUID, edge_message_id),
WITH_DATA(int8_t, message_type),
WITH_DATA(MyCommon, tcu_common)
);

Related

Getting undefined references for C functions while making ESP8266 project

I followed this tutorial for installing a Kaa application into an ESP8266, and it worked after a few modifications: https://kaaproject.github.io/kaa/docs/v0.10.0/Programming-guide/Using-Kaa-endpoint-SDKs/C/SDK-ESP8266/
One of the modifications I had to make was to move a line of code in eagle.app.v6.ld because of byte overflow (arrow points to change I made):
...
.irom0.text : ALIGN(4)
{
_irom0_text_start = ABSOLUTE(.);
*(.literal.* .text.*) --> moved from ".text : ALIGN(4){...}"
...
}
...
After I did this I still had some byte overflow, so I modified the original cmake command from the documentation to disable cmake extensions that were taking up space:
cmake \
-DCMAKE_TOOLCHAIN_FILE=../kaa/toolchains/esp8266.cmake \
-DKAA_PLATFORM=esp8266 \
-DCMAKE_BUILD_TYPE=MinSizeRel \
-DWITH_EXTENSION_CONFIGURATION=OFF \
-DWITH_EXTENSION_EVENT=OFF \
-DWITH_EXTENSION_LOGGING=OFF \
-DWITH_EXTENSION_NOTIFICATION=OFF \
-DWITH_EXTENSION_USER=OFF \
-DWITH_ENCRYPTION=OFF \
-DKAA_MAX_LOG_LEVEL=3 ..
Finally, when I ran the make command. it worked. I then created and flashed the binaries into my ESP. Then I reset my ESP with GPIO0 high (so it can boot from flash) and the ESP sent "Hello, Kaa!" into the serial port I was connected to.
Now, however. I am trying to make it so that my ESP8266 connects to my Kaa server and generates an Endpoint Profile, so I need the code to create a transport channel to communicate to both the Bootstrap server and Operations server.
To do this, I tried using the code from Your First Kaa Application that generates fake temp readings and receives the configuration schema and pushes data to my Cassandra server via log appender:
https://kaaproject.github.io/kaa/docs/v0.10.0/Programming-guide/Your-first-Kaa-application/
So in my attempt to do this, I left my directory the same:
CMakeLists.txt
driver/
uart.h
uart.c
ld/
eagle.app.v6.ld
eagle.rom.addr.v6.ld
kaa/
<put Kaa SDK here> --> replaced with new SDK with log and configuration schema
user/
user_main.c
src/
kaa_demo.c --> replaced with new code from "Your First Kaa Application"
I then replaced this code into my kaa_demo.c file:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <time.h>
#include <kaa/kaa.h>
#include <kaa/platform/kaa_client.h>
#include <kaa/kaa_error.h>
#include <extensions/configuration/kaa_configuration_manager.h>
#include <extensions/logging/kaa_logging.h>
#include <kaa/gen/kaa_logging_gen.h>
#include <kaa/platform/kaa_client.h>
#include <kaa/utilities/kaa_log.h>
#include <kaa/platform-impl/common/ext_log_upload_strategies.h>
static int32_t sample_period;
static time_t last_sample_time;
extern kaa_error_t ext_unlimited_log_storage_create(void **log_storage_context_p, kaa_logger_t *logger);
/* Retrieves current temperature. */
static int32_t get_temperature_sample(void)
{
/* For the sake of example, random data is used */
return rand() % 10 + 25;
}
/* Periodically called by Kaa SDK. */
static void example_callback(void *context)
{
time_t current_time = time(NULL);
/* Respect sample period */
if (difftime(current_time, last_sample_time) >= sample_period) {
int32_t temperature = get_temperature_sample();
printf("Sampled temperature: %i\n", temperature);
last_sample_time = current_time;
kaa_user_log_record_t *log_record = kaa_logging_data_collection_create();
log_record->temperature = temperature;
kaa_logging_add_record(kaa_client_get_context(context)->log_collector, log_record, NULL);
}
}
/* Receives new configuration data. */
static kaa_error_t on_configuration_updated(void *context, const kaa_root_configuration_t *conf)
{
(void) context;
printf("Received configuration data. New sample period: %i seconds\n", conf->sample_period);
sample_period = conf->sample_period;
return KAA_ERR_NONE;
}
int main(void)
{
/* Init random generator used to generate temperature */
srand(time(NULL));
/* Prepare Kaa client. */
kaa_client_t *kaa_client = NULL;
kaa_error_t error = kaa_client_create(&kaa_client, NULL);
if (error) {
return EXIT_FAILURE;
}
/* Configure notification manager. */
kaa_configuration_root_receiver_t receiver = {
.context = NULL,
.on_configuration_updated = on_configuration_updated
};
error = kaa_configuration_manager_set_root_receiver(
kaa_client_get_context(kaa_client)->configuration_manager,
&receiver);
if (error) {
return EXIT_FAILURE;
}
/* Obtain default configuration shipped within SDK. */
const kaa_root_configuration_t *dflt = kaa_configuration_manager_get_configuration(
kaa_client_get_context(kaa_client)->configuration_manager);
printf("Default sample period: %i seconds\n", dflt->sample_period);
sample_period = dflt->sample_period;
/* Configure data collection. */
void *log_storage_context = NULL;
void *log_upload_strategy_context = NULL;
/* The internal memory log storage distributed with Kaa SDK. */
error = ext_unlimited_log_storage_create(&log_storage_context,
kaa_client_get_context(kaa_client)->logger);
if (error) {
return EXIT_FAILURE;
}
/* Create a strategy based on timeout. */
error = ext_log_upload_strategy_create(
kaa_client_get_context(kaa_client), &log_upload_strategy_context,
KAA_LOG_UPLOAD_BY_TIMEOUT_STRATEGY);
if (error) {
return EXIT_FAILURE;
}
/* Strategy will upload logs every 5 seconds. */
error = ext_log_upload_strategy_set_upload_timeout(log_upload_strategy_context, 5);
if (error) {
return EXIT_FAILURE;
}
/* Specify log bucket size constraints. */
kaa_log_bucket_constraints_t bucket_sizes = {
.max_bucket_size = 32, /* Bucket size in bytes. */
.max_bucket_log_count = 2, /* Maximum log count in one bucket. */
};
/* Initialize the log storage and strategy (by default, they are not set). */
error = kaa_logging_init(kaa_client_get_context(kaa_client)->log_collector,
log_storage_context, log_upload_strategy_context, &bucket_sizes);
if (error) {
return EXIT_FAILURE;
}
/* Start Kaa SDK's main loop. example_callback is called once per second. */
error = kaa_client_start(kaa_client, example_callback, kaa_client, 1);
/* Should get here only after Kaa stops. */
kaa_client_destroy(kaa_client);
if (error) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
I left the CMakeLists.txt file the same:
cmake_minimum_required(VERSION 3.0.2)
project(kaa_demo C)
# Add Kaa SDK directory
add_subdirectory(kaa)
# Add source files
add_library(kaa_demo_s STATIC user/user_main.c driver/uart.c src/kaa_demo.c)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99")
if(NOT DEFINED ESP_RTOS_SDK)
set(ESP_RTOS_SDK /opt/Espressif/esp-rtos-sdk)
endif()
# specify include directories
target_include_directories(kaa_demo_s PUBLIC driver)
target_include_directories(kaa_demo_s PUBLIC .)
target_include_directories(kaa_demo_s PUBLIC
${ESP_RTOS_SDK}/extra_include
${ESP_RTOS_SDK}/include
${ESP_RTOS_SDK}/include/lwip
${ESP_RTOS_SDK}/include/lwip/ipv4
${ESP_RTOS_SDK}/include/lwip/ipv6
${ESP_RTOS_SDK}/include/espressif/
)
exec_program(xtensa-lx106-elf-gcc .
ARGS -print-libgcc-file-name
OUTPUT_VARIABLE ESP8266_LIBGCC
)
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/ld)
target_link_libraries(kaa_demo_s PUBLIC
kaac
${ESP_RTOS_SDK}/lib/libfreertos.a
${ESP_RTOS_SDK}/lib/libhal.a
${ESP_RTOS_SDK}/lib/libpp.a
${ESP_RTOS_SDK}/lib/libphy.a
${ESP_RTOS_SDK}/lib/libnet80211.a
${ESP_RTOS_SDK}/lib/libwpa.a
${ESP_RTOS_SDK}/lib/liblwip.a
${ESP_RTOS_SDK}/lib/libmain.a
${ESP_RTOS_SDK}/lib/libssl.a
${ESP_RTOS_SDK}/lib/libhal.a
${ESP8266_LIBGCC}
-Teagle.app.v6.ld
)
file(WRITE ${CMAKE_BINARY_DIR}/blank.c "")
add_executable(kaa_demo ${CMAKE_BINARY_DIR}/blank.c)
target_link_libraries(kaa_demo kaa_demo_s)
I left the user_main.c file the same:
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include "uart.h"
extern int main(void);
static void main_task(void *pvParameters)
{
(void)pvParameters;
main();
for (;;);
}
void user_init(void)
{
uart_init_new();
UART_SetBaudrate(UART0, 115200);
UART_SetPrintPort(UART0);
portBASE_TYPE error = xTaskCreate(main_task, "main_task", 512, NULL, 2, NULL );
if (error < 0) {
printf("Error creating main_task! Error code: %ld\r\n", error);
}
}
I left eagle.app.v6.ld (except for the modification described in the beginning) and eagle.rom.addr.ld, uart.h, and uart.c the same (obtained from github)
So when I change to the build directory and run this cmake command:
cmake \
-DCMAKE_TOOLCHAIN_FILE=../kaa/toolchains/esp8266.cmake \
-DKAA_PLATFORM=esp8266 \
-DCMAKE_BUILD_TYPE=MinSizeRel \
-DWITH_EXTENSION_CONFIGURATION=OFF \
-DWITH_EXTENSION_EVENT=OFF \
-DWITH_EXTENSION_LOGGING=OFF \
-DWITH_EXTENSION_NOTIFICATION=OFF \
-DWITH_EXTENSION_USER=OFF \
-DWITH_ENCRYPTION=OFF \
-DKAA_MAX_LOG_LEVEL=3 ..
I get this: (esp8266 is my username, kaa-app is the main directory that contains: CMakeLists.txt, build, drivers, etc.)
-- Default SDK location will be used: /opt/Espressif/esp-rtos-sdk
-- Toolchain path: /opt/Espressif/crosstool-NG/builds/xtensa-lx106-elf
-- ESP8266 SDK path: /opt/Espressif/esp-rtos-sdk
==================================
BUILD_TYPE = MinSizeRel
KAA_PLATFORM = esp8266
KAA_MAX_LOG_LEVEL = 3
==================================
BOOTSTRAP ENABLED
PROFILE ENABLED
KAA WILL BE INSTALLED TO /usr/local
-- Could NOT find Doxygen (missing: DOXYGEN_EXECUTABLE)
-- Configuring done
-- Generating done
-- Build files have been written to: /home/esp8266/Documents/kaa-app/build
So then I run make, and I get this error:
libkaa_demo_s.a(kaa_demo2.c.obj):(.text.example_callback+0x8): undefined reference to `time'
libkaa_demo_s.a(kaa_demo2.c.obj):(.text.example_callback+0xc): undefined reference to `difftime'
libkaa_demo_s.a(kaa_demo2.c.obj):(.text.example_callback+0x20): undefined reference to `kaa_logging_add_record'
libkaa_demo_s.a(kaa_demo2.c.obj): In function `example_callback':
kaa_demo2.c:(.text.example_callback+0x35): undefined reference to `time'
kaa_demo2.c:(.text.example_callback+0x42): undefined reference to `difftime'
kaa_demo2.c:(.text.example_callback+0x9e): undefined reference to `kaa_logging_add_record'
libkaa_demo_s.a(kaa_demo2.c.obj):(.text.startup.main+0x10): undefined reference to `srand'
libkaa_demo_s.a(kaa_demo2.c.obj):(.text.startup.main+0x14): undefined reference to `kaa_configuration_manager_set_root_receiver'
libkaa_demo_s.a(kaa_demo2.c.obj):(.text.startup.main+0x18): undefined reference to `kaa_configuration_manager_get_configuration'
libkaa_demo_s.a(kaa_demo2.c.obj):(.text.startup.main+0x1c): undefined reference to `ext_unlimited_log_storage_create'
libkaa_demo_s.a(kaa_demo2.c.obj):(.text.startup.main+0x20): undefined reference to `ext_log_upload_strategy_create'
libkaa_demo_s.a(kaa_demo2.c.obj):(.text.startup.main+0x24): undefined reference to `ext_log_upload_strategy_set_upload_timeout'
libkaa_demo_s.a(kaa_demo2.c.obj):(.text.startup.main+0x28): undefined reference to `kaa_logging_init'
libkaa_demo_s.a(kaa_demo2.c.obj):(.text.startup.main+0x3a): undefined reference to `time'
libkaa_demo_s.a(kaa_demo2.c.obj):(.text.startup.main+0x40): undefined reference to `srand'
libkaa_demo_s.a(kaa_demo2.c.obj): In function `main':
kaa_demo2.c:(.text.startup.main+0x69): undefined reference to `kaa_configuration_manager_set_root_receiver'
kaa_demo2.c:(.text.startup.main+0x7b): undefined reference to `kaa_configuration_manager_get_configuration'
kaa_demo2.c:(.text.startup.main+0xa5): undefined reference to `ext_unlimited_log_storage_create'
kaa_demo2.c:(.text.startup.main+0xb8): undefined reference to `ext_log_upload_strategy_create'
kaa_demo2.c:(.text.startup.main+0xc5): undefined reference to `ext_log_upload_strategy_set_upload_timeout'
kaa_demo2.c:(.text.startup.main+0xe6): undefined reference to `kaa_logging_init'
collect2: error: ld returned 1 exit status
CMakeFiles/kaa_demo.dir/build.make:120: recipe for target 'kaa_demo' failed
make[2]: *** [kaa_demo] Error 1
CMakeFiles/Makefile2:107: recipe for target 'CMakeFiles/kaa_demo.dir/all' failed
make[1]: *** [CMakeFiles/kaa_demo.dir/all] Error 2
Makefile:127: recipe for target 'all' failed
make: *** [all] Error 2
So it seems that when it tries linking the files it cannot find the new headers I included:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <time.h>
#include <kaa/kaa.h>
#include <kaa/platform/kaa_client.h>
#include <kaa/kaa_error.h>
#include <extensions/configuration/kaa_configuration_manager.h>
#include <extensions/logging/kaa_logging.h>
#include <kaa/gen/kaa_logging_gen.h>
#include <kaa/platform/kaa_client.h>
#include <kaa/utilities/kaa_log.h>
#include <kaa/platform-impl/common/ext_log_upload_strategies.h>
However, the target directory that is included in CMakeLists.txt contains the header files that I need (for the C headers):
${ESP_RTOS_SDK}/extra_include
So I really do not know what I need to add or modify, I am completely stuck. Any help would be much appreciated! Thanks!
For data_collection you have to set -DWITH_EXTENSION_LOGGING=ON .
For esp8266 you have a different "time.h" library /kaa/src/kaa/platform-impl/esp8266/platform that doesn't support the function time(NULL), but support
kaa_time_t kaa_esp8266_get_time(void);

Calling OpenSSL's PEM_write_PUBKEY || PEM_write_PrivateKey API makes the program to exit abruptly with message "no OPENSSL_Applink"

I am trying to use EVP_* APIs from OpenSSL, but I have encountered a weird strange behavior when trying to dump the Public/Private key from the EVP_PKEY struct.
Issue:: After populating the EVP_PKEY struct, on calling PEM_write_PUBKEY API (see TRIAL1), the program exits. The same happens on calling of PEM_write_PrivateKey API (see TRIAL2).
Output: I am left with a temp.pem 0 bytes file and a message on cmd prompt saying OPENSSL_Uplink(5D8C7000,08): no OPENSSL_Applink
#define TRIAL1
void InitOpenSSLLib(void)
{
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
}
int main(int argc, char** argv)
{
EVP_PKEY_CTX* ctx = NULL;
EVP_PKEY* pKeyPair = EVP_PKEY_new();
BIO *mem = BIO_new(BIO_s_mem());
FILE* fp = fopen("temp.pem", "wb");
InitOpenSSLLib();
ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, 0);
EVP_PKEY_keygen_init(ctx);
EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 2048);
EVP_PKEY_keygen(ctx, &pKeyPair);
// Succeeds till here... all of the above called APIs return value greater than 0
#ifdef TRIAL1
// Program exits even before printing any error
if (PEM_write_PUBKEY(fp, pKeyPair) <= 0)
printf("PEM_write_PUBKEY failed\n");
#elif TRIAL2
// same behavior with this API too
PEM_write_PrivateKey(fp, pKeyPair, NULL, NULL, 0, 0, NULL);
#endif
// Tried this too.... but the control never reaches here
fflush(fp);
// Tried this too... most of the mem struct members are NULL.
EVP_PKEY_print_private(mem, pKeyPair, 0, 0);
//.... Cleanup codes
fclose(fp);
BIO_free_all(mem);
return 0;
}
Any pointers? Am I doing anything wrong here? Is there any other way to dump private/public key or the pair PEM format to a file?
I am using VC++ 2015. Also, on hitting Ctrl+F5, the prompt shows the message:: OPENSSL_Uplink(5D8C7000,08): no OPENSSL_Applink
Answering my own question for future devs
The key question here is the error (read, message) thrown by OpenSSL, i.e., no OPENSSL_Applink.
As documented here,
As per 0.9.8 the above limitation is eliminated for .DLLs. OpenSSL
.DLLs compiled with some specific run-time option [we insist on the
default /MD] can be deployed with application compiled with different
option or even different compiler. But there is a catch! Instead of
re-compiling OpenSSL toolkit, as you would have to with prior
versions, you have to compile small C snippet with compiler and/or
options of your choice. The snippet gets installed as
/include/openssl/applink.c and should be either added to
your application project or simply #include-d in one [and only one] of
your application source files. Failure to link this shim module into
your application manifests itself as fatal "no OPENSSL_Applink"
run-time error. An explicit reminder is due that in this situation
[mixing compiler options] it is as important to add CRYPTO_malloc_init
prior first call to OpenSSL.
you should check your compiling options for /MD (assuming you know the Code Generation options under VC++ Project Properties).
I did the same, but still my issue didn't get resolved. The answer to this is the second paragraph to the link given [https://www.openssl.org/docs/faq.html#PROG2], where it instructs us to include <install-root>/include/openssl/applink.c
Where to get this applink.c file if not found in openssl's
dir?
In my case, I installed OpenSSL using exe binary and I couldn't find this specific applink.c file anywhere in my <install-root>, so I started looking and found it here on GitHub.
All you have to do is, include this file as source file in your project or better save it in openssl install-root dir, so that you can include it from the same location for other projects too.
// applink.c from https://github.com/openssl/openssl/blob/master/ms/applink.c
/*
* Copyright 2004-2016 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the OpenSSL license (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
* in the file LICENSE in the source distribution or at
* https://www.openssl.org/source/license.html
*/
#define APPLINK_STDIN 1
#define APPLINK_STDOUT 2
#define APPLINK_STDERR 3
#define APPLINK_FPRINTF 4
#define APPLINK_FGETS 5
#define APPLINK_FREAD 6
#define APPLINK_FWRITE 7
#define APPLINK_FSETMOD 8
#define APPLINK_FEOF 9
#define APPLINK_FCLOSE 10 /* should not be used */
#define APPLINK_FOPEN 11 /* solely for completeness */
#define APPLINK_FSEEK 12
#define APPLINK_FTELL 13
#define APPLINK_FFLUSH 14
#define APPLINK_FERROR 15
#define APPLINK_CLEARERR 16
#define APPLINK_FILENO 17 /* to be used with below */
#define APPLINK_OPEN 18 /* formally can't be used, as flags can vary */
#define APPLINK_READ 19
#define APPLINK_WRITE 20
#define APPLINK_LSEEK 21
#define APPLINK_CLOSE 22
#define APPLINK_MAX 22 /* always same as last macro */
#ifndef APPMACROS_ONLY
# include <stdio.h>
# include <io.h>
# include <fcntl.h>
static void *app_stdin(void)
{
return stdin;
}
static void *app_stdout(void)
{
return stdout;
}
static void *app_stderr(void)
{
return stderr;
}
static int app_feof(FILE *fp)
{
return feof(fp);
}
static int app_ferror(FILE *fp)
{
return ferror(fp);
}
static void app_clearerr(FILE *fp)
{
clearerr(fp);
}
static int app_fileno(FILE *fp)
{
return _fileno(fp);
}
static int app_fsetmod(FILE *fp, char mod)
{
return _setmode(_fileno(fp), mod == 'b' ? _O_BINARY : _O_TEXT);
}
#ifdef __cplusplus
extern "C" {
#endif
__declspec(dllexport)
void **
# if defined(__BORLANDC__)
/*
* __stdcall appears to be the only way to get the name
* decoration right with Borland C. Otherwise it works
* purely incidentally, as we pass no parameters.
*/
__stdcall
# else
__cdecl
# endif
OPENSSL_Applink(void)
{
static int once = 1;
static void *OPENSSL_ApplinkTable[APPLINK_MAX + 1] =
{ (void *)APPLINK_MAX };
if (once) {
OPENSSL_ApplinkTable[APPLINK_STDIN] = app_stdin;
OPENSSL_ApplinkTable[APPLINK_STDOUT] = app_stdout;
OPENSSL_ApplinkTable[APPLINK_STDERR] = app_stderr;
OPENSSL_ApplinkTable[APPLINK_FPRINTF] = fprintf;
OPENSSL_ApplinkTable[APPLINK_FGETS] = fgets;
OPENSSL_ApplinkTable[APPLINK_FREAD] = fread;
OPENSSL_ApplinkTable[APPLINK_FWRITE] = fwrite;
OPENSSL_ApplinkTable[APPLINK_FSETMOD] = app_fsetmod;
OPENSSL_ApplinkTable[APPLINK_FEOF] = app_feof;
OPENSSL_ApplinkTable[APPLINK_FCLOSE] = fclose;
OPENSSL_ApplinkTable[APPLINK_FOPEN] = fopen;
OPENSSL_ApplinkTable[APPLINK_FSEEK] = fseek;
OPENSSL_ApplinkTable[APPLINK_FTELL] = ftell;
OPENSSL_ApplinkTable[APPLINK_FFLUSH] = fflush;
OPENSSL_ApplinkTable[APPLINK_FERROR] = app_ferror;
OPENSSL_ApplinkTable[APPLINK_CLEARERR] = app_clearerr;
OPENSSL_ApplinkTable[APPLINK_FILENO] = app_fileno;
OPENSSL_ApplinkTable[APPLINK_OPEN] = _open;
OPENSSL_ApplinkTable[APPLINK_READ] = _read;
OPENSSL_ApplinkTable[APPLINK_WRITE] = _write;
OPENSSL_ApplinkTable[APPLINK_LSEEK] = _lseek;
OPENSSL_ApplinkTable[APPLINK_CLOSE] = _close;
once = 0;
}
return OPENSSL_ApplinkTable;
}
#ifdef __cplusplus
}
#endif
#endif
That's it, this will eliminate the strange behavior of program exit.

Call Racket function from C

I have a Racket module hw.rkt:
#lang racket/base
(provide hw)
(define (hw) (displayln "Hello, world!"))
I would like to write a C program that embeds the Racket runtime and applies the procedure (hw).
There is example code here which demonstrates how to embed the Racket runtime and apply a procedure that is in racket/base, or to read and evaluate an S-expression, but I've had no luck modifying this code to allow access to the (hw) procedure.
This page seems to say that it is possible to do what I want to do by first compiling hw.rkt to hw.c using raco ctool --c-mods, and this works just fine when I try it, but I still can't actually access the (hw) procedure.
If someone could post a complete example program, or simply describe which C functions to use, I would be very appreciative. From there I can figure out the rest.
Editing to provide examples of things I have tried.
I modified the example program to get rid of the "evaluate command line arguments" bit and skip straight to the REPL so that I could experiment. Thus (with "hw.c" the result of running raco ctool --c-mods hw.c ++libs racket/base hw.rkt):
#define MZ_PRECISE_GC
#include "scheme.h"
#include "hw.c"
static int run(Scheme_Env *e, int argc, char *argv[])
{
Scheme_Object *curout = NULL, *v = NULL, *a[2] = {NULL, NULL};
Scheme_Config *config = NULL;
int i;
mz_jmp_buf * volatile save = NULL, fresh;
MZ_GC_DECL_REG(8);
MZ_GC_VAR_IN_REG(0, e);
MZ_GC_VAR_IN_REG(1, curout);
MZ_GC_VAR_IN_REG(2, save);
MZ_GC_VAR_IN_REG(3, config);
MZ_GC_VAR_IN_REG(4, v);
MZ_GC_ARRAY_VAR_IN_REG(5, a, 2);
MZ_GC_REG();
declare_modules(e);
v = scheme_intern_symbol("racket/base");
scheme_namespace_require(v);
config = scheme_current_config();
curout = scheme_get_param(config, MZCONFIG_OUTPUT_PORT);
save = scheme_current_thread->error_buf;
scheme_current_thread->error_buf = &fresh;
if (scheme_setjmp(scheme_error_buf)) {
scheme_current_thread->error_buf = save;
return -1; /* There was an error */
} else {
/* read-eval-print loop, uses initial Scheme_Env: */
a[0] = scheme_intern_symbol("racket/base");
a[1] = scheme_intern_symbol("read-eval-print-loop");
v = scheme_dynamic_require(2, a);
scheme_apply(v, 0, NULL);
scheme_current_thread->error_buf = save;
}
MZ_GC_UNREG();
return 0;
}
int main(int argc, char *argv[])
{
return scheme_main_setup(1, run, argc, argv);
}
Things that don't work (and their error messages):
Calling (hw) from the REPL
hw: undefined:
cannot reference undefined identifier
context...:
/usr/local/share/racket/collects/racket/private/misc.rkt:87:7
((dynamic-require 'hw 'hw))
standard-module-name-resolver: collection not found
for module path: hw
collection: "hw"
in collection directories:
context...:
show-collection-err
standard-module-name-resolver
/usr/local/share/racket/collects/racket/private/misc.rkt:87:7
((dynamic-require "hw.rkt" 'hw))
standard-module-name-resolver: collection not found
for module path: racket/base/lang/reader
collection: "racket/base/lang"
in collection directories:
context...:
show-collection-err
standard-module-name-resolver
standard-module-name-resolver
/usr/local/share/racket/collects/racket/private/misc.rkt:87:7
Editing the example code
v = scheme_intern_symbol("racket/base");
scheme_namespace_require(v);
v = scheme_intern_symbol("hw");
scheme_namespace_require(v);
Error:
standard-module-name-resolver: collection not found
for module path: hw
collection: "hw"
in collection directories:
context...:
show-collection-err
standard-module-name-resolver
SIGSEGV MAPERR sicode 1 fault on addr 0xd0
Aborted
(The segfault was probably because I didn't check the value of 'v' before trying to scheme_namespace_require it.)
Editing the example code mk. 2
v = scheme_intern_symbol("racket/base");
scheme_namespace_require(v);
v = scheme_intern_symbol("hw.rkt");
scheme_namespace_require(v);
Error:
hw.rkt: bad module path
in: hw.rkt
context...:
standard-module-name-resolver
SIGSEGV MAPERR sicode 1 fault on addr 0xd0
Aborted
(re: segfault: as above)
Editing the example code mk. 3
v = scheme_intern_symbol("racket/base");
scheme_namespace_require(v);
v = scheme_intern_symbol("./hw.rkt");
scheme_namespace_require(v);
(as above)
Editing the example code mk. 4
/* read-eval-print-loop, uses initial Scheme_Env: */
a[0] = scheme_intern_symbol("hw");
a[1] = scheme_intern_symbol("hw");
v = scheme_dynamic_require(2, a);
(as mk. 1, save the segfault)
Editing the example code mk. 5
/* read-eval-print loop, uses initial Scheme_Env: */
a[0] = scheme_intern_symbol("hw");
a[1] = scheme_eval(a[0], e);
scheme_apply(a[1], 0, NULL);
Error:
hw: undefined;
cannot reference undefined identifier
Answered by Matthew Flatt here. When using dynamic-require, I needed to quote the name of the module twice, not once. Thanks to Dr. Flatt for their assistance.

C library to parse approximate dates

I'm looking for a plain C counterpart for date.js date.parse().
That is, something that understands "week ago" or "yesterday" as input. English-only is OK.
Note: a library should not be licensed under GPL, so Git's date.c or parser for GNU date -d wouldn't do. BTW, if you wonder why wouldn't I just sit down and code this, go and look at the source of mentioned libraries...
The following solution is not exactly what you've asked for but I hope that despite not being a plain C answer it will cover your needs. Reinventing the wheel isn't a way to go so let's use date.js in C by running it with SpiderMonkey, the Mozilla JavaScript engine.
Here's how I did it. I've begun with downloading date.js and translating it into a const char* named code defined in date.js.h.
( \
echo 'const char *code =' ; \
curl https://datejs.googlecode.com/files/date.js | \
sed -e 's/\\/\\\\/g; s/"/\\"/g; s/^/"/; s/\r\?$/\\n"/'; \
echo ';' \
) > date.js.h
Then I took the JSAPI's Hello, World! as a starting point.
#include "jsapi.h"
#include "date.js.h"
static JSClass global_class = { "global", JSCLASS_GLOBAL_FLAGS,
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
JSCLASS_NO_OPTIONAL_MEMBERS };
void reportError(JSContext *cx, const char *message, JSErrorReport *report) {
fprintf(stderr, "%s:%u:%s\n",
report->filename ? report->filename : "<no filename>",
(unsigned int) report->lineno, message);
}
int main(int argc, const char *argv[]) {
JSRuntime *rt;
JSContext *cx;
JSObject *global;
rt = JS_NewRuntime(8L * 1024L * 1024L);
if (rt == NULL) return 1;
cx = JS_NewContext(rt, 8192);
if (cx == NULL) return 1;
JS_SetOptions(cx, JSOPTION_VAROBJFIX | JSOPTION_JIT | JSOPTION_METHODJIT);
JS_SetVersion(cx, JSVERSION_LATEST);
JS_SetErrorReporter(cx, reportError);
global = JS_NewCompartmentAndGlobalObject(cx, &global_class, NULL);
if (global == NULL) return 1;
if (!JS_InitStandardClasses(cx, global)) return 1;
/* Here's where the interesting stuff is starting to take place.
* Begin by evaluating sources of date.js */
jsval out;
if (!JS_EvaluateScript(cx, global, code, strlen(code), "code", 1, &out))
return 1;
/* Now create a call to Date.parse and evaluate it. The return value should
* be a timestamp of a given date. If no errors occur convert the timestamp
* to a double and print it. */
const int buflen = 1024;
char parse[buflen + 1];
snprintf(parse, buflen, "Date.parse(\"%s\").getTime();", argv[1]);
if (!JS_EvaluateScript(cx, global, parse, strlen(parse), "parse", 1, &out))
return 1;
double val;
JS_ValueToNumber(cx, out, &val);
printf("%i\n", (int) (val / 1000));
/* Finally, clean everything up. */
JS_DestroyContext(cx);
JS_DestroyRuntime(rt);
JS_ShutDown();
return 0;
}
Here's how it works in practice.
$ time ./parse "week ago"
1331938800
0.01user 0.00system 0:00.02elapsed 92%CPU (0avgtext+0avgdata 6168maxresident)k
0inputs+0outputs (0major+1651minor)pagefaults 0swaps
$ time ./parse yesterday
1332457200
0.01user 0.00system 0:00.02elapsed 84%CPU (0avgtext+0avgdata 6168maxresident)k
0inputs+0outputs (0major+1653minor)pagefaults 0swaps
As you can see it's quite fast and you could significantly increase its performance by reusing the initially created context for all subsequent calls to Date.parse.
Speaking of licensing issues, date.js is available under terms of MIT and SpiderMonkey is available under MPL 1.1, GPL 2.0 or LGPL 2.1. Linking it dynamically satisfies the non-GPL requirement.
TL;DR: git clone https://gist.github.com/2180739.git && cd 2180739 && make && ./parse yesterday
Date formatting is pretty gruesome, there is no easy way of doing this.
You need to take into account day and month names of the selected language, then make sure you receive the data in a specific format: "dd/mm/yyyy", "day mon, yyyy" and so on.
Also, as you say, you need to interpret some specific keywords, thus you need access to the current timestamp (date, time, or date&time) on the machine.
Hoping you need that for Linux, I think you can start reading from here: Convert textual time and date information back
Or you can simply tokenize your input string, by splitting it using some predefined separators (comma, slash, minus, space etc.), trimming the spaces of the tokens, and then implement an automata to handle the list of tokens and build your date variable.
Make sure you add some restrictions for the input date format, and throw errors for wrong or incompatible tokens.

Pretty-printing a binary tree in C (and other imperative languages)

(First-time poster and rather new in programming, so be patient, please!)
I'm interested in both an efficient general algorithm for printing formatted binary trees (in a CLI environment) and a C implementation. Here is some code that I wrote myself for fun (this is a much simplified version of the original and part of a larger program supporting many BST operations, but it should compile just fine):
#include <stdbool.h> // C99, boolean type support
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define DATATYPE_IS_DOUBLE
#define NDEBUG // disable assertions
#include <assert.h>
#define WCHARBUF_LINES 20 // def: 20
#define WCHARBUF_COLMS 800 // def: 80 (using a huge number, like 500, is a good idea,
// in order to prevent a buffer overflow :)
#define RECOMMENDED_CONS_WIDTH 150
#define RECOMMENDED_CONS_WIDTHQ "150" // use the same value, quoted
/* Preprocessor directives depending on DATATYPE_IS_* : */
#if defined DATATYPE_IS_INT || defined DATATYPE_IS_LONG
#define DTYPE long int
#define DTYPE_STRING "INTEGER"
#define DTYPE_PRINTF "%*.*ld"
#undef DATATYPE_IS_CHAR
#elif defined DATATYPE_IS_FLOAT
#define DTYPE float
#define DTYPE_STRING "FLOAT"
#define DTYPE_PRINTF "%*.*f"
#undef DATATYPE_IS_CHAR
#elif defined DATATYPE_IS_DOUBLE
#define DTYPE double
#define DTYPE_STRING "DOUBLE"
#define DTYPE_PRINTF "%*.*lf"
#undef DATATYPE_IS_CHAR
#elif defined DATATYPE_IS_CHAR
#define DTYPE char
#define DTYPE_STRING "CHARACTER"
#define DTYPE_PRINTF "%*.*c" /* using the "precision" sub-specifier ( .* ) with a */
/* character will produce a harmless compiler warning */
#else
#error "DATATYPE_IS_* preprocessor directive undefined!"
#endif
typedef struct node_struct {
DTYPE data;
struct node_struct *left;
struct node_struct *right;
/* int height; // useful for AVL trees */
} node;
typedef struct {
node *root;
bool IsAVL; // useful for AVL trees
long size;
} tree;
static inline
DTYPE get_largest(node *n){
if (n == NULL)
return (DTYPE)0;
for(; n->right != NULL; n=n->right);
return n->data;
}
static
int subtreeheight(node *ST){
if (ST == NULL)
return -1;
int height_left = subtreeheight(ST->left);
int height_right = subtreeheight(ST->right);
return (height_left > height_right) ? (height_left + 1) : (height_right + 1);
}
void prettyprint_tree(tree *T){
if (T == NULL) // if T empty, abort
return;
#ifndef DATATYPE_IS_CHAR /* then DTYPE is a numeric type */
/* compute spaces, find width: */
int width, i, j;
DTYPE max = get_largest(T->root);
width = (max < 10) ? 1 :
(max < 100) ? 2 :
(max < 1000) ? 3 :
(max < 10000) ? 4 :
(max < 100000) ? 5 :
(max < 1000000) ? 6 :
(max < 10000000) ? 7 :
(max < 100000000) ? 8 :
(max < 1000000000) ? 9 : 10;
assert (max < 10000000000);
width += 2; // needed for prettier results
#if defined DATATYPE_IS_FLOAT || defined DATATYPE_IS_DOUBLE
width += 2; // because of the decimals! (1 decimal is printed by default...)
#endif // float or double
int spacesafter = width / 2;
int spacesbefore = spacesafter + 1;
//int spacesbefore = ceil(width / 2.0);
#else /* character input */
int i, j, width = 3, spacesbefore = 2, spacesafter = 1;
#endif // #ifndef DATATYPE_IS_CHAR
/* start wchar_t printing, using a 2D character array with swprintf() : */
struct columninfo{ // auxiliary structure
bool visited;
int col;
};
wchar_t wcharbuf[WCHARBUF_LINES][WCHARBUF_COLMS];
int line=0;
struct columninfo eachline[WCHARBUF_LINES];
for (i=0; i<WCHARBUF_LINES; ++i){ // initialization
for (j=0; j<WCHARBUF_COLMS; ++j)
wcharbuf[i][j] = (wchar_t)' ';
eachline[i].visited = false;
eachline[i].col = 0;
}
int height = subtreeheight(T->root);
void recur_swprintf(node *ST, int cur_line, const wchar_t *nullstr){ // nested function,
// GCC extension!
float offset = width * pow(2, height - cur_line);
++cur_line;
if (eachline[cur_line].visited == false) {
eachline[cur_line].col = (int) (offset / 2);
eachline[cur_line].visited = true;
}
else{
eachline[cur_line].col += (int) offset;
if (eachline[cur_line].col + width > WCHARBUF_COLMS)
swprintf(wcharbuf[cur_line], L" BUFFER OVERFLOW DETECTED! ");
}
if (ST == NULL){
swprintf(wcharbuf[cur_line] + eachline[cur_line].col, L"%*.*s", 0, width, nullstr);
if (cur_line <= height){
/* use spaces instead of the nullstr for all the "children" of a NULL node */
recur_swprintf(NULL, cur_line, L" ");
recur_swprintf(NULL, cur_line, L" ");
}
else
return;
}
else{
recur_swprintf(ST->left, cur_line, nullstr);
recur_swprintf(ST->right, cur_line, nullstr);
swprintf(wcharbuf[cur_line] + eachline[cur_line].col - 1, L"("DTYPE_PRINTF"",
spacesbefore, 1, ST->data);
//swprintf(wcharbuf[cur_line] + eachline[cur_line].col + spacesafter + 1, L")");
swprintf(wcharbuf[cur_line] + eachline[cur_line].col + spacesafter + 2, L")");
}
}
void call_recur(tree *tr){ // nested function, GCC extension! (wraps recur_swprintf())
recur_swprintf(tr->root, -1, L"NULL");
}
call_recur(T);
/* Omit empty columns: */
int omit_cols(void){ // nested function, GCC extension!
int col;
for (col=0; col<RECOMMENDED_CONS_WIDTH; ++col)
for (line=0; line <= height+1; ++line)
if (wcharbuf[line][col] != ' ' && wcharbuf[line][col] != '\0')
return col;
return 0;
}
/* Use fputwc to transfer the character array to the screen: */
j = omit_cols() - 2;
j = (j < 0) ? 0 : j;
for (line=0; line <= height+1; ++line){ // assumes RECOMMENDED_CONS_WIDTH console window!
fputwc('\n', stdout); // optional blanc line
for (i=j; i<j+RECOMMENDED_CONS_WIDTH && i<WCHARBUF_COLMS; ++i)
fputwc(wcharbuf[line][i], stdout);
fputwc('\n', stdout);
}
}
(also uploaded to a pastebin service, in order to preserve syntax highlighting)
It works quite well, although the automatic width setting could be better. The preprocessor magic is a bit silly (or even ugly) and not really related to the algorithm, but it allows using various data types in the tree nodes (I saw it as a chance to experiment a bit with the preprocessor - remember, I am a newbie!).
The main program is supposed to call
system("mode con:cols="RECOMMENDED_CONS_WIDTHQ" lines=2000");
before calling prettyprint_tree(), when running inside cmd.exe .
Sample output:
(106.0)
(102.0) (109.0)
(101.5) NULL (107.0) (115.0)
NULL NULL (106.1) NULL (113.0) NULL
NULL NULL NULL NULL
Ideally, the output would be like this (the reason I'm using the wprintf() family of functions is being able to print Unicode characters anyway):
(107.0)
┌─────┴─────┐
(106.1) NULL
┌───┴───┐
NULL NULL
So, my questions:
What do you think about this code? (Coding style suggestions are also very welcome!)
Can it be extended in an elegant way in order to include the line-drawing characters? (Unfortunately, I don't think so.)
Any other algorithms in C or other imperative languages (or imperative pseudo-code)?
Somewhat unrelated: What's your opinion about nested functions (non-portable GNU extension)? I think it's an elegant way to write recursive parts of a function without having to provide all the local variables as arguments (and also useful as an implementation-hiding technique), but it could be my Pascal past :-) I'm interested in the opinion of more experienced coders.
Thank you in advance for your responses!
PS. The question is not a duplicate of this one.
edit:
Jonathan Leffler wrote an excellent answer that will most probably become the "accepted answer" after a few days (unless someone posts something equally awesome!). I decided to respond here instead of commenting because of the space constraints.
The code above is actually part of a larger "homework" project (implementing BST operations in a shared library + a CLI app using that library). However, the "prettyprint" function was not part of the requirements; just something I decided to add myself.
I also added a "convert to AVL without rotations" function, that used "arraystr" as an intermediate representation ;-) I forgot that it wasn't used here. I've edited the code to remove it. Also, the bool IsAVL struct member is anything but unused; just not used in this particular function. I had to copy/paste code from various files and make a lot of changes in order to present the code cited above. That's a problem that I don't know how to solve. I would gladly post the whole program, but it is too large and commented in my mother-tongue (not in English!).
The whole project was about 1600 LOC (including comments) with multiple build targets (debug/release/static-linking) and it compiled cleanly with -Wall and -Wextra enabled. Assertions and debug messages were enabled/disabled automatically depending on the build target. Also I thought that function prototypes weren't needed for nested functions, after all nested functions do not implement any external interface by definition - GCC certainly didn't complain here. I don't know why there are so many warnings on OSX :(
I'm using GCC 4.4.1 on Windows 7.
Despite writing and testing this program on Windows, I am actually a Linux user... Still, I can't stand vim and I use nano (inside GNU screen) or gedit instead (shoot me)! In any case, I prefer the K&R brace style :)
Portability doesn't really matter, for Linux users GCC is pretty much de facto... The fact that it also works well under Windows is a nice bonus.
I'm not using a VCS, perhaps I should. I want to try, but all of them seem too complex for my needs and I don't know how to choose one :-)
You are definitely right about checking for depth overflow, thankfully it is very easy to add.
Thanks for the L' ' advice!
I find your suggestion (encapsulating "the whole of the drawing code so that the screen image and related information is in a single structure") extremely interesting... but I don't really understand what you mean as "encapsulation". Could you, please, provide 3 or 4 lines of (pseudo)code showing a possible function declaration and/or a possible function call?
This is my first "large-ish" (and non-trivial) program and I'm really thankful for your advice.
edit #2:
Here is an implementation of the "quick and dirty" method mentioned here.
(edit #3: I decided to split it to a separate answer, since it is a valid answer to the OP.)
Many responses mentioned Graphviz. I already knew about it (many Linux apps are linked against it) but I thought it would be overkill for a 10KB CLI executable. However, I'll keep it in mind for the future. It seems great.
You need to decide on whether your code needs to be portable. If you might ever need to use a compiler other than GCC, the nested functions are lethal to your portability goal. I would not use them - but my portability goals may not be the same as yours.
Your code is missing <wchar.h>; it compiles fairly cleanly without it - GCC complained about missing prototypes for your non-static functions and for swprintf() and fputwc()), but adding <wchar.h> generates a lot of serious warnings related to swprintf(); they are actually diagnosing a bug.
gcc -O -I/Users/jleffler/inc -std=c99 -Wall -Wextra -Wmissing-prototypes \
-Wstrict-prototypes -Wold-style-definition -c tree.c
tree.c:88:6: warning: no previous prototype for ‘prettyprint_tree’
tree.c: In function ‘prettyprint_tree’:
tree.c:143:10: warning: no previous prototype for ‘recur_swprintf’
tree.c: In function ‘recur_swprintf’:
tree.c:156:17: warning: passing argument 2 of ‘swprintf’ makes integer from pointer without a cast
/usr/include/wchar.h:135:5: note: expected ‘size_t’ but argument is of type ‘int *’
tree.c:156:17: error: too few arguments to function ‘swprintf’
/usr/include/wchar.h:135:5: note: declared here
tree.c:160:13: warning: passing argument 2 of ‘swprintf’ makes integer from pointer without a cast
/usr/include/wchar.h:135:5: note: expected ‘size_t’ but argument is of type ‘int *’
tree.c:174:22: warning: passing argument 2 of ‘swprintf’ makes integer from pointer without a cast
/usr/include/wchar.h:135:5: note: expected ‘size_t’ but argument is of type ‘int *’
tree.c:174:22: warning: passing argument 3 of ‘swprintf’ makes pointer from integer without a cast
/usr/include/wchar.h:135:5: note: expected ‘const wchar_t * restrict’ but argument is of type ‘int’
tree.c:177:13: warning: passing argument 2 of ‘swprintf’ makes integer from pointer without a cast
/usr/include/wchar.h:135:5: note: expected ‘size_t’ but argument is of type ‘int *’
tree.c:177:13: error: too few arguments to function ‘swprintf’
/usr/include/wchar.h:135:5: note: declared here
tree.c: In function ‘prettyprint_tree’:
tree.c:181:10: warning: no previous prototype for ‘call_recur’
tree.c:188:9: warning: no previous prototype for ‘omit_cols’
(This is GCC 4.5.2 on MacOS X 10.6.5.)
Do look up the interface to swprintf(); it is more like snprintf() than sprintf() (which is A Good Thing™!).
The overall idea is interesting. I suggest choosing one representation when submitting your code for analysis, and cleaning up anything that is not relevant to the code analysis. For example, the arraystr type is defined but unused - you don't want to let people like me get cheap shots at your code. Similarly with the unused structure members; don't even leave them as comments, even if you might want to keep them in the code in your VCS (though why?). You are using a version control system (VCS), aren't you? And that's a rhetorical question - if you aren't using a VCS, start using one now, before you lose something you value.
Design-wise, you want to avoid doing things like requiring the main program to run an obscure system() command - your code should take care of such issues (maybe with an initializer function, and perhaps a finalizer function to undo the changes made to the terminal settings).
One more reason not to like nested functions: I can't work out how to get a declaration of the function in place. What seemed like plausible alternatives did not work - but I didn't go and read the GCC manual on them.
You check for column-width overflow; you do not check for depth overflow. Your code will crash and burn if you create a tree that is too deep.
Minor nit: you can tell people who do not use 'vi' or 'vim' to edit - they don't put the opening brace of a function in column 1. In 'vi', the opening brace in column 1 gives you an easy way to the start of a function from anywhere inside it ('[[' to jump backwards; ']]' to jump to the start of the next function).
Don't disable assertions.
Do include a main program and the relevant test data - it means people can test your code, instead of just compiling it.
Use wide-character constants instead of casts:
wcharbuf[i][j] = (wchar_t)' ';
wcharbuf[i][j] = L' ';
Your code creates a big screen image (20 lines x 800 columns in the code) and fills in the data to be printed. That's a reasonable way to do it. With care, you could arrange to handle the line-drawing characters. However, I think you would need to rethink the core drawing algorithms. You would probably want to encapsulate the whole of the drawing code so that the screen image and related information is in a single structure, which can be passed by reference (pointer) to functions. You'd have a set of functions to draw various bits at positions your tree-searching code designates. You would have a function to draw the data value at an appropriate position; you would have a function to draw lines at appropriate positions. You would probably not have nested functions - it is, to my eyes, far harder to read the code when there's a function nested inside another. Making functions static is good; make the nested functions into static (non-nested) functions. Give them the context they need - hence the encapsulation of the screen image.
Overall a good start; lots of good ideas. Lots still to do.
Request for information on encapsulation...
You could use a structure such as:
typedef struct columninfo Colinfo;
typedef struct Image
{
wchar_t image[WCHARBUF_LINES][WCHARBUF_COLUMNS];
Colinfo eachline[WCHARBUF_LINES];
} Image;
Image image;
You might find it convenient and/or sensible to add some extra members; that would show up during the implementation. You might then create a function:
void format_node(Image *image, int line, int column, DTYPE value)
{
...
}
You could also make some of the constants, such as spacesafter into enum values:
enum { spacesafter = 2 };
These can then be used by any of the functions.
Coding style: The prettyprint_tree() function juggles too much computation and data to be comfortable to read. Initialization and printing of the image buffer can for example be placed in separate functions and the width computation also. I am sure you can write a formula with log to replace the
width = (max < 10) ? 1 :
(max < 100) ? 2 :
(max < 1000) ? 3 :
...
computation.
I am not used to reading nested functions and C, which makes it much harder for me to scan your code. Unless you don't share your code with others or have ideological reasons for tying the code to GCC, I wouldn't use those extensions.
Algorithm: For a quick and dirty pretty-printer, written in C, I would never use your style of layout. In comparison to your algorithm, it is a no-brainer to write an in-order traversal to print
a
/ \
b c
as
c
a
b
and I don't mind having to tilt my head. For anything prettier than that I would much rather emit
digraph g { a -> b; a -> c; }
and leave it to dot to do the formatting.
This code should work its from:http://www.ihas1337code.com/2010/09/how-to-pretty-print-binary-tree.html
#include <fstream>
#include <iostream>
#include <deque>
#include <iomanip>
#include <sstream>
#include <string>
#include <cmath>
using namespace std;
struct BinaryTree {
BinaryTree *left, *right;
int data;
BinaryTree(int val) : left(NULL), right(NULL), data(val) { }
};
// Find the maximum height of the binary tree
int maxHeight(BinaryTree *p) {
if (!p) return 0;
int leftHeight = maxHeight(p->left);
int rightHeight = maxHeight(p->right);
return (leftHeight > rightHeight) ? leftHeight + 1: rightHeight + 1;
}
// Convert an integer value to string
string intToString(int val) {
ostringstream ss;
ss << val;
return ss.str();
}
// Print the arm branches (eg, / \ ) on a line
void printBranches(int branchLen, int nodeSpaceLen, int startLen, int nodesInThisLevel, const deque<BinaryTree*>& nodesQueue, ostream& out) {
deque<BinaryTree*>::const_iterator iter = nodesQueue.begin();
for (int i = 0; i < nodesInThisLevel / 2; i++) {
out << ((i == 0) ? setw(startLen-1) : setw(nodeSpaceLen-2)) << "" << ((*iter++) ? "/" : " ");
out << setw(2*branchLen+2) << "" << ((*iter++) ? "\\" : " ");
}
out << endl;
}
// Print the branches and node (eg, ___10___ )
void printNodes(int branchLen, int nodeSpaceLen, int startLen, int nodesInThisLevel, const deque<BinaryTree*>& nodesQueue, ostream& out) {
deque<BinaryTree*>::const_iterator iter = nodesQueue.begin();
for (int i = 0; i < nodesInThisLevel; i++, iter++) {
out << ((i == 0) ? setw(startLen) : setw(nodeSpaceLen)) << "" << ((*iter && (*iter)->left) ? setfill('_') : setfill(' '));
out << setw(branchLen+2) << ((*iter) ? intToString((*iter)->data) : "");
out << ((*iter && (*iter)->right) ? setfill('_') : setfill(' ')) << setw(branchLen) << "" << setfill(' ');
}
out << endl;
}
// Print the leaves only (just for the bottom row)
void printLeaves(int indentSpace, int level, int nodesInThisLevel, const deque<BinaryTree*>& nodesQueue, ostream& out) {
deque<BinaryTree*>::const_iterator iter = nodesQueue.begin();
for (int i = 0; i < nodesInThisLevel; i++, iter++) {
out << ((i == 0) ? setw(indentSpace+2) : setw(2*level+2)) << ((*iter) ? intToString((*iter)->data) : "");
}
out << endl;
}
// Pretty formatting of a binary tree to the output stream
// # param
// level Control how wide you want the tree to sparse (eg, level 1 has the minimum space between nodes, while level 2 has a larger space between nodes)
// indentSpace Change this to add some indent space to the left (eg, indentSpace of 0 means the lowest level of the left node will stick to the left margin)
void printPretty(BinaryTree *root, int level, int indentSpace, ostream& out) {
int h = maxHeight(root);
int nodesInThisLevel = 1;
int branchLen = 2*((int)pow(2.0,h)-1) - (3-level)*(int)pow(2.0,h-1); // eq of the length of branch for each node of each level
int nodeSpaceLen = 2 + (level+1)*(int)pow(2.0,h); // distance between left neighbor node's right arm and right neighbor node's left arm
int startLen = branchLen + (3-level) + indentSpace; // starting space to the first node to print of each level (for the left most node of each level only)
deque<BinaryTree*> nodesQueue;
nodesQueue.push_back(root);
for (int r = 1; r < h; r++) {
printBranches(branchLen, nodeSpaceLen, startLen, nodesInThisLevel, nodesQueue, out);
branchLen = branchLen/2 - 1;
nodeSpaceLen = nodeSpaceLen/2 + 1;
startLen = branchLen + (3-level) + indentSpace;
printNodes(branchLen, nodeSpaceLen, startLen, nodesInThisLevel, nodesQueue, out);
for (int i = 0; i < nodesInThisLevel; i++) {
BinaryTree *currNode = nodesQueue.front();
nodesQueue.pop_front();
if (currNode) {
nodesQueue.push_back(currNode->left);
nodesQueue.push_back(currNode->right);
} else {
nodesQueue.push_back(NULL);
nodesQueue.push_back(NULL);
}
}
nodesInThisLevel *= 2;
}
printBranches(branchLen, nodeSpaceLen, startLen, nodesInThisLevel, nodesQueue, out);
printLeaves(indentSpace, level, nodesInThisLevel, nodesQueue, out);
}
int main() {
BinaryTree *root = new BinaryTree(30);
root->left = new BinaryTree(20);
root->right = new BinaryTree(40);
root->left->left = new BinaryTree(10);
root->left->right = new BinaryTree(25);
root->right->left = new BinaryTree(35);
root->right->right = new BinaryTree(50);
root->left->left->left = new BinaryTree(5);
root->left->left->right = new BinaryTree(15);
root->left->right->right = new BinaryTree(28);
root->right->right->left = new BinaryTree(41);
cout << "Tree pretty print with level=1 and indentSpace=0\n\n";
// Output to console
printPretty(root, 1, 0, cout);
cout << "\n\nTree pretty print with level=5 and indentSpace=3,\noutput to file \"tree_pretty.txt\".\n\n";
// Create a file and output to that file
ofstream fout("tree_pretty.txt");
// Now print a tree that's more spread out to the file
printPretty(root, 5, 0, fout);
return 0;
}
Maybe you can take a look at the Bresenham's line algorithm that it could be suitable for you
Here is a C implementation of the "quick and dirty" method mentioned here. It doesn't get much quicker and/or dirtier:
void shittyprint_tree(tree *T){ // Supposed to be quick'n'dirty!
// When DTYPE is "char", width is a bit larger than needed.
if (T == NULL)
return;
const int width = ceil(log10(get_largest(T->root)+0.01)) + 2;
const wchar_t* sp64 = L" ";
void nested(node *ST, int spaces){ // GCC extension
if (ST == NULL){
wprintf(L"\n"); // Can be commented to disable the extra blanc line.
return;
}
nested(ST->right, spaces + width);
wprintf(L"%*.*s("DTYPE_PRINTF")\n", 0, spaces, sp64, 1, 1, ST->data);
nested(ST->left, spaces + width);
}
nested(T->root, 2);
}
Sample output (using the same tree as before):
(115.0)
(113.0)
(109.0)
(107.0)
(106.1)
(106.0)
(102.0)
(101.5)
I can't say, though, that it fits my original requirements...

Resources