SQLite Forum

sqlite3_exec: the 3rd argument
Login

sqlite3_exec: the 3rd argument

(1) By anonymous on 2021-01-17 09:01:45 [link] [source]

The 3rd argument of this API is the callback function.

  1. The callback function is executed as many times as there are rowsin the result. Correct?
  2. The callback function returns the column names every time it is called? Correct? If affirmative, isn't this superfluous?

  3. How does the callback function handle the different types in the result columns? (Int, Float etc)?

  4. My callback function consistently fails on the second call: any ideas on how to overcome this? (I'm using C#)

(2) By Larry Brasfield (LarryBrasfield) on 2021-01-17 14:30:40 in reply to 1 [link] [source]

The callback function is executed as many times as there are rowsin [sic] the result. Correct?

That's what the docs for sqlite3_exec() claim, and I have found it to be true.

The callback function returns the column names every time it is called? Correct? If affirmative, isn't this superfluous?

Yes, and no. For some applications, it is a great convenience that the column names are available to the callback.

How does the callback function handle the different types in the result columns? (Int, Float etc)?

That would be up to the library user. The sqlite3_column_type() function is likely to be useful if the column types are not known or thought to be coerceable.

My callback function consistently fails on the second call: any ideas on how to overcome this? (I'm using C#)

It is time to learn how to use a debugger. Also, interfaces between the .Net CLR execution context and native code are generally non-trivial. More study is indicated. My guess is that you have botched a memory ownership issue. However, this subtopic is truly off-topic in this forum.

(3) By anonymous on 2021-01-17 16:26:53 in reply to 1 [link] [source]

I figured out this one:

4. My callback function consistently fails on the second call: any ideas on how to overcome this? (I'm using C#)

The reason is:

If an sqlite3_exec() callback returns non-zero, the sqlite3_exec() routine returns SQLITE_ABORT without invoking the callback again and without running any subsequent SQL statements.

My callback was returning 1; however, returning 0, I encounter the same error on the 6th iteration irrespective of the number of columns in the result. (... more to do with my code).

(4) By anonymous on 2021-01-22 10:57:50 in reply to 1 [link] [source]

Is there an sqlite3 API that the callback function should invoke before returning 0?

Irrespective of the number of columns in my query, I am getting

Attempted to read or write protected memory.

after some records i.e. sqlite3_exec is failing with that error before reaching the last record that my query returns.

(5) By anonymous on 2021-01-22 15:06:38 in reply to 4 [link] [source]

Irrespective of the number of columns in my query, I am getting

Attempted to read or write protected memory.

It may be time to use the debugger to get a backtrace of where this error happens. Speculating on numbers of sqlite3_exec callbacks isn't likely to get useful results.

(6) By anonymous on 2021-01-22 16:02:12 in reply to 5 [link] [source]

It may be time to use the debugger to get a backtrace of where this error happens.

I did think of that but I have this feedback upon hitting the error:

Source=<Cannot evaluate the exception source>

I'm using the pre-compiled binary (rather than compiling my own version).

(7) By Larry Brasfield (LarryBrasfield) on 2021-01-22 17:30:08 in reply to 6 [link] [source]

You will generally need a debug build to conveniently debug code at the source level. But that is unlikely to be of much help because this is what you will find: The SQLite library is suffering an address fault because it has been given a trashed heap and is susceptible to the GIGO principle. The heap is almost certainly being taken from its pristine (not-garbage) initial condition to a trashed condition by effects flowing from your code or possibly other portions of the application using the SQLite library.

I suggest you get your callback scheme working with some simple data type that does not require heap allocation (such as floats or integers), and only once you have that working make the transition to passing strings or blobs through your native-mode / coddled-execution interface. There is far less opportunity to be using bad pointers with the simple data types.

(8) By Keith Medcalf (kmedcalf) on 2021-01-22 18:28:52 in reply to 1 [link] [source]

The callback function is executed as many times as there are rowsin the result. Correct?

Yes.

The callback function returns the column names every time it is called? Correct?

Yes.

If affirmative, isn't this superfluous?

No.

How does the callback function handle the different types in the result columns? (Int, Float etc)?

It does not. All values are converted to text strings. This interface is designed for "primitive applications"

My callback function consistently fails on the second call: any ideas on how to overcome this? (I'm using C#)

Are you attempting to "hang onto a pointer" after your callback returns? You cannot do this. The pointer arrays received for the data and colnames are only valid for the duration of the execution of the callback. They are invalid once you return from the callback function. If you want to access the data after the callback is complete or after sqlite3_exec is complete, you need to copy it somewhere else that you control. The same thing applies to the contents of the array of pointers and the data itself. You should not be attempting to modify that which you do not own.

(9) By anonymous on 2021-01-22 21:15:55 in reply to 8 [link] [source]

Are you attempting to "hang onto a pointer" after your callback returns?

No.

My SQL is:

select * from employees; //chinook.db; table employees has 15 columns and 8 rows

My callback function gets the data correctly to start with, as follows (showing first row below)

names values
{string[15]} {string[15]}
[0]: "EmployeeId" [0]: "1"
[1]: "LastName" [1]: "Adams"
[2]: "FirstName" [2]: "Andrew"
[3]: "Title" [3]: "General Manager"
[4]: "ReportsTo" [4]: null
[5]: "BirthDate" [5]: "1962-02-18 00:00:00"
[6]: "HireDate" [6]: "2002-08-14 00:00:00"
[7]: "Address" [7]: "11120 Jasper Ave NW"
[8]: "City" [8]: "Edmonton"
[9]: "State" [9]: "AB"
[10]: "Country" [10]: "Canada"
[11]: "PostalCode" [11]: "T5K 2N1"
[12]: "Phone" [12]: "+1 (780) 428-9482"
[13]: "Fax" [13]: "+1 (780) 428-3457"
[14]: "Email" [14]: "andrew@chinookcorp.com"

I've got the 15 column names and values, including the null value for ReportsTo. The values are literals, as you've pointed out.

My callback function writes the data it receives to a file, its content as follows:

1-0:EmployeeId=1
1-1:LastName=Adams
1-2:FirstName=Andrew
1-3:Title=General Manager
1-4:ReportsTo=
1-5:BirthDate=1962-02-18 00:00:00
1-6:HireDate=2002-08-14 00:00:00
1-7:Address=11120 Jasper Ave NW
1-8:City=Edmonton
1-9:State=AB
1-10:Country=Canada
1-11:PostalCode=T5K 2N1
1-12:Phone=+1 (780) 428-9482
1-13:Fax=+1 (780) 428-3457
1-14:Email=andrew@chinookcorp.com
2-0:EmployeeId=2
2-1:LastName=Edwards
2-2:FirstName=Nancy
2-3:Title=Sales Manager
2-4:ReportsTo=1
2-5:BirthDate=1958-12-08 00:00:00
2-6:HireDate=2002-05-01 00:00:00
2-7:Address=825 8 Ave SW
2-8:City=Calgary
2-9:State=AB
2-10:Country=Canada
2-11:PostalCode=T2P 2T3
2-12:Phone=+1 (403) 262-3443
2-13:Fax=+1 (403) 262-3322
2-14:Email=nancy@chinookcorp.com
3-0:EmployeeId=3
3-1:LastName=Peacock
3-2:FirstName=Jane
3-3:Title=Sales Support Agent
3-4:ReportsTo=2
3-5:BirthDate=1973-08-29 00:00:00
3-6:HireDate=2002-04-01 00:00:00
3-7:Address=1111 6 Ave SW
3-8:City=Calgary
3-9:State=AB
3-10:Country=Canada
3-11:PostalCode=T2P 5M5
3-12:Phone=+1 (403) 262-3443
3-13:Fax=+1 (403) 262-6712
3-14:Email=jane@chinookcorp.com

The first number is the record number (index 1), the second number is the column number (index 0) , followed by the column name and column value.

Only 3 of 8 rows come to the callback function. Then I hit this error:

Message=Attempted to read or write protected memory. This is often an indication that other memory is corrupt. Source=<Cannot evaluate the exception source>

1.I've tried with other tables with fewer and more columns and records (in case it was a buffer issue) and meet the same fatal error.

2.I've also experimented with adding delays (in case there was a timing issue) in the callback function but it makes no difference.

3.I am using the pre-compiled 32-bit 3.34 version. I tried with 3.26 - makes no difference.

Since my code works for 3 records, it is probably 'correct'.

I've run out of options for debugging the reason for the error I am getting after 3 rows. Hopefully, someone can share their insight so I can solve this problem.

When I call sqlite3_exec from my code, on which line in sqlite3.c does it land?

(10) By Larry Brasfield (LarryBrasfield) on 2021-01-23 02:15:53 in reply to 9 [link] [source]

Since my code works for 3 records, it is probably 'correct'.

That is nowhere close to true. However, it does suggest a debugging strategy. After each record, do a heap integrity check.

I must also submit that your definition of "works" is far too narrow. Maybe your code produces the results you expect 3 times, but it clearly causes some degradation of something, such that successive calls have a lower probability of satisfying even your weak kind of "works".

I've run out of options for debugging the reason for the error I am getting after 3 rows. Hopefully, someone can share their insight so I can solve this problem.

Well, I already suggested an approach. How did that work out? The result may very well show you that whether "it works" depends on something in your code.

You did not answer Keith's question, "Are you attempting to 'hang onto a pointer' after your callback returns?" This leads me to suspect you do not know what he means or why that programming sin should be among your chief suspects. (The values you cite in your not-quite-an-answer to him are not literals.)

When I call sqlite3_exec from my code, on which line in sqlite3.c does it land?

If you can find "sqlite3_exec(" in sqlite3.c, you will find instances of that text in 4 categories: (1) Inside of comments; (2) A forward declaration; (3) In other code calling into sqlite3_exec(); and (4) A definition of sqlite3_exec(...). When you call it, that last is where your call "lands".

I feel compelled to note that somebody who cannot look at sqlite3.c to find a function definition is unlikely to be able, absent extremely compelling evidence, to correctly diagnose that a bug lies outside his own code and hence must be in somebody else's code. The sqlite3_exec() API is used successfully by many thousands of SQLite library users, and the whole library API is tested extensively for every release and intervening code drop. The overwhelming likelihood is that your callback is doing something that cannot work reliably. If it passes around soon-to-be-stale pointers to dynamically allocated memory, to be used after they are in fact stale, (such as Keith and I suspect leading to our suggestions), that is what you must stop doing.

Your debugging effort only going to be hindered by your wish to absolve your own code. That is an attitude you would do better to shed. Your goal during debugging is to figure out what is going wrong, with enough detail that it is reasonable to decide what code is violating its (explicit or implicit) contract. Only then can you proclaim the location of bug. You are nowhere close to that point, and having counted loops before a crash is not a sign of being close.

(11) By anonymous on 2021-01-23 07:43:07 in reply to 10 [link] [source]

Your very long response does nothing absolutely nothing to help me search for a resolution.

I am not, not even attempting to, absolve(ing) my code. That's why I put correct in quotes. Clearly there is an issue, somewhere.

You asserted:

You did not answer Keith's question, "Are you attempting to 'hang onto a pointer' after your callback returns?"

I suggest you re-read the opening paragraph in my response.

My question was specific:

When I call sqlite3_exec from my code, on which line in sqlite3.c does it land?

Your response does not help.

I am looking for hints to help me rule out the following:

1.That it is not my DLLImport code (my investigation is still ongoing)

2.That it is not my Callback code (my investigation is still ongoing)

that is the source of the problem. On balance, since I am getting 3 records back with what I have suggests that the problem is more subtle.And I am not even thinking that it is anything to do with SQLite yet; that is why I've tried with several versions thereof.

(12) By Ryan Smith (cuz) on 2021-01-23 12:09:06 in reply to 9 [link] [source]

Please just show the actual code of the callback and any other code you have in that project that ever touches the DB connection.

So far you've been experiencing some frustration feedback because your posts have all been: "I have a black box, when I put this SQL into it, an egg comes out. Sometimes the egg is broken, specifically, the fourth egg - what is the problem?"

There are three possibilities -

  1. You are lying (unintentionally for sure) and your code does something that some C# programmer on here will recognize as the error.
  2. Your code is perfectly fine but C# or the Wrapper does something weird, which someone on here can test easily when they can reproduce your code on their machines.
  3. SQLite is broken since not many people use that specific interface and it may have gone unnoticed and your code has finally shown the error, in which case being able to set up your code on our side would help in letting us debug the problem and fix SQLite.

Can you see the general theme here? We need to see the code. Your explanations may have felt complete to you because you are privy to your own code, but for us it is all staring at a black box and playing twenty questions with you to find the problem.

Fun as that is, the result will be much quicker when we can just see the code.

PS: It would be "nice" if you can whittle down the code to just a few lines that still produces the error, but we will be happy to look at it either way.

(13) By anonymous on 2021-01-23 12:29:18 in reply to 12 [link] [source]

PS: It would be "nice" if you can whittle down the code to just a few lines that still produces the error, but we will be happy to look at it either way.

Please refer here for my code (also, note the response/advice the callback interface is rarely the best way to do something even in C.

(14) By Kees Nuyt (knu) on 2021-01-23 15:23:35 in reply to 13 [link] [source]

Please refer here for my code (also, note the response/advice the callback interface is rarely the best way to do something even in C.

That stackoverflow post only shows the function headers, not the actual processing code.

(15) By anonymous on 2021-01-23 16:28:03 in reply to 14 [link] [source]

That you ask for the actual code raises doubts in my mind; however, to save time:

using System;
using System.Runtime.InteropServices;
using System.Text;

namespace ConsoleApp1
{
    class Program
    {
        [DllImport("sqlite3.dll", EntryPoint = "sqlite3_exec", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        static extern int sqlite3_exec(IntPtr dbHandle, byte[] sql, Callback callback, string args, out IntPtr errmsg);
        [DllImport("sqlite3.dll", EntryPoint = "sqlite3_open", CallingConvention = CallingConvention.Cdecl)]
        static extern int sqlite3_open(string filename, out IntPtr dbPtr); //[MarshalAs(UnmanagedType.LPTStr)]
        [DllImport("sqlite3.dll", EntryPoint = "sqlite3_close_v2", CallingConvention = CallingConvention.Cdecl)]
        static extern int sqlite3_close_v2(IntPtr dbHandle);//        int sqlite3_close_v2(sqlite3*);
        internal delegate int Callback(IntPtr p, int n, [In] [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr, SizeParamIndex = 1)]  string[] names, [In][MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr, SizeParamIndex = 1)] string[] values);

        static void Main(string[] args)
        {
            Callback IR = new Callback(IterateResults);
            IntPtr dbHandle;
            IntPtr errMsg = IntPtr.Zero;
            if (0 == sqlite3_open(@"d:\sqlite32\db\chinook.db", out dbHandle))
            {
                int rc = sqlite3_exec(dbHandle, Encoding.Default.GetBytes(@"select * from employees;"), IR, "First Argument of callback", out errMsg);
                // CAllback on previous line failes!!!
            }
            else
            {
                Console.WriteLine("failed to open database");
            }
        }
        public static int IterateResults(IntPtr unused, int n, string[] values, string[] names)
        {
            for (int i = 0; i < n; i++)
            {
                Console.WriteLine($"{names[i]}={values[i]}");
            }
            return 0;
        }
    }
}

This is a Console Application; using Visual Studio 2017, tested the build with framework 4.7.2, Platform target Any CPU and Prefer 32-bit enabled. If you need to know how to reference SQLite3.DLL version 3.34, see thread [3] here.

(16) By Larry Brasfield (LarryBrasfield) on 2021-01-23 17:22:44 in reply to 11 [link] [source]

Your very long response does nothing absolutely nothing to help me search for a resolution.

I can see that. The "search" you intend must exclude using heap checking tools or changing your code to see whether use of simpler data types correlates with your problem. Good luck with that "search"; you will need it.

You did not answer Keith's question, "Are you attempting to 'hang onto a pointer' after your callback returns?"

I suggest you re-read the opening paragraph in my response.

I did read it and found a simple "No." followed by an elaboration of facts unrelated to the question. It was just as if you answered a different question.

When I call sqlite3_exec from my code, on which line in sqlite3.c does it land?

Your response does not help.

The line number might be 116761 if your SQLite version is 3.25. Does that help? Or do you expect some shoemaker's elf to provide a table with the line number you demand for every version of SQLite you might be using?

I provided guidance for finding that line in whatever version of sqlite3.c you happen to have. To say that does not help shows that your notion of what will be useful in your quest is extremely limited -- so limited that I doubt you will find it here or at stackoverflow.

On balance, since I am getting 3 records back with what I have suggests that the problem is more subtle

More subtle than what? More subtle than the effects of heap corruption?

To any experienced programmer, the fact that you see 3 (or 6 or 2) "successful" callback executions followed by an address fault when sqlite3_exec() is called suggests that the SQLite library code has been asked to use a corrupted heap, and that corruption has led to an address fault when the heap manager attempts to use the corrupted heap data structure. As many thousands of test cases show and many thousands of the library's users know, the library is pretty good about not corrupting the heap. Hence, it is reasonable to suspect that your code is corrupting the heap. Yet you are immune to doing the simple work needed to ascertain whether or not that is happening. Too much work, or beyond your ken, I suppose. Better to see if somebody else has a solution.

And I am not even thinking that it is anything to do with SQLite yet; that is why I've tried with several versions thereof.

Strange. Several versions of your failing code would be a better experiment. For example, if your callback, (which you have not revealed, even at the stackoverflow site where your plea for help was also made), does nothing with the data passed to it (by indirect reference), does the address fault still occur? Does it still occur if the callback makes only deep copies of the data? Is the heap intact across your sqlite3_exec() calls? Is it intact across execution of your callback? Inserting some diagnostics would be much more fruitful than trying different versions of sqlite3.c and hoping that matters.

Ryan has given you good advice for helping others to help you. And as Kees noted (and I confirm), you tried but failed to do that. Is this because you do not know what code is actually running when your callback is called? Or are you simply hopeful that with a few seconds more work posting that link that somebody is going to spot your bug?

Given what I see of your programming skill outside of the nice, managed execution environment that C# provides, I think you would be way ahead to use System.Data.SQLite and its SQLiteDataReader class to pull data from your SQLite database. Clearly, you are not yet knowledgeable enough about using C to be using the Native Code interfacing capability. (If you were, you would not be asking others to find a function definition for you in sqlite3.c .)

(17) By anonymous on 2021-01-23 17:59:22 in reply to 16 [link] [source]

I think you would be way ahead to use System.Data.SQLite

We've discussed this.

System.Data.SQLite is way behind in terms of SQLite3 releases & it has a very large footprint (code- and dependency- wise).

I want to be in control of which version of SQLite3 I use; that way,

  • when 3.35 comes along, I 'll have all the new SQL functions available.
  • I am in control of what functionality I deliver and can choose how I do that.

(18) By anonymous on 2021-01-23 18:10:28 in reply to 16 [link] [source]

Clearly, you are not yet knowledgeable enough about using C

Very true.

One lifetime is not enough to learn everything.

Besides, the whole point of SQLite3.DLL is that I do NOT need to know its inner workings (however much that might help) when using it via its exposed interface i.e. its published APIs.

(I wish the SQLite3 documentation was a little less terse & provided worked examples but I imagine that that might be impossible given the huge number of clients that use it on all diverse platforms.)

It is mostly difficult UNTIL you know how.

It is easy when you know how but then beginners' questions appear tiresome as hinted by forum responses.

(19) By Larry Brasfield (LarryBrasfield) on 2021-01-23 21:26:28 in reply to 15 [link] [source]

That you ask for the actual code raises doubts in my mind;

I cannot imagine why, so it would be educational for you to say why.

You might be interested to know that when I substitute your code into a demo .Net Core console application targeting 'Any CPU', change the DB and table name literals to match some databases I have laying around, and put a 64-bit sqlite3.dll next to the .exe, then I can build the app and run it without any address faults, including blatting out a table with 22588 rows.

Is that code in your post 15 what is actually producing address faults for you?

(20) By anonymous on 2021-01-23 22:43:51 in reply to 19 [link] [source]

so it would be educational for you to say why.

I thought that anyone willing or capable of troubleshooting code will be able to create the code without any problems.

On the other hand, it would make sense to use the same code as myself; therefore, the request for code is valid.

I can build the app and run it without any address faults

Thank you very much for doing this and relaying the outcome. As I've mentioned somewhere above, I've run out of options for fixing the error I'm encountering

I am using Console App(.Net Framework) and 32-bit SQLite3.DLL.

I'll try to re-create your setup (.Net Core & 64-bit SQLite3) and report back if I can also re-create your outcome, namely, success!

Switching to .Net Core is a sound idea but I need to be working with 32-bit SQLite3 my reasons will be clearer when I provide feedback ... more soon.

(21) By anonymous on 2021-01-23 23:19:01 in reply to 19 [link] [source]

Larry, I switched to 64-bit SQLite3.DLL (still using .Net Framework) and was able to replicate your success with a table containing 15,000 records. (And it feels good!)

I've raised a new topic SQLite3 v3.34 ANOMALY - Precompiled Binaries for Windows for investigating this anomaly.

(22) By anonymous on 2021-01-23 23:24:17 in reply to 19 [link] [source]

I omitted to respond to this earlier:

Is that code in your post 15 what is actually producing address faults for you?

Yes (with the 32-bit SQLite3). No fault with 64-bit SQLite3.

(23) By Larry Brasfield (LarryBrasfield) on 2021-01-24 01:54:20 in reply to 20 [source]

so it would be educational for you to say why [asking for code "raises doubts"].

I thought that anyone willing or capable of troubleshooting code will be able to create the code without any problems. On the other hand, it would make sense to use the same code as myself; therefore, the request for code is valid.

Someone capable of seeing what is wrong with some code would likely create code that did not fail, unless they intended to create a specific bug. However, creating an address fault can be done in so many different ways that it would be pure chance if such creation happened to match how some unseen code did so. This is why "show the code" is so much preferred over summary descriptions.

I can build the app and run it without any address faults

Thank you ... I am using Console App(.Net Framework) and 32-bit SQLite3.DLL

This might be an parameter marshaling problem, or it could be simply that you have not yet told the auto-magic marshaling builder enough that it knows what to expect. [a] A diligent perusal of the Native Code interfacing docs is indicated. I doubt that your problem is a simple bug in the .Net marshaling code or C# compiler. Debugging at the assembler level would likely be revealing as to what is going wrong, but not how to fix it. Debugging at that level for the working and failing versions, with cross-comparison, would be more interesting.

[a. I was surprised at how convenient marshaling setup has become since I last had to do it. Maybe it is not quite as easy as it looks in that post 15 code except when certain defaults are correct. ]

Switching to .Net Core is a sound idea but I need to be working with 32-bit SQLite3 my reasons will be clearer when I provide feedback ... more soon.

I am not advocating .Net Core or use of a 64-bit DLL, at least not here. I was trying to get defaults more likely to be favorable because, while I did not see anything wrong popping out of the Delegate declaration, I was less sure as to what might be missing. To me, it seemed too easy.

(24) By anonymous on 2021-01-24 08:04:47 in reply to 23 [link] [source]

or it could be simply that you have not yet told the auto-magic marshaling builder

The 32-bit problem manifests itself even when I am not using marshalling to get the string values - I pass a single pointer to the callback and (minimally) use something like this:

{
            string[] values = new string[n];
            string[] names = new string[n];          
            int ptr_size = Marshal.SizeOf(typeof(IntPtr));
            for (int i=0; i<n; i++)
            {
                IntPtr vp;

                vp = Marshal.ReadIntPtr(values_ptr, i * ptr_size);
                values[i] = util.from_utf8(vp);

                vp = Marshal.ReadIntPtr(names_ptr, i * ptr_size);
                names[i] = util.from_utf8(vp);
            }

where values_ptr is a single pointer for values & the corresponding pointer for column names is names_ptr; n is the number of columns.

(25.1) By Larry Brasfield (LarryBrasfield) on 2021-01-25 23:11:16 edited from 25.0 in reply to 24 [link] [source]

Please examine the following code, then say whether you want to continue asserting that there is something amiss worth anybody else's investigation. You may also want to read about the UnmanagedFunctionPointerAttribute.

using System; using System.Runtime.InteropServices; namespace ConsoleApp2 { class Program { const string dbLib = "sqlite3.dll";

 [DllImport(dbLib, EntryPoint = "sqlite3_exec", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
 static extern int sqlite3_exec(IntPtr dbHandle, [In][MarshalAs(UnmanagedType.LPStr)] string sql,
                                Callback callback, IntPtr arbArg, ref IntPtr errmsg);

 [DllImport(dbLib, EntryPoint = "sqlite3_open", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
 static extern int sqlite3_open([In][MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr dbPtr);

 [DllImport(dbLib, EntryPoint = "sqlite3_close_v2", CallingConvention = CallingConvention.Cdecl)]
 static extern int sqlite3_close_v2(IntPtr dbHandle);

 [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
 internal delegate int Callback(IntPtr p, int n,
            [In][MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr, SizeParamIndex = 1)] string[] names,
            [In][MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr, SizeParamIndex = 1)] string[] values);

 static int callCount = 0;

 static void Main(string[] args)
 {
    Callback IR = new Callback(IterateResults);
    IntPtr dbHandle;
    IntPtr errMsg = IntPtr.Zero;

    if (args.Length < 2)
    {
        Console.WriteLine("Provide DB filename and a table name as arguments.");
    }
    else if (0 == sqlite3_open(args[0], out dbHandle))
    {
        int rc = sqlite3_exec(dbHandle, @"select * from " + args[1], IR, dbHandle, ref errMsg);

        Console.WriteLine($"exec return: {rc}");
        sqlite3_close_v2(dbHandle);
    }
    else
    {
        Console.WriteLine("failed to open database");
    }
 }
 public static int IterateResults(IntPtr unused, int n, string[] values, string[] names)
 {
    ++callCount;
    for (int i = 0; i < n; i++)
    {
        Console.WriteLine($"{names[i]}[{callCount}]={values[i]}");
    }
    return 0;
 }

} }