Using C# classes in Xojo

Hi James,

A good place to start would be: http://www.xojo.com/blog/en/2013/12/build-automation.php

See the User Guide - Framework
http://www.xojo.com/download/extras.php

Just another vote for possibly the best forum thread of the year;) These methods of creating .NET dllā€™s have been very handy of late.

I do have a problem with deployment though. My newly created dll works fine on my development machine with ā€˜Register for com interopā€™ selected but on a client pc Iā€™m having a lot of problems.

Even using REGASM, Iā€™m finding that my dll isnā€™t working. It has some dependant dllā€™s and Iā€™m sure this is the case, but to date, without installing visual studio on the clients machine Iā€™m having problems registering. Is there a simple way to determine what isnā€™t being found as far as dependencies are concerned?
Cheers,
James

James,

Check out . This might give you some insight into what you are missing when you deploy.

  • jim

Thanks Jim,

Iā€™ve tried DW, but didnā€™t have much success. Iā€™ll revisit again!

J

James,

Just a thought, if your deployment target is XP, the .net framework itself may need to be installed.

  • jim

Hey Jim,

Yeah, Iā€™m lucky enough that everything is Win7 now;)

Following on kind of on topic:)

I gave up for now on the deployment of a Com Object (ActiveX) at around 2am. At 3am I had a fully working system using Robert Gieseckeā€™s unmanaged exports (Thanks Gary!).

I did have a few problems with the classes having to be set as static but I got around that by having the externally visible classes set to static and then having these call my complex multi class internal routines. Happy to post code if anyone stumbles across this thread in the future and requires it. Just let me know.

So onto question 2:
Iā€™m using soft declares to connect to my shiny new .dll. They work fine, but are not being released on app quit. If I try to build or run again, the app will not load unless I log out and back in again. Is there a way to force release the dll on app quit?

Question 3 is another curly one relating to thread safety. Iā€™m hoping to call this dll from a few places in my program, but Iā€™m finding that it isnā€™t thread safe and Iā€™m getting unintended stuff happening. Do I need to make my dll thread safe of is there a way in Xojo to protect it? If I run two apps at the same time accessing the dll it works fine.

Thanks again guys. Hopefully this tread is adding some value to the body of knowledge. I know itā€™s been a steep learning curve for me!

James,

Threading ā€œin processā€ is not easy outside of the safe threading model that Xojo provides. If you wish to spawn threads inside a DLL , you must terminate those threads gracefully when your Xojo application terminates otherwise you end up with all sorts of chaos and odd behavior.

Without seeing your source code, I canā€™t tell you exactly what the issue is but having done this myself, on multiple projects, my gut feeling is that you need to look at how you are dealing with this.

Regards,

Jim

Hey Jim,

I guess to answer your question, Iā€™m not dealing with it;) Iā€™m running into the problem of not threading (I guess). Iā€™m starting to think that I need to multithread however Iā€™m not sure that my dll requires it or maybe Iā€™m just writing dodgy code!

The biggest problem I have is that when I quit my app it isnā€™t releasing the dll. Ie, I canā€™t rename, move or reopen my program because the dll is in use!

Any suggestions?

I should point out that is only seems to happen in the debugger.

For those of you playing at home, Iā€™ve attached my C# dll in the hope that it may assist another newbie as I was/am in the C# syntax. Iā€™m new to C# (a few days old now;) )
You can see Iā€™m using a static class called using the DLL export command (Connect_Camera). This class creates a new instance of my dynamic classes. Hope this makes sense but at the end of the day my dll connects to a specific IP camera.

Iā€™m connecting to this dll using a standard soft declare and sending a window handle from my Xojo app that enables me to view live footage within Xojo. Kinda neat!

The only remaining issue I have is that when I call this dll twice from the same application, I get all sorts of issues. Mainly pictures switching between multiple cameras etc, hence my question on how to thread. In the short term Iā€™m thinking of compiling multiple dllā€™s with different names in the hope that this will workā€¦

Any suggestions on my dodgy code appreciated;)

Cheers,
James

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RGiesecke.DllExport;
using System.Runtime.InteropServices;
using NETSDKDLL_DOTNET;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Forms;

    
    public class TestExport
{
    public int nLoginId;
    public int nPortId;
    public int lRealStreamId;
    private fRealDataCallBack fReadlCallBack;
    public bool haveLogin;
    private fDecCallBackFunction fDecodecall;
    public string datain;
    public int junk;
    public string pXml;
    public int Saved_Login;

        [DllExport]

