Skip to content

Commit 47893b6

Browse files
authored
Merge pull request #73 from bgilbert/dict
Generate dicom-dict hash tables at compile time; deprecate `dcm_init()`
2 parents 15166cf + e7c2630 commit 47893b6

File tree

12 files changed

+5268
-5169
lines changed

12 files changed

+5268
-5169
lines changed

.github/workflows/run_unit_tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ jobs:
7575
;;
7676
esac
7777
meson setup builddir --werror $setup_args
78-
meson compile -C builddir
78+
DEBUG_DICT=1 meson compile -C builddir
7979
$sudo meson install -C builddir
8080
8181
- name: Run unit tests

doc/source/usage.rst

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,11 +137,6 @@ locked to prevent subsequent modification via :c:func:`dcm_sequence_lock()`.
137137
A Sequence is automatically locked when used as a value in a Data Element
138138
with Value Representation SQ (Sequence of Items).
139139

140-
Call :c:func:`dcm_init()` from the main thread during program startup for
141-
libdicom initialisation to be threadsafe. If you do not call this, it will be
142-
triggered for you on first use, but in this case libdicom initialisation will
143-
not be threadsafe.
144-
145140
Error handling
146141
++++++++++++++
147142

include/dicom/dicom.h

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,15 +116,20 @@ typedef struct _DcmSequence DcmSequence;
116116
/**
117117
* Start up libdicom.
118118
*
119-
* Call this from the main thread during program startup for libdicom to be
120-
* threadsafe.
121-
*
122-
* If you don't do this, libdicom will attempt to call it for you in a safe
123-
* way, but cannot guarantee this on all platforms and with all compilers, and
124-
* therefore cannot guarantee thread safety.
119+
* Call this from the main thread during program startup.
125120
*
126121
* This function can be called many times.
122+
*
123+
* .. deprecated:: 1.1.0
124+
* Calling this function is no longer necessary.
127125
*/
126+
#ifndef BUILDING_LIBDICOM
127+
#if defined(_MSC_VER)
128+
__declspec(deprecated("dcm_init() no longer needs to be called"))
129+
#elif defined(__GNUC__)
130+
__attribute__((deprecated("dcm_init() no longer needs to be called")))
131+
#endif
132+
#endif
128133
DCM_EXTERN
129134
void dcm_init(void);
130135

meson.build

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,6 @@ endif
107107
if cc.has_header('unistd.h')
108108
cfg.set('HAVE_UNISTD_H', '1')
109109
endif
110-
# we use a constructor attr for dcm_init()
111-
cfg.set('HAS_CONSTRUCTOR', cc.has_function_attribute('constructor'))
112110

