Skip to content

Commit e99b02b

Browse files
grendellojonpryor
authored andcommitted
[monodroid] Migrate to C++ (#2089)
The purpose of this conversion is to start using a language that is stricter and yet more powerful than C. Stricter in type safety, in separation of private and public functions, in data encapsulation. Taking advantage of the C++ *language* features (as opposed to its standard library) allows us to write better structured, safer and less fragile code. C++ also allows us to implement safer memory management (not part of this commit, see below). This commit implements the basic, skeletal work on straightforward C to C++ conversion: * Introduces classes to encompass a number of APIs that can be grouped together * fixes a number of issues with the old C code (void pointer arithmetic, incorrect type casts, comparisons of integers with different signs or of different sizes) * introduces strict type checking in the Mono interface API (it used to use `void*` pointers which could lead to confusion and hard to detect runtime/build time errors, see below) The Mono API (found in the `DylibMono` class) was error prone, as mentioned above, because it allowed code similar to the following fragment to build without any warning or error from the compilers: void my_code (MonoDomain *domain) { } // ... MonoDomain *domain = get_domain (); my_code (&domain); `MonoDomain` (and other Mono types) were `typedef`ed to be `void` which made the above call "valid" (as a `void**` ISA `void*`). A developer working with code wouldn't immediately know what was the issue because of the `typedef` hiding the actual `MonoDomain` type. During test execution, code in `monodroid-glue.cc` would mistakenly pass a pointer to `MonoDomain` pointer (a.k.a `void**`) to the Mono runtime caused the runtime to block while attempting to obtain a lock on the domain where the domain did not exist and, obviously, not initialized, so and the call couldn't succeed. Thus the idea to change the typedefs to `struct XY` which introduces a unique type, making all mistakes of this kind (as well as invalid casts) obviously wrong and caught by the compiler. This commit does *not* implement much in the are of memory management because such changes would make porting code from master to the branch increasingly complex and error prone. For the same reason `monodroid-glue.cc` is not split up into separate files (which is the eventual plan) but merely introduces a handful of classes in that file. After the commit is merged into master both of those changes will be implemented to further take advantage of C++ capabilities.
1 parent aa73a7f commit e99b02b

26 files changed

+3608
-1962
lines changed

src/monodroid/CMakeLists.txt

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -147,12 +147,14 @@ if(ENABLE_NDK)
147147
add_definitions("-DANDROID")
148148
add_definitions("-DLINUX -Dlinux -D__linux__")
149149
else()
150+
# MinGW/mxe need it for {v,a}sprintf
151+
add_definitions("-D_GNU_SOURCE")
150152
if(APPLE)
151153
add_definitions("-DAPPLE_OS_X")
152154
set(HOST_BUILD_NAME "host-Darwin")
153155
elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL Linux)
154156
if(NOT MINGW AND NOT WIN32)
155-
add_definitions("-D_GNU_SOURCE -DLINUX -Dlinux -D__linux__")
157+
add_definitions("-DLINUX -Dlinux -D__linux__")
156158
set(HOST_BUILD_NAME "host-Linux")
157159

158160
if(EXISTS "/.flatpak-info")
@@ -205,9 +207,9 @@ endif()
205207

206208
add_definitions("-DHAVE_CONFIG_H")
207209
add_definitions("-D_REENTRANT")
210+
add_definitions("-DDYLIB_MONO")
208211
add_definitions("-DJI_DLL_EXPORT")
209212
add_definitions("-DMONO_DLL_EXPORT")
210-
add_definitions("-DDYLIB_MONO")
211213
add_definitions("-DSGEN_BRIDGE_VERSION=${SGEN_BRIDGE_VERSION}")
212214