        public static string Connect_Camera(string IP_addr, short Port_in, string User_in, string Pass_In, int window_handle, int channel, int flip)
        {
           int temp1;
           TestExport TestExport = new TestExport();
           temp1 = TestExport.Login(IP_addr, Port_in, User_in, Pass_In);
           System.Threading.Thread.Sleep(300);              // not sure what i can get away with here
           TestExport.Play(window_handle, 1);

           return "Win!";

        }
           
        public int Login(string ip_addr, short port_in, string user_in, string pass_in)
        {
            try
            {

                nLoginId = 5;          //this may fix issue of memory leak by killing the connection each time through a call make non zero

            //    MessageBox.Show("callback yes");
                //CString strXML;
                PLAYERDLL.IP_TPS_Init();
                NETSDKDLL.IP_NET_DVR_Init();
                this.fDecodecall = new fDecCallBackFunction(this.OnDecCallBackFunction);
                this.fReadlCallBack = new fRealDataCallBack(this.OnRealDataCallBack);
                NETSDKDLL.IP_NET_DVR_SetStatusEventCallBack(new StatusEventCallBack(this.OnStatusEventCallBack), IntPtr.Zero);
            //    MessageBox.Show("here");
                if (this.nLoginId != 0)
                {
                    NETSDKDLL.IP_NET_DVR_Logout(this.nLoginId);
                    this.haveLogin = false;
                }
                IP_NET_DVR_DEVICEINFO lpDeviceInfo = new IP_NET_DVR_DEVICEINFO();
                this.nLoginId = NETSDKDLL.IP_NET_DVR_Login(ip_addr, port_in, user_in, pass_in, ref lpDeviceInfo);
                Saved_Login = this.nLoginId;
            }
            catch
            {
                MessageBox.Show("error in login");
            }
            return nLoginId;
        }

        public int Play(int hwnd, int channel_in)
       {
            try
            {
              //  nLoginId = loginID;
                lRealStreamId = 5;   //set these to non zero to try to kill existing streams before connecting a new one may need to be managed
                nPortId = 5;
                if (this.nLoginId == 0)
                {
                   MessageBox.Show("please login to the device first!");
                }
                else if (!this.haveLogin)    //  might need to manage this myself
                {
                    MessageBox.Show("device online state error,please play video after login device!");
                }
                else
                {

                    if (this.lRealStreamId != 0) 
                    {
                        NETSDKDLL.IP_NET_DVR_StopRealPlay(this.lRealStreamId);
                    }

                    if (this.nPortId != 0)
                       
                    {
                        PLAYERDLL.IP_TPS_CloseStream(this.nPortId);
                    }
                  
                    IP_NET_DVR_CLIENTINFO lpClientInfo = new IP_NET_DVR_CLIENTINFO();
                    USRE_VIDEOINFO pUser = new USRE_VIDEOINFO();

                    lpClientInfo.lChannel = 1;
                    pUser.nVideoPort = 0x22a;
                    pUser.nVideoChannle = channel_in;
                    int handle = hwnd;
                    pUser.pUserData = (IntPtr)handle;//this.pictureBox1.Handle;
                    this.nPortId = 1;
                    this.lRealStreamId = NETSDKDLL.IP_NET_DVR_RealPlay(this.nLoginId, ref lpClientInfo, this.fReadlCallBack, ref pUser, 1);

                    if (this.lRealStreamId == 0)
                    {
                        MessageBox.Show("please login to the device first!");
                    }
                }
            }
            catch
            { MessageBox.Show("error in play"); }
            return lRealStreamId;
        }


        public int OnDecCallBackFunction(long nPort, IntPtr pBuf, long nSize, ref FRAME_INFO pFrameInfo, IntPtr pUser, long nReserved2)
        {
         //   MessageBox.Show("callback 1");
            return 0;
        }

        public int OnRealDataCallBack(int lRealHandle, int dwDataType, IntPtr pBuffer, int dwBufSize, ref FRAME_EXTDATA pExtData)
        {

         //   MessageBox.Show("callback 3");
            IntPtr pUserData = pExtData.pUserData;
            if (dwDataType == 0)
            {
                return PLAYERDLL.IP_TPS_InputVideoData(1, pBuffer, dwBufSize, pExtData.bIsKey, (int)pExtData.timestamp);
            }
            if ((dwDataType != 1) && (dwDataType == 2))
            {
                STREAM_AV_PARAM stream_av_param = new STREAM_AV_PARAM();
                stream_av_param = (STREAM_AV_PARAM)Marshal.PtrToStructure(pBuffer, stream_av_param.GetType());
                int pSize = Marshal.SizeOf(typeof(VIDEO_PARAM));
                IntPtr ptr = Marshal.AllocHGlobal(0x2800);
                Marshal.StructureToPtr(stream_av_param.videoParam, ptr, false);
                PLAYERDLL.IP_TPS_OpenStream(1, ptr, pSize, 0, 40);
                Marshal.FreeHGlobal(ptr);
                short bHaveAudio = stream_av_param.bHaveAudio;
                PLAYERDLL.IP_TPS_Play(1, pUserData);
            }
            return 0;
        }

