-
Notifications
You must be signed in to change notification settings - Fork 0
/
valstat_dll.cs
325 lines (279 loc) · 10.5 KB
/
valstat_dll.cs
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
#nullable enable
/*
Make sure you read this first
https://docs.microsoft.com/en-us/dotnet/standard/native-interop/best-practices
*/
//
// here is how to allocate and free for C# interop
//
// string smMsg = "whatever" ;
// var len = Marshal.SizeOf(typeof(smMsg));
// IntPtr msg_intptr = Marshal.AllocHGlobal(len);
// try {
// // call the function, convert output to struct, etc...
// }
// finally {
// Marshal.FreeHGlobal(msg_intptr);
// }
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace valstatcsharp;
internal class valstat_dll
{
// This is as far as we will go here: const is a compile time thing in C#
const string valstat_dll_location = @"C:\Users\dusan\.valstat\valstat_dll.dll";
// during the development we need the full path to the dll, so we hard code it here.
// there might be better solutions:
// 1. store the paths in the configuration file
// 2. dynamiclay load the dll and get the function inside: https://stackoverflow.com/a/8836228/10870835
// that might be very slow
/// <summary>
/// obtain the dll used and the name of the compiler used to build it
/// obviously this is Windows specific C#
/// </summary>
/// <returns>valstat {bool ?, string ?} </returns>
internal static ( bool ? , string ? ) descriptor()
{
// local extern function declaration is C#9 feature
// CharSet attribute parameter is CharSet.Unicode
// CharSet.Ansi is default and that is unfortunate
[DllImport(valstat_dll_location, EntryPoint = "compiler_string", CharSet = CharSet.Unicode, ExactSpelling = true)]
static extern Int32 compiler_string([Out] char[] string_, Int32 string_length_);
[DllImport(valstat_dll_location, EntryPoint = "this_name", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
static extern bool this_name([MarshalAs(UnmanagedType.LPWStr)] string name_, out Int32 string_length_);
char[] compiler_name_ = new char[128];
int compiler_name_size = compiler_string(compiler_name_, 128);
// it is unlikely the compiler name will be one char or less
if (compiler_name_size < 2 )
return ( null, (new Win32Exception(Marshal.GetLastWin32Error())).Message ) ;
var compiler_name_string = new string(compiler_name_, 0, compiler_name_size);
// first ask how much space is required for the string rezult
// if the target dll is built in debug mode
// follow through debugger and you will find yourself inside
// the dll written in C
int size_ = 0;
if (false == this_name("", out size_))
return (null, (new Win32Exception(Marshal.GetLastWin32Error())).Message);
// second obtain the string of reported size
var dll_name_ = new string(' ', size_);
if (false == this_name(dll_name_, out size_))
return (null, (new Win32Exception(Marshal.GetLastWin32Error())).Message);
(string File, int Line) = DBJcore.FileLineInfo();
DBJLog.debug(
"\n(File:{3})(Line:{4})(Function:{2}) : Compiler used to build {0} is: {1}\n",
dll_name_,
compiler_name_string,
DBJcore.Whoami(),
File,
Line
);
return (true, string.Empty );
}
/// <summary>
/// VALSTAT structure C version:
///
/// enum { icp_data_count = 255 };
/// typedef struct {
/// int val;
/// char data[icp_data_count];
/// }
/// int_charr_pair;
///
/// VALSTAT structure C# version
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
struct int_charr_pair
{
public IntPtr val;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 255)]
public string data;
}
/// <summary>
/// make tuple valstat aka: ( int ? , string )
/// we need to "marshall" to tuples since tuples are so called "generics"
/// and generics can not be marshalled in C#9:
///
/// MarshalDirectiveException
/// Cannot marshal 'parameter #1': Non-blittable generic types cannot be marshaled.
///
///</summary>
/// <param name="icp">C# struct version</param>
/// <param name="caller_">the name of the caller</param>
static (int? val, string? stat)
int_charr_pair_to_tuple
(ref int_charr_pair icp)
{
// field names are optional
// start in valstat EMPTY state
(int? val, string? stat) vstat = (null, null);
if (icp.val != IntPtr.Zero)
vstat.val = Marshal.ReadInt32(icp.val);
if (!string.IsNullOrEmpty(icp.data))
{
if (!string.IsNullOrWhiteSpace(icp.data))
{
vstat.stat = icp.data;
}
}
return vstat;
}
// log it and return it
// notice the ad-hoc tuple declaration
static (int? val, string? stat)
log_the_tuple((int? val, string? stat) vstat, [CallerMemberName] string caller_ = " ")
{
DBJLog.debug(@"{0}: {{ value: {1}, status: {2} }}",
caller_,
vstat.val.ToString() ?? "empty",
vstat.stat ?? "empty" );
return vstat;
}
internal static (int? val, string? stat) safe_division
(int nominator, int denominator)
{
[DllImport(valstat_dll_location)]
static extern void safe_division(out int_charr_pair vst_ptr, int numerator, int denominator);
// allocate the valstat struct here
int_charr_pair icp = new int_charr_pair();
// call the dll
safe_division(out icp, nominator, denominator);
// log and marhsall to tuple
// return tuple
return log_the_tuple(int_charr_pair_to_tuple(ref icp));
}
/// <summary>
/// this is the tuple variant of the struct above
/// general C#9 code will use tuples
/// but we can not use non-blitable tuples C# interop function arguments
/// and this is non-blitable besause it used string
/// this is static variable not a tuple declaration
/// </summary>
static (int?, string?) vstat_tuple;
////////////////////////////////////////////////////////////////////////////////////////
// declare a C# funcion pointer aka `delegate`
// C version:
// typedef void (*safe_division_fp)(int_charr_pair*);
private delegate void safe_division_delegate(int_charr_pair icp);
// implement the `delegate`
// this is a callback used from inside the DLL
// we need to take care not to spend too
// much time in here, because dll will wait
// for this function to return
private static void safe_division_fp(int_charr_pair icp)
{
// saving the result
vstat_tuple = log_the_tuple(int_charr_pair_to_tuple(ref icp));
}
internal static (int? val, string? stat)
safe_division_2(int nominator, int denominator)
{
[DllImport(valstat_dll_location)]
static extern void safe_division_cb(safe_division_delegate callback_, int numerator, int denominator);
// call into dkk
safe_division_cb(safe_division_fp, nominator, denominator);
// return the result of log_the_tuple() obtained
// from inside safe_division_fp()
return vstat_tuple;
}
} // valstat_dll
// dbj
#region deprecated
// https://stackoverflow.com/a/47648504/10870835
/*
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct WAVEFORMATEX
{
public ushort wFormatTag;
public ushort nChannels;
public uint nSamplesPerSec;
public uint nAvgBytesPerSec;
public ushort nBlockAlign;
public ushort wBitsPerSample;
public ushort cbSize;
// C# should generate pretty decent
// ToString() here, but it did not
// ditto ...
public override string ToString()
{
return this.GetType().FormattedName()
+ $"\n{nameof(wFormatTag)} \t: {wFormatTag.ToString()}"
+ $"\n{nameof(nChannels)} \t: {nChannels.ToString()}"
+ $"\n{nameof(nSamplesPerSec)} \t: {nSamplesPerSec.ToString()}"
+ $"\n{nameof(nAvgBytesPerSec)} : {nAvgBytesPerSec.ToString()}"
+ $"\n{nameof(nBlockAlign)} \t: {nBlockAlign.ToString()}"
+ $"\n{nameof(wBitsPerSample)} \t: {wBitsPerSample.ToString()}"
+ $"\n{nameof(cbSize)} \t\t: {cbSize.ToString()}";
}
}
internal static void test_waveformat_from_dll()
{
#if DEBUG
[DllImport(@"D:\DEVL\GITHUB\DBJDBJ\valstat_dll\x64\Debug\valstat_dll.dll"
, EntryPoint = "waveformat", CharSet = CharSet.Unicode, ExactSpelling = true)]
#else
[DllImport(@"D:\DEVL\GITHUB\DBJDBJ\valstat_dll\x64\Release\valstat_dll.dll"
, EntryPoint = "waveformat", CharSet = CharSet.Unicode, ExactSpelling = true)]
#endif
static extern Int32 waveformat(out WAVEFORMATEX wfx);
WAVEFORMATEX wfx;
try
{
var result = waveformat(out wfx);
Console.WriteLine(result);
Console.WriteLine(wfx);
}
catch (System.DllNotFoundException x)
{
DBJLog.debug(x);
}
}
*/
/*
[StructLayout(LayoutKind.Sequential)]
unsafe struct int_string_pair
{
public int val;
public string stat;
public int stat_len;
}
/// make emptry int char array pair
static int_string_pair make(int stat_size)
{
var ret = new int_string_pair();
ret.val = 0;
ret.stat = " ".ToLength((uint)stat_size);
ret.stat_len = stat_size;
return ret;
}
/// from tuple
static unsafe int_string_pair make((int?, string?) tup)
{
var ret = new int_string_pair();
#pragma warning disable CS8629 // Nullable value type may be null.
#pragma warning disable CS8601 // Possible null reference assignment.
ret.val = (int)(tup.Item1 ?? default);
ret.stat = tup.Item2 ?? default;
ret.stat_len = ret.stat.Length;
#pragma warning restore CS8601 // Possible null reference assignment.
#pragma warning restore CS8629 // Nullable value type may be null.
return ret;
}
/// to tuple
static (int?, string?) make(int_string_pair isp)
{
return (isp.val, isp.stat);
}
internal unsafe static void test_valstat_dll()
{
[DllImport(valstat_dll_location)]
static extern void safe_division([Out] int_string_pair valstat, int numerator, int denominator);
var vis = make(0xF);
safe_division(vis, 42, 12);
var (val, stat) = make(vis);
DBJLog.debug(@"{0} : {{ value: {1}, status: {2} }}", whoami(), val ?? default, stat ?? default);
}
*/
#endregion