06-04-2021 02:47 PM - edited 06-04-2021 03:17 PM
Hello everyone,
I'm trying to use the DAQmx C library in Unity to receive signal from National Instruments hardware in Unity.
Because the C library is static and there is no .dll file, only .lib and header file, I created a wrapper dll that I chose which DAQmx method to integrate into Unity.
Currently I implemented the following methods:
DAQmxCreateTask(taskName, taskHandle);
DAQmxStopTask(taskHandle);
DAQmxStartTask(taskHandle);
DAQmxCreateAIVoltageChan(taskHandle, physicalChannel, nameToAssignToChannel, terminalConfig, minVal, maxVal, units, customScaleName);
DAQmxSetSampTimingType(taskHandle, data);
DAQmxReadAnalogF64(taskHandle, numSampsPerChan, timeout, fillMode, readArray, arraySizeInSamps, sampsPerChanRead, reserved);
DAQmxCfgSampClkTiming(taskHandle, source, rate, activeEdge, sampleMode, sampsPerChan);
DAQmxGetExtendedErrorInfo(errorString, bufferSize);
When I try to replicate this example "Acq-IntClk" in Unity, only my DAQmxCreateTask() call returned a 0, and when I call DAQmxCreateAIVoltageChan(); DAQmxCfgSampClkTiming(); DAQmxStartTask(); DAQmxReadAnalogF64(); they all returned error code -200088, which seemed to be this https://knowledge.ni.com/KnowledgeArticleDetails?id=kA00Z0000019QMjSAM&l=en-US , meaning "Task specified is invalid or does not exist."
Further more, when I try to use DAQmxGetExtendedErrorInfo() to get the error message, it doesn't seem to properly write value to the char[] I'm passing in. And it doesn't seem to work with string either. However when I try to pass an empty string (string errBuff = "";) with a char[] size of 2048, Unity will crash, which means that Unity is accessing the C library successfully? (DAQmxCreateTask() returns 0 should also prove this)
I'm a Unity developer, and I don't have any previous experience with DAQmx. I've successfully integrated and using other native library in Unity before, but I'm stuck on this one.
Solved! Go to Solution.
06-08-2021 09:50 AM - edited 06-08-2021 09:53 AM
First, I'm not sure what you refer to about creating a DLL from the DAQmx lib file. That lib file is simply an import library for the actual DAQmx dll file anyhow, so there IS actually a DLL file you can call. If you need some DLL import directives in Unity3D similar to C#'s Interop DLLImport declarations, since you can't link in lib files. All you need to do is directly referencing the DAQmx user space DLL nicaiu.dll that gets installed in your System directory by the DAQmx installer.
The problem you see could be either that your turning the daqmx.lib into a daqmx.dll didn't quite initialize the real daqmx dll correctly or it could be something with your Unity3D import directives not being quite right. This Interop stuff is always tricky to do right.
06-08-2021 11:30 AM
And since Unity3D seems to use C# in fact, you should not need to have to create your own Interop import declarations to use DAQmx from it.
This document describes the installation of DAQmx and other driver support for Visual Studio C# or other .Net applications. You should be able to import the according libraries directly in your C# project after that.
06-11-2021 02:43 PM
Hi, thanks for the help.
I tried to use the .net DLL for DAQmx in Unity, but it shows up as native plugin instead of managed plugin.
When I look this up, other people are having the same problem, and they suspect that DAQmx .net DLL is a "mixed library".
So I then try to wrap the c library (which according to internet is a "static library" hence no DLL for it) into a C++ DLL and use it in Unity.
It looks like at least this method is partially working, as the CreateTask() is returning a "0", meaning it succeeded, but all other methods returns the same error code -200088.
06-12-2021 04:46 PM
While the DAQmx .Net interface is of course a mixed library internally I was under the impression that its interface is fully managed. Under the hood it simply calls into the real DAQmx DLLs which of course are fully native. So I'm not quite sure what the problem is here. Is Unity3D only able to call fully managed DLLs? Doesn't it support Interop calls, that are used to call into native libraries under the hood?
07-14-2021 04:42 PM
Yes, I believe Unity cannot read mixed library. It sees the .Net dll as unmanaged.
However, I succeeded using LabView to get the readings and send to Unity through UDP.
Thanks for the help!
02-14-2026 09:43 PM - edited 02-14-2026 09:54 PM
@rolfk : You saved the day. Using "nicaiu.dll" was the answer. I've been struggling with this issue for such a long time, but your hint solved it! Thank you so much.
02-14-2026 09:49 PM - edited 02-14-2026 09:51 PM
By the way, here is an example. This code uses keyboard inputs, UDP packets (only skeleton: actual parser and control logic have not been implemented), and NIDAQ inputs (only the first three AIs are implemented) to control an Agent in Unity. You need to specify the device name and channels in the inspector. This code has been confirmed to work with USB6001 in Unity 6.3.
//AgentController.cs
using UnityEngine;
using UnityEngine.InputSystem;
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Collections.Generic;
using System.Threading;
using System.Runtime.InteropServices;
using System.Diagnostics;
/// <summary>
/// Threaded Controller managing Keyboard, UDP, and NI-DAQmx Analog Inputs.
/// Background threads handle I/O to keep the main Unity Render/Physics thread smooth.
/// </summary>
public class AgentController : MonoBehaviour
{
[Header("Movement Settings")]
public float moveSpeed = 10f;
public float rotationSpeed = 100f;
[Header("UDP Configuration")]
public bool useUDP = false;
public int udpPort = 11000;
private UdpClient udpClient;
private IPEndPoint remoteEndPoint;
private Thread udpThread;
private string lastUdpMsg = null;
private readonly object udpLock = new object(); // Synchronizes access to UDP string
[Header("NIDAQ Configuration")]
public bool useNIDAQ = false;
[Tooltip("Order: Element 0=X, 1=Y, 2=Z. Supports up to 8 channels.")]
public List<string> physicalChannels = new List<string> { "Dev1/ai0" };
private IntPtr taskHandle = IntPtr.Zero;
private Thread daqThread;
private double[] lastDaqSignals = new double[8]; // Thread-safe buffer for hardware values
private readonly object daqLock = new object(); // Synchronizes access to DAQ array
private int activeChannelCount = 0;
// Master switch to shut down background threads on exit
private bool isRunning = false;
void Start()
{
isRunning = true;
if (useUDP) InitUDPThreaded();
if (useNIDAQ) InitNIDAQThreaded();
}
/// <summary>
/// Starts a background thread to listen for UDP packets.
/// Only the latest packet is kept; older ones are overwritten.
/// </summary>
private void InitUDPThreaded()
{
try
{
udpClient = new UdpClient(udpPort);
remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
// Fixes a Windows-specific bug where UDP throws an exception if the remote port closes
const int SIO_UDP_CONNRESET = -1744830452;
udpClient.Client.IOControl(SIO_UDP_CONNRESET, new byte[] { 0 }, null);
udpThread = new Thread(() => {
while (isRunning)
{
// Check if data is waiting in the OS buffer
while (udpClient.Available > 0)
{
byte[] data = udpClient.Receive(ref remoteEndPoint);
string msg = Encoding.ASCII.GetString(data);
// Overwrite previous message so Update() only sees the newest data
lock (udpLock) { lastUdpMsg = msg; }
}
//Thread.Sleep(5); // Supposed to be 5ms, but in reality it is close to 15ms because of OS overhead.
Stopwatch sw = Stopwatch.StartNew();
while (sw.Elapsed.TotalMilliseconds < 5)
{
// "Spin" until time has passed -> Uses the CPU more but precise.
}
}
});
udpThread.IsBackground = true;
udpThread.Start();
}
catch (Exception e) { UnityEngine.Debug.LogError($"UDP Thread Init Failed: {e.Message}"); }
}
/// <summary>
/// Initializes NI-DAQmx via P/Invoke and starts a hardware polling thread.
/// </summary>
private void InitNIDAQThreaded()
{
if (physicalChannels == null || physicalChannels.Count == 0) return;
activeChannelCount = Mathf.Min(physicalChannels.Count, 8);
try
{
string config = string.Join(", ", physicalChannels.ToArray());
// Create and configure the hardware task
NINative.DAQmxCreateTask("AgentTask", out taskHandle);
NINative.DAQmxCreateAIVoltageChan(taskHandle, config, "", -1, -10.0, 10.0, 10348, null);
NINative.DAQmxStartTask(taskHandle);
daqThread = new Thread(() => {
double[] buffer = new double[activeChannelCount];
while (isRunning)
{
int read;
// Poll 1 sample per channel. Timeout 0.1s to allow thread to exit quickly.
int err = NINative.DAQmxReadAnalogF64(taskHandle, 1, 0.1, 0, buffer, (uint)activeChannelCount, out read, IntPtr.Zero);
if (err == 0 && read > 0)
{
lock (daqLock) { Array.Copy(buffer, lastDaqSignals, activeChannelCount); }
}
//Thread.Sleep(1); // Supposed to be 5ms, but in reality it is close to 15ms because of OS overhead.
Stopwatch sw = Stopwatch.StartNew();
while (sw.Elapsed.TotalMilliseconds < 5)
{
// "Spin" until time has passed -> Uses the CPU more but precise.
}
}
});
daqThread.IsBackground = true;
daqThread.Start();
UnityEngine.Debug.Log($"NIDAQ Thread Started for: {config}");
}
catch (Exception e) { UnityEngine.Debug.LogError($"NIDAQ Thread Init Failed: {e.Message}"); }
}
void Update()
{
Vector3 moveInput = Vector3.zero;
Vector3 rotInput = Vector3.zero;
Keyboard kb = Keyboard.current;
// 1. Capture Keyboard Input (Additive)
if (kb != null)
{
moveInput += GetKeyboardMovement(kb);
rotInput += GetKeyboardRotation(kb);
}
// 2. Capture UDP Input (Most recent only)
lock (udpLock)
{
if (lastUdpMsg != null)
{
moveInput += ParseUDP(lastUdpMsg);
lastUdpMsg = null; // Mark as "consumed" so we don't process it twice
}
}
// 3. Capture NIDAQ Input (Thread-safe copy)
if (useNIDAQ)
{
double[] sigs = new double[8];
lock (daqLock) { Array.Copy(lastDaqSignals, sigs, 8); }
moveInput += MapDaqToMovement(sigs);
//rotInput += MapDaqToMovement(sigs); // needs to be implemented
}
// 4. Move Agent
transform.Translate(moveInput * moveSpeed * Time.deltaTime, Space.Self);
transform.Rotate(rotInput * rotationSpeed * Time.deltaTime);
}
private Vector3 GetKeyboardMovement(Keyboard kb)
{
float x = 0, y = 0, z = 0;
if (kb.wKey.isPressed) z += 1; if (kb.sKey.isPressed) z -= 1;
if (kb.aKey.isPressed) x -= 1; if (kb.dKey.isPressed) x += 1;
if (kb.eKey.isPressed) y += 1; if (kb.qKey.isPressed) y -= 1;
return new Vector3(x, y, z);
}
private Vector3 GetKeyboardRotation(Keyboard kb)
{
float p = 0, y = 0, r = 0;
if (kb.jKey.isPressed) y -= 1; if (kb.lKey.isPressed) y += 1;
if (kb.iKey.isPressed) p -= 1; if (kb.kKey.isPressed) p += 1;
if (kb.uKey.isPressed) r += 1; if (kb.oKey.isPressed) r -= 1;
return new Vector3(p, y, r);
}
private Vector3 MapDaqToMovement(double[] signals)
{
// Maps list elements to axes: Index 0=X, 1=Y, 2=Z
float x = (activeChannelCount > 0) ? (float)signals[0] : 0;
float y = (activeChannelCount > 1) ? (float)signals[1] : 0;
float z = (activeChannelCount > 2) ? (float)signals[2] : 0;
return new Vector3(x, y, z);
}
private Vector3 ParseUDP(string msg)
{
// Example: Assume message is forward-only. Replace with your custom parser.
return Vector3.forward;
}
void OnApplicationQuit()
{
isRunning = false; // Flag background threads to stop loops
// Cleanup NI-DAQmx Task
if (taskHandle != IntPtr.Zero)
{
NINative.DAQmxStopTask(taskHandle);
NINative.DAQmxClearTask(taskHandle);
}
// Close UDP Socket
if (udpClient != null) udpClient.Close();
// Give threads time to finish clean shutdown
if (daqThread != null) daqThread.Join(200);
if (udpThread != null) udpThread.Join(200);
}
}
/// <summary>
/// Static Native P/Invoke wrapper for the National Instruments nicaiu.dll driver.
/// </summary>
public static class NINative
{
private const string dllName = "nicaiu.dll";
[DllImport(dllName)]
public static extern int DAQmxCreateTask(string taskName, out IntPtr taskHandle);
[DllImport(dllName)]
public static extern int DAQmxCreateAIVoltageChan(IntPtr taskHandle, string physicalChannel, string nameToAssignToChannel, int terminalConfig, double minVal, double maxVal, int units, string customScaleName);
[DllImport(dllName)]
public static extern int DAQmxStartTask(IntPtr taskHandle);
[DllImport(dllName)]
public static extern int DAQmxReadAnalogF64(IntPtr taskHandle, int numSampsPerChan, double timeout, uint fillMode, double[] readArray, uint arraySizeInSamps, out int sampsPerChanRead, IntPtr reserved);
[DllImport(dllName)]
public static extern int DAQmxStopTask(IntPtr taskHandle);
[DllImport(dllName)]
public static extern int DAQmxClearTask(IntPtr taskHandle);
}