        public int OnStatusEventCallBack(int lUser, int nStateCode, IntPtr pResponse, IntPtr pUser)
        {
       //     MessageBox.Show("callback 2");
            //  Debug.WriteLine("test");
            if (nStateCode == 4)
            {
                this.haveLogin = true;
            }
            else if ((nStateCode == 5) || (nStateCode == 0x1f))
            {
                this.haveLogin = false;
            }
            return 0;
        }
    }

[

EDIT : fixed code closing tag

Going to be using this method as we move a large project from RB to .net. As we will be doing it in stages, needed a way to mix new code & windows developed in .net with old code in the RB project this was the perfect find. The example was done in C# so I thought I would include the vb.net equivalent:

[code]Imports System
Imports System.Windows.Forms
Imports System.Runtime.InteropServices

<InterfaceType(ComInterfaceType.InterfaceIsDual)>
Public Interface XojoManagedInterface
<DispId(1)>
Function SayHi(Name As String) As Integer
Sub HelloXojo()
End Interface

<ClassInterface(ClassInterfaceType.None)>
Public Class JordansXojoCode : Implements XojoManagedInterface

Public Function SayHi(Name As String) As Integer Implements XojoManagedInterface.SayHi
    MsgBox("hello " + Name)
    Return 0
End Function
Public Sub HelloXojo() Implements XojoManagedInterface.HelloXojo
    MsgBox("Hello xojo from VB.NET!")
End Sub

End Class[/code]

Going to be using this method as we move a large project from RB to .net. As we will be doing it in stages, needed a way to mix new code & windows developed in .net with old code in the RB project this was the perfect find. The example was done in C# so I thought I would include the vb.net equivalent:

[code]Imports System
Imports System.Windows.Forms
Imports System.Runtime.InteropServices

<InterfaceType(ComInterfaceType.InterfaceIsDual)>
Public Interface XojoManagedInterface
<DispId(1)>
Function SayHi(Name As String) As Integer
Sub HelloXojo()
End Interface

<ClassInterface(ClassInterfaceType.None)>
Public Class JordansXojoCode : Implements XojoManagedInterface

Public Function SayHi(Name As String) As Integer Implements XojoManagedInterface.SayHi
    MsgBox("hello " + Name)
    Return 0
End Function
Public Sub HelloXojo() Implements XojoManagedInterface.HelloXojo
    MsgBox("Hello xojo from VB.NET!")
End Sub

End Class[/code]

[quote=49657:@Jim Cramer]Ohh yes! Iā€™ve used this technique many times in the past to get access to .NET goodiesā€¦:slight_smile:

There is another way to do this that I havenā€™t explored yet:

https://www.nuget.org/packages/UnmanagedExports

Essentially this turns any .NET DLL into something we can call with standard XOJO decalresā€¦looks interesting.

Regards,

Jim[/quote]

This made my year. Thank you! :slight_smile:

Sorry if I revive a two years old thread. Iā€™m in the process of trying to convert an existing c# .net dll library for reuse into XOJO. Iā€™m new to this and Iā€™m trying to inspect the possibility to migrate my codebase from vb/c# to XOJO.
Iā€™m a newbie, so please be gentle with me.

Iā€™ve got a class which contains some methods but also some constructors, for examble:

[code] public PortClient(string serialPort)
{
this.serialport = new SerialPort();
serialport.PortName = serialPort;
serialport.BaudRate = baudRate;
serialport.Parity = parity;
serialport.StopBits = stopBits;
serialport.WriteTimeout = 10000;
serialport.ReadTimeout = connectTimeout;

        serialport.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);      
    }[/code]

I canā€™t seem to be able to export the DLL to with UnmanagedExports as the directive [DllExport] can only be used for methods, but not for constructors. I know that I would probably need to contact Giesecke, the author of UnmanagedExports, but I also thought somebody already solved this in a clever way I didnā€™t.