113111
configure_file(
114112
output : 'config.h',
@@ -143,12 +141,26 @@ install_headers(
143141
# src
144142
library_includes = include_directories('include')
145143
library_options = ['-DBUILDING_LIBDICOM']
144+
dict_build = executable(
145+
'dicom-dict-build',
146+
['src/dicom-dict-build.c', 'src/dicom-dict-tables.c'],
147+
dependencies : [uthash],
148+
include_directories : library_includes,
149+
native : true,
150+
)
151+
dict_lookup = custom_target(
152+
'dicom-dict-lookup',
153+
command : [dict_build, '@OUTPUT@'],
154+
output : ['dicom-dict-lookup.c', 'dicom-dict-lookup.h'],
155+
)
146156
library_sources = [
157+
dict_lookup,
147158
'src/getopt.c',
148159
'src/dicom.c',
149160
'src/dicom-io.c',
150161
'src/dicom-data.c',
151162
'src/dicom-dict.c',
163+
'src/dicom-dict-tables.c',
152164
'src/dicom-file.c',
153165
'src/dicom-parse.c',
154166
]

src/dicom-dict-build.c

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
#include "config.h"
2+
3+
#ifdef _WIN32
4+
// the Windows CRT considers fopen and getenv unsafe
5+
#define _CRT_SECURE_NO_WARNINGS
6+
// and deprecates strdup
7+
#define strdup(v) _strdup(v)
8+
#endif
9+
10+
#include <stdbool.h>
11+
#include <stdio.h>
12+
#include <stdlib.h>
13+
#include <string.h>
14+
15+
#include "uthash.h"
16+
17+
#include <dicom/dicom.h>
18+
#include "pdicom.h"
19+
#include "dicom-dict-tables.h"
20+
21+
#define EMPTY 0xffffffff
22+
#define MAX_PROBES 10
23+
24+
static FILE *c;
25+
static FILE *h;
26+
27+
static void make_table(const char *name, const void *items, int count,
28+
int item_size, int key_offset, int key_len,
29+
bool string_key_skip_empty)
30+
{
31+
int table_len = count * 4;
32+
unsigned *table = malloc(sizeof(unsigned) * table_len);
33+
for (int i = 0; i < table_len; i++) {
34+
table[i] = EMPTY;
35+
}
36+
37+
#define KEY(index) ((const void *) \
38+
((const char *) items + (index) * item_size + key_offset))
39+
int probe_counts[MAX_PROBES] = {0};
40+
for (int i = 0; i < count; i++) {
41+
if (string_key_skip_empty && ! *(const char *) KEY(i)) {
42+
continue;
43+
}
44+
int this_key_len = key_len ? key_len : (int) strlen(KEY(i));
45+
unsigned hash;
46+
HASH_FUNCTION(KEY(i), this_key_len, hash);
47+
int cell, probe;
48+
for (probe = 0, cell = hash % table_len;
49+
probe < MAX_PROBES && table[cell] != EMPTY;
50+
cell = (hash + ++probe) % table_len) {
51+
if (!memcmp(KEY(i), KEY(table[cell]), this_key_len)) {
52+
fprintf(stderr, "%s: Duplicate key at %d\n", name, i);
53+
exit(1);
54+
}
55+
}
56+
if (probe >= MAX_PROBES) {
57+
fprintf(stderr, "%s: Too many probes at %d\n", name, i);
58+
exit(1);
59+
}
60+
table[cell] = i;
61+
probe_counts[probe]++;
62+
}
63+
64+
int repr_bits = count > UINT8_MAX ? 16 : 8;
65+
unsigned empty_repr = (1 << repr_bits) - 1;
66+
fprintf(c, "const unsigned %s_len = %d;\n", name, table_len);
67+
fprintf(h, "extern const unsigned %s_len;\n", name);
68+
fprintf(c, "const uint%d_t %s_empty = 0x%x;\n", repr_bits, name, empty_repr);
69+
fprintf(h, "extern const uint%d_t %s_empty;\n", repr_bits, name);
70+
fprintf(c, "const uint%d_t %s_dict[%u] = {", repr_bits, name, table_len);
71+
fprintf(h, "extern const uint%d_t %s_dict[];\n\n", repr_bits, name);
72+
for (int i = 0; i < table_len; i++) {
73+
if (!(i % 8)) {
74+
fprintf(c, "\n");
75+
}
76+
fprintf(c, "0x%x, ", table[i] == EMPTY ? empty_repr : table[i]);
77+
}
78+
fprintf(c, "\n};\n\n");
79+
free(table);
80+
81+
if (getenv("DEBUG_DICT")) {
82+
double total_probes = 0;
83+
for (int i = 0; i < MAX_PROBES; i++) {
84+
total_probes += probe_counts[i] * (i + 1);
85+
}
86+
fprintf(stderr, "%-40s: %.3f probes/lookup, %7d bytes\n",
87+
name, total_probes / count, table_len * (repr_bits / 8));
88+
}
89+
}
90+
91+
int main(int argc, char **argv)
92+
{
93+
if (argc != 3) {
94+
fprintf(stderr, "Usage: %s c-file h-file\n", argv[0]);
95+
return 1;
96+
}
97+
98+
c = fopen(argv[1], "w");
99+
h = fopen(argv[2], "w");
100+
if (c == NULL || h == NULL) {
101+
fprintf(stderr, "Couldn't open files\n");
102+
return 1;
103+
}
104+
105+
fprintf(c, "#include <stdint.h>\n\n");
106+
fprintf(h, "#define LOOKUP_MAX_PROBES %d\n\n", MAX_PROBES);
107+
108+
#define ITEM_SIZE(table) sizeof((table)[0])
109+
#define FIELD_SIZE(table, field) sizeof((table)[0].field)
110+
#define FIELD_OFFSET(table, field) \
111+
((int) ((const char *) &(table)[0].field - (const char *) &(table)[0]))
112+
113+
make_table("dcm_vrtable_from_str",
114+
dcm_vr_table, dcm_vr_table_len,
115+
ITEM_SIZE(dcm_vr_table),
116+
FIELD_OFFSET(dcm_vr_table, str),
117+
0, // strlen
118+
false);
119+
120+
make_table("dcm_attribute_from_tag",
121+
dcm_attribute_table, dcm_attribute_table_len,
122+
ITEM_SIZE(dcm_attribute_table),
123+
FIELD_OFFSET(dcm_attribute_table, tag),
124+
FIELD_SIZE(dcm_attribute_table, tag),
125+
false);
126+
127+
// The "" keyword appears several times and is used for retired tags ...
128+
// we can't map this to tags unambiguously, so we skip it in the table
129+
make_table("dcm_attribute_from_keyword",
130+
dcm_attribute_table, dcm_attribute_table_len,
131+
ITEM_SIZE(dcm_attribute_table),
132+
FIELD_OFFSET(dcm_attribute_table, keyword),
133+
0, // strlen
134+
true);
135+
136+
if (fclose(c) || fclose(h)) {
137+
fprintf(stderr, "Couldn't write files\n");
138+
return 1;
139+
}
140+
141+
return 0;
142+
}

0 commit comments

Comments
 (0)