Dobot Magician: DobotDllType.py und DobotDll.dll testen

Ausführliche Zusammenfassung des Testwegs – vom Projektaufbau über die Herkunft von COM10 bis zum gezielten Test des DLL-Ladens unter Python 3.14.5 (64 Bit).

1. Ziel des Tests

Das Ziel war, die Dobot-Python-API schrittweise zu testen und dabei die einzelnen Fehlerquellen sauber voneinander zu trennen:

  1. Läuft die verwendete Python-Version und stimmt die Architektur?
  2. Wird das SDK-Verzeichnis korrekt gefunden?
  3. Ist die Datei DobotDll.dll vorhanden?
  4. Kann DobotDllType.py als Python-Modul importiert werden?
  5. Kann DobotDllType.py anschließend die DLL wirklich laden?
  6. Erst danach: Kann eine Verbindung zum Dobot hergestellt werden?
Didaktischer Grundgedanke: Nicht mehrere Fehlerquellen gleichzeitig testen. Jeder Testschritt soll genau eine Frage beantworten.

2. Projektstruktur

Für die Tests wurde folgende Grundstruktur verwendet:

Dobot-Python/
├── dobot.py
├── sdk64/
│   ├── __init__.py
│   ├── DobotDllType.py
│   └── DobotDll.dll
└── api-testen/
    ├── start.py
    └── test.py

Die Datei test.py liegt zwei Ebenen unterhalb von Dobot-Python. Deshalb liefert:

Path(__file__).resolve().parent

den Ordner api-testen, während:

Path(__file__).resolve().parent.parent

den Projektordner Dobot-Python ergibt.

Wichtige Korrektur: Der Kommentar „Übergeordneten Ordner projekte in den Python-Suchpfad aufnehmen“ war ungenau. Tatsächlich wird hier der Ordner Dobot-Python in sys.path aufgenommen.

3. Ausgangspunkt: Test über dobot.py

Der erste Test verwendete:

from pathlib import Path
import sys

# Übergeordneten Ordner "Dobot-Python" in den Python-Suchpfad aufnehmen
PROJEKTORDNER = Path(__file__).resolve().parent.parent

if str(PROJEKTORDNER) not in sys.path:
    sys.path.insert(0, str(PROJEKTORDNER))

import dobot
from sdk64 import DobotDllType as dType

api = dobot.init()

sys.exit()

Die Ausgabe zeigte:

Python-Version:     3.14.5
Python-Architektur: 64bit
SDK-Verzeichnis:    D:\Github\andreas519.github.io\projekte\dobot_magician\Dobot-Python\sdk64
DLL-Datei:          D:\Github\andreas519.github.io\projekte\dobot_magician\Dobot-Python\sdk64\DobotDll.dll
DLL vorhanden:      True

FEHLER: Die serielle Schnittstelle COM10 ist nicht verfügbar.

Vom Betriebssystem erkannte COM-Ports:
  COM13    USB-SERIAL CH340 (COM13)

Das Programm wird beendet.

Process ended with exit code 1.

Damit war bereits klar:

4. Woher kam COM10?

In start.py stand kein COM10. Die Ursache war der Aufruf:

import dobot

api = dobot.init()

Mit import dobot wird das Modul dobot.py geladen. Dort ist bzw. war COM10 als Standardwert für die Initialisierung hinterlegt.

Ein Aufruf ohne Argument:

api = dobot.init()

entspricht deshalb sinngemäß einem Aufruf wie:

api = dobot.init("COM10")
Merksatz: Ein Wert muss nicht in der aktuellen Datei stehen. Er kann aus einem importierten Modul oder aus einem Standardparameter einer Funktion stammen.

Der aktuelle Windows-Computer erkannte den angeschlossenen USB-Seriell-Wandler jedoch als:

COM13    USB-SERIAL CH340 (COM13)

Damit war der erste Fehler verstanden: dobot.init() erwartete COM10, tatsächlich war aktuell COM13 vorhanden.

5. Direkter Test von DobotDllType.py

Um dobot.py als zusätzliche Fehlerquelle auszuschließen, wurde ein direkter Test von DobotDllType.py vorbereitet.

Die Testdatei ermittelt zunächst die wichtigsten Pfade:

from pathlib import Path
import platform
import sys

PROJEKTORDNER = Path(__file__).resolve().parent.parent

if str(PROJEKTORDNER) not in sys.path:
    sys.path.insert(0, str(PROJEKTORDNER))

sdk_verzeichnis = PROJEKTORDNER / "sdk64"
dll_datei = sdk_verzeichnis / "DobotDll.dll"

print("Python-Version:    ", platform.python_version())
print("Python-Architektur:", platform.architecture()[0])
print("SDK-Verzeichnis:   ", sdk_verzeichnis)
print("DLL-Datei:         ", dll_datei)
print("DLL vorhanden:     ", dll_datei.exists())

from sdk64 import DobotDllType as dType

print("DobotDllType.py:   ", dType.__file__)

