File: csharp.py

package info (click to toggle)
mupdf 1.25.1%2Bds1-9
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 21,644 kB
  • sloc: ansic: 270,937; python: 20,709; java: 6,916; javascript: 1,865; makefile: 1,130; xml: 550; sh: 430; cpp: 325; cs: 313; awk: 10; sed: 7; lisp: 3
file content (325 lines) | stat: -rw-r--r-- 12,787 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
'''
Things for generating C#-specific output.
'''
from . import cpp
from . import parse
from . import rename
from . import state
from . import util

import jlib

import textwrap
import os


def make_outparam_helper_csharp(
        tu,
        cursor,
        fnname,
        fnname_wrapper,
        generated,
        main_name,
        ):
    '''
    Write C# code for a convenient tuple-returning wrapper for MuPDF
    function that has out-params. We use the C# wrapper for our generated
    {main_name}_outparams() function.

    We don't attempt to handle functions that take unsigned char* args
    because these generally indicate sized binary data and cannot be handled
    generically.
    '''
    def write(text):
        generated.swig_csharp.write(text)

    main_name = rename.ll_fn(cursor.mangled_name)
    return_void = cursor.result_type.spelling == 'void'
    if fnname == 'fz_buffer_extract':
        # Write custom wrapper that returns the binary data as a C# bytes
        # array, using the C# wrapper for buffer_extract_outparams_fn(fz_buffer
        # buf, buffer_extract_outparams outparams).
        #
        write(
                textwrap.dedent(
                f'''

                // Custom C# wrapper for fz_buffer_extract().
                public static class mupdf_{rename.class_('fz_buffer')}_extract
                {{
                    public static byte[] {rename.method('fz_buffer', 'fz_buffer_extract')}(this mupdf.{rename.class_('fz_buffer')} buffer)
                    {{
                        var outparams = new mupdf.{rename.ll_fn('fz_buffer_storage')}_outparams();
                        uint n = mupdf.mupdf.{rename.ll_fn('fz_buffer_storage')}_outparams_fn(buffer.m_internal, outparams);
                        var raw1 = mupdf.SWIGTYPE_p_unsigned_char.getCPtr(outparams.datap);
                        System.IntPtr raw2 = System.Runtime.InteropServices.HandleRef.ToIntPtr(raw1);
                        byte[] ret = new byte[n];
                        // Marshal.Copy() raises exception if <raw2> is null even if <n> is zero.
                        if (n == 0) return ret;
                        System.Runtime.InteropServices.Marshal.Copy(raw2, ret, 0, (int) n);
                        buffer.{rename.method( 'fz_buffer', 'fz_clear_buffer')}();
                        buffer.{rename.method( 'fz_buffer', 'fz_trim_buffer')}();
                        return ret;
                    }}
                }}
                ''')
                )
        return

    # We don't attempt to generate wrappers for fns that take or return
    # 'unsigned char*' - swig does not treat these as zero-terminated strings,
    # and they are generally binary data so cannot be handled generically.
    #
    if parse.is_pointer_to(cursor.result_type, 'unsigned char'):
        jlib.log(f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because it returns unsigned char*.', 1)
        return
    for arg in parse.get_args( tu, cursor):
        if parse.is_pointer_to(arg.cursor.type, 'unsigned char'):
            jlib.log(f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has unsigned char* arg.', 1)
            return
        if parse.is_pointer_to_pointer_to(arg.cursor.type, 'unsigned char'):
            jlib.log(f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has unsigned char** arg.', 1)
            return
        if arg.cursor.type.get_array_size() >= 0:
            jlib.log(f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has array arg.', 1)
            return
        if arg.cursor.type.kind == state.clang.cindex.TypeKind.POINTER:
            pointee = state.get_name_canonical( arg.cursor.type.get_pointee())
            if pointee.kind == state.clang.cindex.TypeKind.ENUM:
                jlib.log(f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has enum out-param arg.', 1)
                return
            if pointee.kind == state.clang.cindex.TypeKind.FUNCTIONPROTO:
                jlib.log(f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has fn-ptr arg.', 1)
                return
            if pointee.is_const_qualified():
                # Not an out-param.
                jlib.log(f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has pointer-to-const arg.', 1)
                return
            if arg.cursor.type.get_pointee().spelling == 'FILE':
                jlib.log(f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has FILE* arg.', 1)
                return
            if pointee.spelling == 'void':
                jlib.log(f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because has void* arg.', 1)
                return

    num_return_values = 0 if return_void else 1
    for arg in parse.get_args( tu, cursor):
        if arg.out_param:
            num_return_values += 1
    assert num_return_values

    if num_return_values > 7:
        # On linux, mono-csc can fail with:
        #   System.NotImplementedException: tuples > 7
        #
        jlib.log(f'Cannot generate C# out-param wrapper for {cursor.mangled_name} because would require > 7-tuple.')
        return

    # Write C# wrapper.
    arg0, _ = parse.get_first_arg( tu, cursor)
    if not arg0.alt:
        return

    method_name = rename.method( arg0.alt.type.spelling, fnname)

    write(f'\n')
    write(f'// Out-params extension method for C# class {rename.class_(arg0.alt.type.spelling)} (wrapper for MuPDF struct {arg0.alt.type.spelling}),\n')
    write(f'// adding class method {method_name}() (wrapper for {fnname}())\n')
    write(f'// which returns out-params directly.\n')
    write(f'//\n')
    write(f'public static class mupdf_{main_name}_outparams_helper\n')
    write(f'{{\n')
    write(f'    public static ')

    def write_type(alt, type_):
        if alt:
            write(f'mupdf.{rename.class_(alt.type.spelling)}')
        elif parse.is_pointer_to(type_, 'char'):
            write( f'string')
        else:
            text = cpp.declaration_text(type_, '').strip()
            if text == 'int16_t':           text = 'short'
            elif text == 'int64_t':         text = 'long'
            elif text == 'size_t':          text = 'ulong'
            elif text == 'unsigned int':    text = 'uint'
            elif text.startswith('enum '):
                # This is primarily for enum pdf_zugferd_profile; C# does not
                # like `enum` prefix, and we need to specify namespace name
                # `mupdf`.
                text = text[5:]
                if text.startswith('pdf_') or text.startswith('fz_'):
                    text = f'{rename.namespace()}.{text}'
            write(f'{text}')

    # Generate the returned tuple.
    #
    if num_return_values > 1:
        write('(')

    sep = ''

    # Returned param, if any.
    if not return_void:
        return_alt = None
        base_type_cursor, base_typename, extras = parse.get_extras( tu, cursor.result_type)
        if extras:
            if extras.opaque:
                # E.g. we don't have access to defintion of fz_separation,
                # but it is marked in classextras with opaque=true, so
                # there will be a wrapper class.
                return_alt = base_type_cursor
            elif base_type_cursor.kind == state.clang.cindex.CursorKind.STRUCT_DECL:
                return_alt = base_type_cursor
        write_type(return_alt, cursor.result_type)
        sep = ', '

    # Out-params.
    for arg in parse.get_args( tu, cursor):
        if arg.out_param:
            write(sep)
            write_type(arg.alt, arg.cursor.type.get_pointee())
            if num_return_values > 1:
                write(f' {arg.name_csharp}')
            sep = ', '

    if num_return_values > 1:
        write(')')

    # Generate function name and params. If first arg is a wrapper class we
    # use C#'s 'this' keyword to make this a member function of the wrapper
    # class.
    #jlib.log('outputs fn {fnname=}: is member: {"yes" if arg0.alt else "no"}')
    write(f' ')
    write( method_name if arg0.alt else 'fn')
    write(f'(')
    if arg0.alt: write('this ')
    sep = ''
    for arg in parse.get_args( tu, cursor):
        if arg.out_param:
            continue
        write(sep)
        if arg.alt:
            # E.g. 'Document doc'.
            write(f'mupdf.{rename.class_(arg.alt.type.spelling)} {arg.name_csharp}')
        elif parse.is_pointer_to(arg.cursor.type, 'char'):
            write(f'string {arg.name_csharp}')
        else:
            text = cpp.declaration_text(arg.cursor.type, arg.name_csharp).strip()
            text = util.clip(text, 'const ')
            text = text.replace('int16_t ', 'short ')
            text = text.replace('int64_t ', 'long ')
            text = text.replace('size_t ', 'uint ')
            text = text.replace('unsigned int ', 'uint ')
            write(text)
        sep = ', '
    write(f')\n')

    # Function body.
    #
    write(f'    {{\n')

    # Create local outparams struct.
    write(f'        var outparams = new mupdf.{main_name}_outparams();\n')
    write(f'        ')

    # Generate function call.
    #
    # The C# *_outparams_fn() generated by swig is inside namespace mupdf {
    # class mupdf { ... } }, so we access it using the rather clumsy prefix
    # 'mupdf.mupdf.'. It will have been generated from a C++ function
    # (generate by us) which is in top-level namespace mupdf, but swig
    # appears to generate the same code even if the C++ function is not in
    # a namespace.
    #
    if not return_void:
        write(f'var ret = ')
    write(f'mupdf.mupdf.{main_name}_outparams_fn(')
    sep = ''
    for arg in parse.get_args( tu, cursor):
        if arg.out_param:
            continue
        write(f'{sep}{arg.name_csharp}')
        if arg.alt:
            extras = parse.get_fz_extras( tu, arg.alt.type.spelling)
            assert extras.pod != 'none' \
                    'Cannot pass wrapper for {type_.spelling} as arg because pod is "none" so we cannot recover struct.'
            write('.internal_()' if extras.pod == 'inline' else '.m_internal')
        sep = ', '
    write(f'{sep}outparams);\n')

    # Generate return of tuple.
    write(f'        return ')
    if num_return_values > 1:
        write(f'(')
    sep = ''
    if not return_void:
        if return_alt:
            write(f'new mupdf.{rename.class_(return_alt.type.spelling)}(ret)')
        else:
            write(f'ret')
        sep = ', '
    for arg in parse.get_args( tu, cursor):
        if arg.out_param:
            write(f'{sep}')
            type_ = arg.cursor.type.get_pointee()
            if arg.alt:
                    write(f'new mupdf.{rename.class_(arg.alt.type.spelling)}(outparams.{arg.name_csharp})')
            elif 0 and parse.is_pointer_to(type_, 'char'):
                # This was intended to convert char* to string, but swig
                # will have already done that when making a C# version of
                # the C++ struct, and modern csc on Windows doesn't like
                # creating a string from a string for some reason.
                write(f'new string(outparams.{arg.name_csharp})')
            else:
                write(f'outparams.{arg.name_csharp}')
            sep = ', '
    if num_return_values > 1:
        write(')')
    write(';\n')
    write(f'    }}\n')
    write(f'}}\n')


def csharp_settings(build_dirs):
    '''
    Returns (csc, mono, mupdf_cs).

    csc: C# compiler.
    mono: C# interpreter ("" on Windows).
    mupdf_cs: MuPDF C# code.

    `mupdf_cs` will be None if `build_dirs` is false.

    E.g. on Windows `csc` can be: C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/MSBuild/Current/Bin/Roslyn/csc.exe
    '''
    # On linux requires:
    #   sudo apt install mono-devel
    #
    # OpenBSD:
    #   pkg_add mono
    # but we get runtime error when exiting:
    #   mono:build/shared-release/libmupdfcpp.so: undefined symbol '_ZdlPv'
    # which might be because of mixing gcc and clang?
    #
    if state.state_.windows:
        import wdev
        vs = wdev.WindowsVS()
        jlib.log('{vs.description_ml()=}')
        csc = vs.csc
        jlib.log('{csc=}')
        assert csc, f'Unable to find csc.exe'
        mono = ''
    else:
        mono = 'mono'
        if state.state_.linux:
            csc = 'mono-csc'
        elif state.state_.openbsd:
            csc = 'csc'
        else:
            assert 0, f'Do not know where to find mono. {platform.platform()=}'

    if build_dirs:
        mupdf_cs = os.path.relpath(f'{build_dirs.dir_so}/mupdf.cs')
    else:
        mupdf_cs = None
    return csc, mono, mupdf_cs