Thanks

This technique is not intended to obtain and handle DotNet objects at Xojo side, theyā€™re not compatible. Anything related to a DotNet object must be done at DotNet side, and can be handled by exported static methods. So, in theory, you can create and store your C# objects in some DotNet static property, like an array of objects if you need more than one, or just a property object of such type, using some static Factory Function like ā€œpublic static int CreateMyPortClient(string serialPort)ā€ that will create a new SerialPort(), set everything, store it in an array of SeriaPort()'s, give its int index back to be used as a handle in other static calls at Xojo side, like, e.g. ChangeBaudRate(handleIndex, newBaudRate), something like this. The entire ā€œengineā€ you will have to take care of. I hope you understand what I mean.

You are telling me that I canā€™t reference a dll and consume it for creating objects at runtime?
it would be better to rewrite the whole library class in xojo?

[quote=470934:@Alessandro Mandelli]

You are telling me that I canā€™t reference a dll and consume it for creating objects at runtime?
it would be better to rewrite the whole library class in xojo?[/quote]

Well, I said one way of doing it. Maybe someone can have another solution that I donā€™t know.

Rewriting in Xojo is one option, but anything is up to you.

Iā€™ve been able to export the dll with the interfaced class as per first suggestion in this thread.

using System;
using System.Net.Sockets;
using System.Net;
using System.IO.Ports;
using System.Reflection;
using System.Text;
using System.Collections.Generic;
using System.Runtime.InteropServices;

[Guid("f5f505ff-635b-4cb0-8481-726a8353a78f")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IXojoManagedInterface
{
    int[] ConvertIntToRegisters(int intValue);
}
namespace XojoEasyModbus

{
    /// <summary>
    /// Implements a ModbusClient.
    /// </summary>
    /// 
    [Guid("017b1254-7c54-4bd3-8bc9-d84441791088")]
    [ClassInterface(ClassInterfaceType.None)]
    public partial class XojoModbusClient : IXojoManagedInterface
    {
        /// <summary>
        /// Converts 32 Bit Value to two ModbusRegisters
        /// </summary>
        /// <param name="intValue">Int value which has to be converted into two registers</param>
        /// <returns>Register values</returns>
        public int[] ConvertIntToRegisters(Int32 intValue)
        {
            byte[] doubleBytes = BitConverter.GetBytes(intValue);
            byte[] highRegisterBytes = 
            {
                doubleBytes[2],
                doubleBytes[3],
                0,
                0
            };
            byte[] lowRegisterBytes = 
            {
                
                doubleBytes[0],
                doubleBytes[1],
                0,
                0
            };
            int[] returnValue =
            {
                BitConverter.ToInt32(lowRegisterBytes,0),
                BitConverter.ToInt32(highRegisterBytes,0)
            };
            return returnValue;
        }
    }
}

This above is just a snippet.

The function is implemented in the COM Interop as this:

[code]#if TargetWin32
If mThis = Nil Then Raise New NilObjectException
Dim func As New ConvertIntToRegisters_Func2(mThis.Ptr( 0 ).Ptr(22 * COM.SIZEOF_PTR ))
Dim resultCode As Integer
Dim Return_pRetVal_Param As New MemoryBlock( COM.SafeArray.Size )
resultCode = func.Invoke(mThis, intValue_Param, Return_pRetVal_Param)
If resultCode = 0 Then
Dim retVal As COM.SafeArray
retVal.StringValue(True) = Return_pRetVal_Param.StringValue(0, COM.SafeArray.Size)
Return retVal
Else // Throw Exception
Raise New COM.COMException(ā€œFailed on ConvertIntToRegistersā€, resultCode)
End If

#endif[/code]

From within Xojo I implemented a method like this:

dim o as new XojoEasyModbus.XojoModbusClient dim temp as COM.SafeArray = o.ConvertIntToRegisters(123) dim content() as UInt8 = temp.ByteValue

Calling the function is performed without problems, the SafeArray contains data, but itā€™s meaningless.
Not only that, but the content of temp change every time the function is called.
Like:

Also why content is a 24bytes-array, as the return value from the dll is 8 bytes.

With the introduction of .Net Core C# you can build cross platform DLLā€™s. Does anyone know if there is a generic way from Xojo to call and utilize one of these libraries?

I did find this article:
https://docs.microsoft.com/en-us/dotnet/core/native-interop/expose-components-to-com

But would love to see if someone can throw together a sample Xojo project and .Net library working together