Die zusätzliche Ausgabe von dType.__file__ ist besonders nützlich. Sie zeigt exakt, welche Datei tatsächlich importiert wurde:

DobotDllType.py:    D:\Github\andreas519.github.io\projekte\dobot_magician\Dobot-Python\sdk64\DobotDllType.py
Ergebnis: DobotDllType.py konnte erfolgreich importiert werden.

6. Unterschied: Python-Modul importieren und DLL laden

Diese beiden Schritte sind nicht dasselbe.

6.1 Python-Modul importieren

from sdk64 import DobotDllType as dType

Dadurch wird DobotDllType.py als Python-Modul geladen. Funktionen, Klassen und Konstanten stehen anschließend über dType zur Verfügung.

6.2 Die eigentliche DLL laden

api = dType.load()

Erst dieser Aufruf versucht, DobotDll.dll als native Windows-Bibliothek zu laden.

TestschrittWas wird geprüft?
from sdk64 import DobotDllType as dTypeKann das Python-Modul importiert werden?
dType.__file__Welche konkrete Datei wurde importiert?
dType.load()Kann die native DLL geladen werden?
Wichtig: Wenn api = dType.load() auskommentiert ist, wird die DLL nicht geladen. Dann kann der Import von DobotDllType.py erfolgreich sein, obwohl das spätere Laden der DLL noch fehlschlägt.

7. Der eigentliche DLL-Ladefehler

Mit aktiviertem Aufruf:

api = dType.load()

erschien:

Traceback (most recent call last):
  File "...\api-testen\test.py", line 25, in <module>
    api = dType.load()
  File "...\sdk64\DobotDllType.py", line 460, in load
    return CDLL("DobotDll.dll",  RTLD_GLOBAL)
  File "...\ctypes\__init__.py", line 451, in _load_library
    return _LoadLibrary(self._name, winmode)
FileNotFoundError: Could not find module 'DobotDll.dll'
(or one of its dependencies).
Try using the full path with constructor syntax.

Das wirkte zunächst widersprüchlich, weil vorher ausgegeben wurde:

DLL vorhanden:      True

7.1 Warum ist das kein Widerspruch?

Die Variable:

dll_datei = sdk_verzeichnis / "DobotDll.dll"

prüft nur, ob die Datei an diesem vollständigen Pfad vorhanden ist.

Die Funktion dType.load() verwendet diesen Pfad aber nicht. Laut Fehlermeldung führt DobotDllType.py aus:

return CDLL("DobotDll.dll", RTLD_GLOBAL)

Die DLL wird also nur anhand ihres Dateinamens gesucht:

DobotDll.dll

Das Verzeichnis sdk64 ist damit nicht automatisch Teil des Windows-DLL-Suchpfads.

test.py
│
├── findet:
│   D:\...\Dobot-Python\sdk64\DobotDll.dll
│
│   → DLL vorhanden: True
│
└── dType.load()
    │
    └── CDLL("DobotDll.dll")
        │
        └── sucht erneut nur nach dem Dateinamen
            → FileNotFoundError
Kernaussage: „Datei vorhanden“ bedeutet nicht automatisch „DLL kann von Windows geladen werden“.

8. Test mit os.add_dll_directory()

Für den nächsten Test wird das Verzeichnis sdk64 ausdrücklich in den Windows-DLL-Suchpfad aufgenommen.

dll_verzeichnis_handle = os.add_dll_directory(str(sdk_verzeichnis))

api = dType.load()

Die Funktion:

os.add_dll_directory(...)

registriert das angegebene Verzeichnis für die Suche nach DLLs.

Wichtig: Das zurückgegebene Handle wird in einer Variablen gespeichert. Es soll während des DLL-Ladens erhalten bleiben.

Dieser Test verändert DobotDllType.py zunächst nicht. Damit kann geprüft werden, ob das Problem tatsächlich nur im DLL-Suchpfad liegt.

9. Vollständiges Testprogramm

Die folgende Version trennt die Schritte klar voneinander und gibt verständliche Statusmeldungen aus:

from pathlib import Path
import os
import platform
import sys

# Übergeordneten Ordner "Dobot-Python" in den Python-Suchpfad aufnehmen
PROJEKTORDNER = Path(__file__).resolve().parent.parent

if str(PROJEKTORDNER) not in sys.path:
    sys.path.insert(0, str(PROJEKTORDNER))

sdk_verzeichnis = PROJEKTORDNER / "sdk64"
dll_datei = sdk_verzeichnis / "DobotDll.dll"

print("Python-Version:    ", platform.python_version())
print("Python-Architektur:", platform.architecture()[0])
print("SDK-Verzeichnis:   ", sdk_verzeichnis)
print("DLL-Datei:         ", dll_datei)
print("DLL vorhanden:     ", dll_datei.exists())

print()

# DobotDllType.py importieren
from sdk64 import DobotDllType as dType

print("DobotDllType.py:   ", dType.__file__)

print()
print("DLL-Verzeichnis wird zum Windows-DLL-Suchpfad hinzugefügt ...")