213215
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_COMPILER_FLAGS} ${EXTRA_C_FLAGS} -std=c99")
@@ -232,13 +234,15 @@ set(SOURCES_DIR ${TOP_DIR}/jni)
232234
set(MONODROID_SOURCES
233235
${MONODROID_SOURCES}
234236
${MONO_PATH}/support/zlib-helper.c
235-
${SOURCES_DIR}/cpu-arch-detect.c
236-
${SOURCES_DIR}/dylib-mono.c
237-
${SOURCES_DIR}/embedded-assemblies.c
237+
${SOURCES_DIR}/cpu-arch-detect.cc
238+
${SOURCES_DIR}/debug-constants.cc
239+
${SOURCES_DIR}/dylib-mono.cc
240+
${SOURCES_DIR}/embedded-assemblies.cc
241+
${SOURCES_DIR}/globals.cc
238242
${SOURCES_DIR}/jni.c
239-
${SOURCES_DIR}/monodroid-glue.c
240-
${SOURCES_DIR}/timezones.c
241-
${SOURCES_DIR}/util.c
243+
${SOURCES_DIR}/monodroid-glue.cc
244+
${SOURCES_DIR}/timezones.cc
245+
${SOURCES_DIR}/util.cc
242246
${SOURCES_DIR}/zip/ioapi.c
243247
${SOURCES_DIR}/zip/unzip.c
244248
${JAVA_INTEROP_SRC_PATH}/java-interop.c
@@ -251,9 +255,9 @@ if(UNIX)
251255
set(MONODROID_SOURCES
252256
${MONODROID_SOURCES}
253257
${MONO_PATH}/support/nl.c
254-
${SOURCES_DIR}/debug.c
255-
${SOURCES_DIR}/monodroid-networkinfo.c
256-
${SOURCES_DIR}/xamarin_getifaddrs.c
258+
${SOURCES_DIR}/debug.cc
259+
${SOURCES_DIR}/monodroid-networkinfo.cc
260+
${SOURCES_DIR}/xamarin_getifaddrs.cc
257261
)
258262
endif()
259263

src/monodroid/jni/TODO.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
= Memory management
2+
3+
* Don't use malloc and friends on pointers which aren't passed to Mono
4+
5+
= Strings
6+
7+
* Implement a simple class to perform all string manipulations
8+
9+
= Warnings
10+
11+
* Fix all the C++ warnings regarding constant strings

src/monodroid/jni/cppcompat.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// This is a -*- C++ -*- header
2+
#ifndef __CPP_COMPAT_H
3+
#define __CPP_COMPAT_H
4+
5+
// Since Android doesn't currently have any standard C++ library
6+
// and we don't want to use any implementation of it shipped in
7+
// source form with the NDK (for space reasons), this header will
8+
// contain implementations of certain C++ standard functions, classes
9+
// etc we want to use despite lack of the STL.
10+
//
11+
// When/if we have any STL implementation available on standard Android
12+
// we can remove this file.
13+
namespace std
14+
{
15+
template <typename T> struct remove_reference { using type = T; };
16+
template <typename T> struct remove_reference<T&> { using type = T; };
17+
template <typename T> struct remove_reference<T&&> { using type = T; };
18+
19+
template<typename T> typename remove_reference<T>::type&& move (T&& arg) noexcept
20+
{
21+
return static_cast<typename remove_reference<decltype(arg)>::type&&>(arg);
22+
}
23+
}
24+
#endif

src/monodroid/jni/debug-constants.cc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#include "debug.h"
2+
3+
using namespace xamarin::android;
4+
5+
// These are moved here so that the Windows build works and we don't need to
6+
// #ifdef references to these out in this case. Debugging code generally works
7+
// only on Unix atm
8+
const char Debug::DEBUG_MONO_CONNECT_PROPERTY[] = "debug.mono.connect";
9+
const char Debug::DEBUG_MONO_DEBUG_PROPERTY[] = "debug.mono.debug";
10+
const char Debug::DEBUG_MONO_ENV_PROPERTY[] = "debug.mono.env";
11+
const char Debug::DEBUG_MONO_EXTRA_PROPERTY[] = "debug.mono.extra";
12+
const char Debug::DEBUG_MONO_GC_PROPERTY[] = "debug.mono.gc";
13+
const char Debug::DEBUG_MONO_GDB_PROPERTY[] = "debug.mono.gdb";
14+
const char Debug::DEBUG_MONO_GDBPORT_PROPERTY[] = "debug.mono.gdbport";
15+
const char Debug::DEBUG_MONO_LOG_PROPERTY[] = "debug.mono.log";
16+
const char Debug::DEBUG_MONO_MAX_GREFC[] = "debug.mono.max_grefc";
17+
const char Debug::DEBUG_MONO_PROFILE_PROPERTY[] = "debug.mono.profile";
18+
const char Debug::DEBUG_MONO_RUNTIME_ARGS_PROPERTY[] = "debug.mono.runtime_args";
19+
const char Debug::DEBUG_MONO_SOFT_BREAKPOINTS[] = "debug.mono.soft_breakpoints";
20+
const char Debug::DEBUG_MONO_TRACE_PROPERTY[] = "debug.mono.trace";
21+
const char Debug::DEBUG_MONO_WREF_PROPERTY[] = "debug.mono.wref";
22+
23+
extern "C" const char *__get_debug_mono_log_property (void)
24+
{
25+
return static_cast<const char*> (Debug::DEBUG_MONO_LOG_PROPERTY);
26+
}

src/monodroid/jni/debug.c renamed to src/monodroid/jni/debug.cc

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,19 @@
2121
#include <errno.h>
2222
#include <ctype.h>
2323
#include <assert.h>
24-
#include <pthread.h>
2524

2625
#ifdef ANDROID
2726
#include <android/log.h>
2827
#endif
2928

29+
extern "C" {
3030
#include "java-interop-util.h"
31+
}
3132

3233
#include "monodroid.h"
3334
#include "debug.h"
3435
#include "util.h"
36+
#include "globals.h"
3537

3638
//
3739
// The communication between xs and the app works as follows:
@@ -46,28 +48,27 @@
4648
//
4749
//
4850

49-
#ifdef DEBUG
50-
5151
// monodroid-glue.c
52-
int process_cmd (int fd, char *cmd);
53-
54-
static int conn_port;
55-
static pthread_t conn_thread_id;
52+
extern int process_cmd (int fd, char *cmd);
53+
namespace xamarin { namespace android
54+
{
55+
void* conn_thread (void *arg);
56+
}}
5657

57-
static void* conn_thread (void *arg);
58+
#ifdef DEBUG
59+
using namespace xamarin::android;
5860

59-
typedef struct {
60-
int64_t timeout_time;
61-
} ConnOptions;
61+
int Debug::conn_port = 0;
62+
pthread_t Debug::conn_thread_id = 0;
6263

63-
static void
64-
parse_options (char *options, ConnOptions *opts)
64+
inline void
65+
Debug::parse_options (char *options, ConnOptions *opts)
6566
{
6667
char **args, **ptr;
6768

6869
log_info (LOG_DEFAULT, "Connection options: '%s'", options);
6970

70-
args = monodroid_strsplit (options, ",", -1);
71+
args = utils.monodroid_strsplit (options, ",", -1);
7172

7273
for (ptr = args; ptr && *ptr; ptr++) {
7374
const char *arg = *ptr;
@@ -99,7 +100,7 @@ parse_options (char *options, ConnOptions *opts)
99100
* - 2 if no connection is neccessary
100101
*/
101102
int
102-
start_connection (char *options)
103+
Debug::start_connection (char *options)
103104
{
104105
int res;
105106
ConnOptions opts;
@@ -119,7 +120,7 @@ start_connection (char *options)
119120
if (!conn_port)
120121
return 0;
121122

122-
res = pthread_create (&conn_thread_id, NULL, conn_thread, NULL);
123+
res = pthread_create (&conn_thread_id, NULL, xamarin::android::conn_thread, this);
123124
if (res) {
124125
log_error (LOG_DEFAULT, "Failed to create connection thread: %s", strerror (errno));
125126
return 1;
@@ -134,8 +135,8 @@ start_connection (char *options)
134135
* Handle communication on the socket FD. Return TRUE if its neccessary to create more connections to handle more data.
135136
* Call process_cmd () with each command received.
136137
*/
137-
static int
138-
process_connection (int fd)
138+
inline int
139+
Debug::process_connection (int fd)
139140
{
140141
// make sure the fd/socket blocks on reads/writes
141142
fcntl (fd, F_SETFL, fcntl (fd, F_GETFL, NULL) & ~O_NONBLOCK);
@@ -145,7 +146,7 @@ process_connection (int fd)
145146
int rv;
146147
unsigned char cmd_len;
147148

148-
rv = recv_uninterrupted (fd, &cmd_len, 1);
149+
rv = utils.recv_uninterrupted (fd, &cmd_len, 1);
149150
if (rv == 0) {
150151
log_info (LOG_DEFAULT, "EOF on socket.\n");
151152
return FALSE;
@@ -155,7 +156,7 @@ process_connection (int fd)
155156
return FALSE;
156157
}
157158

158-
rv = recv_uninterrupted (fd, command, cmd_len);
159+
rv = utils.recv_uninterrupted (fd, command, cmd_len);
159160
if (rv <= 0) {
160161
log_info (LOG_DEFAULT, "Error while receiving command from XS (%s)\n", strerror (errno));
161162
return FALSE;
@@ -172,8 +173,8 @@ process_connection (int fd)
172173
}
173174
}
174175

175-
static int
176-
handle_server_connection (void)
176+
inline int
177+
Debug::handle_server_connection (void)
177178
{
178179
int listen_port = conn_port;
179180
struct sockaddr_in listen_addr;
@@ -295,18 +296,23 @@ handle_server_connection (void)
295296
return rv;
296297
}
297298

299+
// TODO: this is less than ideal. We can't use std::function or std::bind beause we
300+
// don't have the C++ stdlib on Android (well, we do but including it would make the
301+
// app huge so we don't want to involve it). To better solve it we need our own equivalent
302+
// to std::function
298303
void*
299-
conn_thread (void *arg)
304+
xamarin::android::conn_thread (void *arg)
300305
{
301-
int res;
306+
assert (arg != nullptr);
302307

303-
res = handle_server_connection ();
308+
int res;
309+
Debug *instance = static_cast<Debug*> (arg);
310+
res = instance->handle_server_connection ();
304311
if (res && res != 3) {
305312
log_fatal (LOG_DEBUGGER, "Error communicating with the IDE, exiting...");
306313
exit (FATAL_EXIT_DEBUGGER_CONNECT);
307314
}
308315

309316
return NULL;
310317
}
311-
312318
#endif /* DEBUG */

src/monodroid/jni/debug.h

Lines changed: 59 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,65 @@
1+
// This is a -*- C++ -*- header
12
#ifndef __MONODROID_DEBUG_H__
23
#define __MONODROID_DEBUG_H__
34

4-
/* Android property containing connection information, set by XS */
5-
#define DEBUG_MONO_CONNECT_PROPERTY "debug.mono.connect"
6-
#define DEBUG_MONO_DEBUG_PROPERTY "debug.mono.debug"
7-
#define DEBUG_MONO_ENV_PROPERTY "debug.mono.env"
8-
#define DEBUG_MONO_EXTRA_PROPERTY "debug.mono.extra"
9-
#define DEBUG_MONO_GC_PROPERTY "debug.mono.gc"
10-
#define DEBUG_MONO_GDB_PROPERTY "debug.mono.gdb"
11-
#define DEBUG_MONO_GDBPORT_PROPERTY "debug.mono.gdbport"
12-
#define DEBUG_MONO_LOG_PROPERTY "debug.mono.log"
13-
#define DEBUG_MONO_MAX_GREFC "debug.mono.max_grefc"
14-
#define DEBUG_MONO_PROFILE_PROPERTY "debug.mono.profile"
15-
#define DEBUG_MONO_RUNTIME_ARGS_PROPERTY "debug.mono.runtime_args"
16-
#define DEBUG_MONO_SOFT_BREAKPOINTS "debug.mono.soft_breakpoints"
17-
#define DEBUG_MONO_TRACE_PROPERTY "debug.mono.trace"
18-
#define DEBUG_MONO_WREF_PROPERTY "debug.mono.wref"
19-
20-
#ifndef WINDOWS
21-
int start_connection (char *options);
5+
#include <stdint.h>
6+
#include <pthread.h>
7+
8+
#ifdef __cplusplus
9+
namespace xamarin { namespace android
10+
{
11+
class Debug
12+
{
13+
private:
14+
struct ConnOptions
15+
{
16+
int64_t timeout_time;
17+
};
18+
19+
public:
20+
/* Android property containing connection information, set by XS */
21+
static const char DEBUG_MONO_CONNECT_PROPERTY[];
22+
static const char DEBUG_MONO_DEBUG_PROPERTY[];
23+
static const char DEBUG_MONO_ENV_PROPERTY[];
24+
static const char DEBUG_MONO_EXTRA_PROPERTY[];
25+
static const char DEBUG_MONO_GC_PROPERTY[];
26+
static const char DEBUG_MONO_GDB_PROPERTY[];
27+
static const char DEBUG_MONO_GDBPORT_PROPERTY[];
28+
static const char DEBUG_MONO_LOG_PROPERTY[];
29+
static const char DEBUG_MONO_MAX_GREFC[];
30+
static const char DEBUG_MONO_PROFILE_PROPERTY[];
31+
static const char DEBUG_MONO_RUNTIME_ARGS_PROPERTY[];
32+
static const char DEBUG_MONO_SOFT_BREAKPOINTS[];
33+
static const char DEBUG_MONO_TRACE_PROPERTY[];
34+
static const char DEBUG_MONO_WREF_PROPERTY[];
35+
36+
public:
37+
explicit Debug ()
38+
{}
39+
40+
#if !defined (WINDOWS) && defined (DEBUG)
41+
int start_connection (char *options);
42+
43+
private:
44+
void parse_options (char *options, ConnOptions *opts);
45+
int process_connection (int fd);
46+
int handle_server_connection (void);
47+
friend void* conn_thread (void *arg);
48+
49+
private:
50+
static int conn_port;
51+
static pthread_t conn_thread_id;
52+
2253
#endif
54+
};
55+
} }
56+
#else // __cplusplus
57+
const char *__get_debug_mono_log_property (void);
58+
59+
#ifndef DEBUG_MONO_LOG_PROPERTY
60+
#define DEBUG_MONO_LOG_PROPERTY __get_debug_mono_log_property ()
61+
#endif
62+
63+
#endif // __cplusplus
2364

2465
#endif /* __MONODROID_DEBUG_H__ */

0 commit comments

Comments
 (0)