# Das Verzeichnis sdk64 für die DLL-Suche registrieren.
# Das Handle muss während des Ladens der DLL erhalten bleiben.
dll_verzeichnis_handle = os.add_dll_directory(str(sdk_verzeichnis))

print("DLL-Verzeichnis registriert.")
print()
print("DobotDll.dll wird geladen ...")

try:
    api = dType.load()

    print("DobotDll.dll wurde erfolgreich geladen.")
    print("API-Objekt:", api)

except Exception as fehler:
    print()
    print("FEHLER beim Laden der DobotDll.dll:")
    print(type(fehler).__name__ + ":", fehler)

    sys.exit(1)

print()
print("Test erfolgreich beendet.")

sys.exit()

Erwartete Ausgabe bei erfolgreichem Laden

Python-Version:     3.14.5
Python-Architektur: 64bit
SDK-Verzeichnis:    D:\...\Dobot-Python\sdk64
DLL-Datei:          D:\...\Dobot-Python\sdk64\DobotDll.dll
DLL vorhanden:      True

DobotDllType.py:    D:\...\Dobot-Python\sdk64\DobotDllType.py

DLL-Verzeichnis wird zum Windows-DLL-Suchpfad hinzugefügt ...
DLL-Verzeichnis registriert.

DobotDll.dll wird geladen ...
DobotDll.dll wurde erfolgreich geladen.
API-Objekt: <CDLL ...>

Test erfolgreich beendet.

10. Was bedeutet das Ergebnis?

Fall A: Die DLL wird erfolgreich geladen

Dann ist bewiesen:

Fall B: Es erscheint weiterhin FileNotFoundError

Dann kann eine Abhängigkeit der DLL fehlen. Die Fehlermeldung weist ausdrücklich darauf hin:

Could not find module 'DobotDll.dll' (or one of its dependencies).

In diesem Fall muss untersucht werden, ob DobotDll.dll weitere DLLs benötigt, die Windows nicht findet.

Fall C: Es erscheint ein Architekturfehler

Dann passen Python und DLL möglicherweise nicht zusammen, zum Beispiel:

Aktueller Stand des Testwegs: Der Import von DobotDllType.py funktioniert. Der Fehler tritt erst beim Aufruf von dType.load() auf. Der nächste gezielte Test ist deshalb die Registrierung des Ordners sdk64 mit os.add_dll_directory().

11. Mögliche dauerhafte Verbesserung von DobotDllType.py

Für das eigentliche Projekt kann es später sinnvoll sein, die DLL relativ zur Datei DobotDllType.py zu laden.

Statt:

return CDLL("DobotDll.dll", RTLD_GLOBAL)

könnte sinngemäß verwendet werden:

from pathlib import Path

dll_path = Path(__file__).resolve().parent / "DobotDll.dll"
return CDLL(str(dll_path), RTLD_GLOBAL)

Damit sucht das Modul die DLL immer in seinem eigenen Verzeichnis:

sdk64/
├── DobotDllType.py
└── DobotDll.dll
Vorteil: Das Test- oder Hauptprogramm kann aus einem beliebigen Unterordner gestartet werden, ohne dass das aktuelle Arbeitsverzeichnis stimmen muss.

12. Fragen und Arbeitsaufträge für Schüler

  1. Was ist der Unterschied zwischen Path.exists() und dem tatsächlichen Laden einer DLL?
  2. Warum kann DobotDllType.py erfolgreich importiert werden, obwohl DobotDll.dll anschließend nicht geladen werden kann?
  3. Welche Aufgabe hat sys.path?
  4. Was liefert __file__?
  5. Warum ist dType.__file__ für die Fehlersuche hilfreich?
  6. Warum ist ein relativer Dateiname wie "DobotDll.dll" weniger robust als ein vollständiger Pfad?
  7. Welche Bedeutung hat die Unterscheidung zwischen 32-Bit- und 64-Bit-Software?
  8. Warum sollte eine Fehlersuche schrittweise aufgebaut sein?

Merksätze

13. Empfohlene Reihenfolge für weitere Tests

  1. os.add_dll_directory() testen.
  2. Prüfen, ob dType.load() erfolgreich ist.
  3. Erst danach den Dobot über den aktuell vorhandenen COM-Port verbinden.
  4. Dann eine reine Leseoperation testen, zum Beispiel GetPose().
  5. Erst anschließend Bewegungsbefehle testen.
Sicherheitsprinzip: Bei einem Roboter zuerst Verbindungs- und Lesefunktionen testen. Bewegungen erst ausführen, wenn der Kommunikationsweg sicher funktioniert.

14. Zusammenfassung in einem Satz

Der bisherige Test zeigt, dass DobotDllType.py korrekt importiert wird, die Datei DobotDll.dll vorhanden ist, aber die ursprüngliche load()-Funktion die DLL nur über ihren Dateinamen sucht; deshalb wird nun das Verzeichnis sdk64 mit os.add_dll_directory() ausdrücklich für die Windows-DLL-Suche registriert.